안녕하세요, IT와 테크 지식을 공부하고 기록하는 루카(Luka)입니다.
오랜만에 글을 쓰게 되었네요. 최근 저희 팀에서는 기존 모놀리식 아키텍처의 일부 기능을 AWS Lambda 기반의 서버리스 아키텍처로 전환하는 프로젝트를 진행했습니다. 처음 서버리스 도입을 결정했을 때, 개발팀 내에서도 "과연 기존 EC2 기반의 안정적인 서비스를 람다로 옮기는 것이 현명할까?"라는 회의적인 시선도 많았습니다. 반신반의하며 시작했던 여정에서 제가 직접 겪은 시행착오와 깨달음을 솔직하게 공유하고자 합니다. 이 글이 서버리스 도입을 고민하는 분들께 실질적인 도움이 되기를 바랍니다.
서버리스, 왜 그렇게 핫한가요? (장점)
서버리스를 도입하면서 제가 가장 크게 체감했던 장점들은 다음과 같습니다. 교과서적인 이야기 같지만, 실제로 써보면 그 진가가 드러납니다.
1. 엄청난 비용 효율성: 쓴 만큼만 낸다!
제가 처음 개발한 간단한 사용자 인증 API는 월간 총 호출 수 10만 회, 평균 실행 시간 100ms 기준으로 월 0.18달러(한화 약 250원) 미만으로 운영될 수 있었습니다. 기존 EC2 인스턴스를 최소 사양으로 유지하는 것과 비교했을 때, 특정 이벤트성 트래픽이 많지 않은 서비스에서는 압도적인 비용 절감 효과를 가져왔습니다. 불필요하게 서버를 켜두는 비용 낭비가 사라진 거죠. 특히 유휴 상태일 때 추가 비용이 발생하지 않는다는 점은 매우 매력적이었습니다.
2. 운영 부담의 해방: 개발팀은 비즈니스 로직에 집중
서버 OS 패치, 런타임 업데이트, 서버 증설/축소 고민… 이 모든 인프라 관리의 굴레에서 벗어날 수 있었습니다. AWS가 서버 및 런타임 환경을 모두 관리해주니, 저희 개발팀은 오로지 비즈니스 로직 개발에만 집중할 수 있게 되었습니다. 덕분에 기존에는 평균 2주였던 배포 주기를 1주로 단축할 수 있었고, 더 빠르게 새로운 기능을 출시할 수 있게 되었습니다. 이는 생산성 측면에서 예상보다 훨씬 큰 이점이었습니다.
3. 마법 같은 자동 확장성: 트래픽 폭증에도 끄떡없다
저희 서비스는 특정 시즌에 트래픽이 평소 대비 50배 이상 폭증하는 이벤트가 있습니다. 기존 EC2 환경에서는 Auto Scaling Group 설정, 로드 밸런서, DB 연결 풀 최적화 등 복잡한 준비가 필요했습니다. 하지만 Lambda는 별도의 설정 없이도 이러한 트래픽을 무리 없이 처리했습니다. 제가 직접 모니터링했을 때, 초당 1,500건의 요청이 동시에 몰려와도 안정적으로 응답하는 것을 확인했습니다. 순간적인 대량 요청에 대한 걱정이 사라졌다는 것만으로도 팀원들의 심리적 안정감이 크게 향상되었습니다.
빛 좋은 개살구? 서버리스의 그림자 (단점 및 고려사항)
물론 서버리스가 항상 장밋빛만 있는 것은 아닙니다. 도입 과정에서 몇 가지 어려움과 단점들을 직접 겪었고, 이를 해결하기 위한 노력이 필요했습니다.
1. 개발자를 괴롭히는 콜드 스타트(Cold Start)
가장 먼저 체감한 문제는 바로 콜드 스타트였습니다. 함수가 오랫동안 호출되지 않다가 처음 호출될 때 발생하는 초기화 지연 현상인데, 특히 JVM 기반 런타임(Java, Scala)에서는 그 체감 속도가 더욱 심했습니다.
-
실측 예시: 제가 Python 3.9 런타임으로 만든 간단한 API는 콜드 스타트 시 평균 2.8초의 지연이 발생했습니다. 반면, Node.js 16.x는 2.5초, Java 11 런타임은 무려 5.1초까지 지연되는 것을 경험했습니다. 사용자 경험에 직접적인 영향을 미 미치는 만큼, 이 문제를 해결하는 것이 시급했습니다.
-
해결책:
- Provisioned Concurrency: 특정 수의 함수 인스턴스를 항상 '핫(Hot)' 상태로 유지하는 기능입니다. 중요한 API에는 필수적으로 적용했습니다.
-
Warmer 패턴 (serverless-plugin-warmup): 저희는 Serverless Framework의
serverless-plugin-warmup플러그인을 사용하여, 매일 새벽 트래픽이 없는 시간에 주기적으로 함수를 호출해 웜업 시켰습니다. ```yaml # serverless.yml (일부) plugins:- serverless-plugin-warmup
custom: warmup: enabled: true events: - schedule: 'cron(0 0 ? * MON-FRI *)' # 매주 월~금 새벽 0시에 웜업 concurrency: 2 # 각 함수당 2개의 인스턴스를 웜업
functions: myFunction: handler: handler.my_function events: - http: path: /my-path method: get warmup: - package: individually: true # 각 함수별로 개별 패키징하여 웜업 ``` 이 설정으로 새벽 시간에도 최소한의 인스턴스가 준비되어 있도록 하여, 아침 첫 요청의 콜드 스타트 지연을 평균 0.5초 이내로 줄일 수 있었습니다.
2. 모니터링 및 디버깅의 복잡성
람다는 작은 단위의 함수로 쪼개져 실행되기 때문에, 기존 모놀리식 서비스처럼 단일 로그 파일을 추적하는 것이 불가능합니다. 여러 함수와 AWS 서비스(API Gateway, SQS, DynamoDB 등)가 엮이면 문제가 어디서 발생하는지 파악하기가 매우 어려워집니다. CloudWatch Logs만으로는 충분하지 않았습니다.
- 해결책:
- AWS X-Ray 도입: 분산 추적 기능을 통해 요청이 어떤 함수를 거쳐 어떤 서비스와 상호작용하는지 시각적으로 파악할 수 있게 되었습니다. 특정 요청에서 병목이 발생하는 지점을 평균 1.2초 내에 찾아낼 수 있게 되면서, 디버깅 시간이 획기적으로 줄었습니다.
- 구조화된 로깅: 모든 함수에서 JSON 형식으로 로그를 출력하여, CloudWatch Logs Insights에서 효과적으로 쿼리하고 분석할 수 있도록 했습니다.
3. 벤더 종속성
서버리스 아키텍처는 특정 클라우드 벤더(AWS, Azure, Google Cloud 등)에 대한 높은 종속성을 가집니다. AWS Lambda를 사용하면 DynamoDB, S3, API Gateway 등 다른 AWS 서비스와 긴밀하게 통합되기 때문에, 나중에 다른 클라우드로 마이그레이션하기가 쉽지 않을 수 있습니다. 이는 장기적인 관점에서 신중하게 고려해야 할 부분입니다.
4. 최대 실행 시간 제한 (Timeout)
Lambda 함수는 기본 3초, 최대 15분이라는 실행 시간 제한이 있습니다. 이는 대부분의 웹 요청 처리에는 충분하지만, 백그라운드에서 대용량 데이터를 처리하거나 복잡한 계산을 수행하는 작업에는 부적합합니다. * 해결책: 이런 작업에는 Lambda 대신 AWS Step Functions를 사용하여 여러 Lambda 함수를 조합하거나, SQS와 Lambda를 연동하여 비동기 처리 파이프라인을 구축했습니다. 예를 들어, 대용량 이미지 압축 작업은 S3에 이미지가 업로드되면 SQS에 메시지를 보내고, 이 메시지를 Lambda가 받아서 짧게 여러 번 처리하는 방식으로 구현했습니다.
AWS Lambda 도입 시 루카의 실전 가이드
제가 직접 겪으며 배운 Lambda 도입 시의 구체적인 팁들을 공유합니다.
1. 언어 선택과 런타임 최적화
- 콜드 스타트 관점: Python, Node.js, Go 런타임이 Java나 C#(.NET) 런타임에 비해 콜드 스타트가 짧은 편입니다. 저희는 주로 Python과 Node.js를 사용했습니다.
- 패키지 크기 줄이기: 배포 패키지 크기는 콜드 스타트에 직접적인 영향을 미칩니다. 불필요한 라이브러리 제거는 물론,
pip install --target .명령을 사용하여 필요한 라이브러리만 포함하도록 했습니다. 제가 운영하던 Node.js Lambda 함수의node_modules폴더를 최적화하여 배포 패키지 크기를 120MB에서 28MB로 줄였더니, 빌드 시간이 42초에서 11초로 단축되었습니다.
2. 메모리 설정의 중요성
Lambda의 메모리 설정은 실행 시간과 비용에 모두 영향을 미칩니다. 무조건 메모리를 높게 잡는다고 좋은 것이 아닙니다.
- 실측 예시: 제가 운영하는 이미지 리사이징 Lambda 함수는 256MB에서 512MB로 늘렸을 때 실행 시간이 300ms에서 150ms로 절반 가까이 줄었습니다. 하지만 1024MB로 늘려도 140ms로 큰 차이가 없었고, 오히려 비용만 늘어났습니다.
- AWS Lambda Power Tuning 툴 활용: AWS Step Functions 기반의 오픈소스 툴인
AWS Lambda Power Tuning을 사용하면, 다양한 메모리 설정에서 함수를 실행하여 성능과 비용의 최적 지점을 쉽게 찾을 수 있습니다. 저는 이 툴을 통해 대부분의 함수에서 최적 메모리 지점이 256MB 또는 512MB임을 확인했습니다.
3. VPC 설정 시 유의사항
데이터베이스나 내부 API 등 VPC 내부에 있는 리소스에 접근해야 할 경우, Lambda를 VPC 내부에 배치해야 합니다. 하지만 이 경우 콜드 스타트 시간이 평균 1~2초 더 길어지는 단점이 있습니다. 이는 Lambda가 VPC 내에서 ENI(Elastic Network Interface)를 프로비저닝하는 데 시간이 걸리기 때문입니다.
- 해결책:
- VPC Endpoint 사용: 가능하다면 S3, DynamoDB 등 AWS 서비스에 접근할 때는 VPC Endpoint를 사용하여 퍼블릭 인터넷을 통하지 않고도 안전하고 빠르게 접근할 수 있도록 설정했습니다. 이는 콜드 스타트 지연을 줄이는 데 도움이 되었습니다.
- 필요한 경우에만 VPC 사용: 모든 Lambda 함수를 VPC 내부에 배치하기보다, 반드시 내부 리소스에 접근해야 하는 함수만 VPC에 두는 것이 좋습니다.
루카's 꿀팁: Serverless Framework와 SAM 활용
수동으로 콘솔에서 Lambda 함수를 설정하는 것은 초기에는 편리할 수 있지만, 장기적으로는 비효율적이고 오류 발생 가능성이 높습니다. Infrastructure as Code(IaC)는 선택이 아닌 필수입니다. 저희 팀은 주로 AWS Serverless Application Model(SAM)과 Serverless Framework를 병행해서 사용하고 있습니다.
1. SAM CLI를 이용한 로컬 개발 및 테스트
SAM CLI는 AWS Lambda 및 API Gateway를 로컬 환경에서 에뮬레이션하여 개발 및 테스트를 훨씬 수월하게 해줍니다.
# 새로운 서버리스 애플리케이션 초기화 (Python 런타임 예시)
sam init --runtime python3.9 --name my-serverless-app --app-template hello-world
# 생성된 프로젝트 디렉토리로 이동
cd my-serverless-app
# 로컬에서 API Gateway 에뮬레이션 시작
sam local start-api
실행 결과 예시:
Mounting HelloWorldFunction at http://127.0.0.1:3000/hello [GET]
You can now browse to the above endpoints to test your code.
Press CTRL+C to quit
이후 http://127.0.0.1:3000/hello로 접속하여 로컬에서 Lambda 함수가 제대로 동작하는지 바로 확인할 수 있습니다.
2. Serverless Framework를 이용한 배포 최적화 예시
Serverless Framework는 개발 생산성을 높이고 배포를 자동화하는 데 매우 강력한 도구입니다. serverless.yml 파일을 통해 모든 설정을 코드로 관리할 수 있습니다.
# serverless.yml (일부)
service: my-app-service # 서비스 이름
provider:
name: aws
runtime: python3.9
region: ap-northeast-2
stage: dev # 개발/스테이징/운영 환경 구분
memorySize: 256 # 기본 메모리 설정 (MB)
timeout: 30 # 기본 타임아웃 (초)
logRetentionInDays: 7 # CloudWatch 로그 보존 기간
environment: # 환경 변수
TABLE_NAME: ${self:service}-${opt:stage}-table
vpc: # VPC 설정 (필요한 경우)
securityGroupIds:
- sg-0123456789abcdef0 # 보안 그룹 ID
subnetIds:
- subnet-0abcdef1234567890 # 서브넷 ID
iam: # IAM 권한 설정
role:
statements:
- Effect: "Allow"
Action:
- dynamodb:GetItem
- dynamodb:PutItem
Resource: "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/${self:service}-${opt:stage}-table"
functions:
myApiFunction:
handler: handler.my_api_function # 함수 핸들러
events:
- http:
path: /data
method: get
cors: true # CORS 설정
provisionedConcurrency: 1 # 콜드 스타트 최소화를 위한 프로비저닝된 동시성
layers:
- arn:aws:lambda:ap-northeast-2:123456789012:layer:my-common-layer:1 # 공통 라이브러리 레이어 사용 예시
위 설정은 Lambda 함수, API Gateway, DynamoDB 접근 권한, VPC 설정 등을 한 번에 정의하고 배포할 수 있도록 해줍니다. provisionedConcurrency 설정으로 콜드 스타트도 관리할 수 있습니다.
자주 겪는 문제와 해결법
제가 서버리스 프로젝트를 진행하면서 가장 자주 겪었던 문제들과 그 해결법을 공유합니다.
1. "Task timed out after X.00 seconds" 에러
- 발생 원인: Lambda 함수가 설정된 타임아웃 시간 내에 작업을 완료하지 못했을 때 발생합니다. 특히, Lambda가 VPC 내부에 있을 때 외부 리소스(인터넷, S3 등) 접근을 시도하는데, NAT Gateway나 VPC Endpoint 설정이 잘못되어 네트워크 지연이 발생하거나 아예 접근이 안 될 때 자주 겪었습니다. 혹은 DB 연결 풀을 제대로 설정하지 않아 매번 새로운 연결을 맺느라 시간이 오래 걸리는 경우도 있었습니다.
- 해결 방법:
- 타임아웃 증대: 먼저 함수 타임아웃 시간을 늘려봅니다 (최대 15분).
- 코드 최적화: 코드 내에서 불필요한 지연 요소를 제거하거나, 병렬 처리가 가능한 부분을 찾아 최적화합니다.
- 네트워크 설정 확인: Lambda가 VPC 내부에 있다면, NAT Gateway 또는 VPC Endpoint가 올바르게 설정되어 외부망 접근이 원활한지 확인합니다. 보안 그룹(Security Group)이 아웃바운드(Outbound) 규칙을 제대로 허용하는지 검토해야 합니다.
- DB 연결 풀 확인: 데이터베이스 연결 풀이 제대로 작동하여 불필요한 연결 생성 오버헤드를 줄이고 있는지 확인합니다.
2. "Permissions error: You are not authorized to perform this operation"
- 발생 원인: Lambda 함수가 사용하는 IAM 실행 역할(Execution Role)에 필요한 AWS 서비스에 대한 접근 권한이 없을 때 발생합니다. 예를 들어, S3 버킷에 파일을 저장해야 하는데 S3 PutObject 권한이 없는 경우입니다.
- 해결 방법:
- CloudWatch Logs 확인: 에러 메시지에 어떤 권한이 부족한지(
AccessDenied) 구체적으로 명시되어 있는 경우가 많습니다. 로그를 통해 정확한Action과Resource를 파악합니다. - IAM 역할 정책 수정: Lambda 함수에 할당된 IAM 역할에 필요한 최소한의 권한을 부여하는 정책을 추가합니다. 예를 들어, S3 버킷에 읽기/쓰기 권한이 필요하다면 다음과 같이 추가합니다.
bash aws iam attach-role-policy --role-name MyLambdaExecutionRole \ --policy-arn arn:aws:iam::aws:policy/AmazonS3FullAccess # 예시, 실제로는 최소 권한을 부여하세요이때AmazonS3FullAccess와 같은 관리형 정책보다는, 필요한 작업(s3:GetObject,s3:PutObject)만 허용하는 사용자 지정 정책을 생성하여 적용하는 것이 보안상 더 좋습니다.
- CloudWatch Logs 확인: 에러 메시지에 어떤 권한이 부족한지(
3. "No space left on device" 에러 (Disk Full)
- 발생 원인: Lambda 함수가 실행될 때 제공되는 임시 스토리지
/tmp공간이 부족할 때 발생합니다. Lambda의/tmp디렉토리는 기본적으로 512MB인데, 대용량 파일 처리, 압축 해제, 머신러닝 모델 로딩 등에서 이 공간을 초과할 수 있습니다. - 해결 방법:
- Ephemeral storage 증대: Lambda 함수 설정에서
Ephemeral storage(임시 스토리지) 크기를 늘립니다. 최대 10GB까지 설정할 수 있습니다.- 콘솔에서 설정: Lambda 함수 설정 > 구성 > 일반 구성 > 편집 > Ephemeral storage (MB)
- S3 활용:
/tmp공간을 직접 사용하기보다, S3 버킷을 중간 저장소로 활용하여 대용량 파일을 처리합니다. 필요한 데이터를 S3에서 다운로드하여 처리한 후, 결과물을 다시 S3에 업로드하고/tmp의 파일을 즉시 삭제하는 방식으로 공간을 관리합니다. - 불필요한 파일 삭제: 함수 실행이 끝날 때
/tmp디렉토리에 생성된 불필요한 파일을 명시적으로 삭제하여 다음 호출에 대비합니다.
- Ephemeral storage 증대: Lambda 함수 설정에서
서버리스 vs. 전통적인 서버 비교
서버리스와 전통적인 서버(EC2 등) 아키텍처의 주요 특징을 비교한 표입니다.
| 특징 | 전통적인 서버 (EC2, VM) | 서버리스 (AWS Lambda) |
|---|---|---|
| 인프라 관리 | 개발팀이 직접 OS, 런타임, 확장성 등을 관리해야 함 | AWS가 서버, OS, 런타임 환경 등 인프라를 모두 관리 |
| 비용 모델 | 서버가 켜져 있는 시간에 따라 고정 비용 과금 (시간당) | 함수 실행 시간에 따라 종량제로 과금 (ms 단위) |
| 확장성 | 수동 설정 또는 Auto Scaling 그룹 구성 필요 | 트래픽에 맞춰 자동으로 거의 무한에 가깝게 확장 |
| 시작 시간 | OS 부팅, 애플리케이션 시작 시간 포함 (수 분~수 십분) | 콜드 스타트 발생 가능 (수백 ms ~ 수 초), 이후 빠름 |
| 운영 복잡도 | 상대적으로 높음 (패치, 모니터링, 로드 밸런싱, 보안) | 상대적으로 낮음 (코드와 설정에 집중, 인프라 걱정 X) |
| 유휴 비용 | 항상 발생 (서버가 켜져 있으면) | 거의 발생하지 않음 (유휴 상태일 때 비용 없음) |
| 실행 시간 제한 | 일반적으로 없음 | 최대 15분으로 제한됨 |
| 벤더 종속성 | 상대적으로 낮음 (VM 이미지 이식 가능성) | 특정 클라우드 벤더에 대한 종속성이 높음 |
핵심 요약
- 서버리스 아키텍처는 인프라 관리 부담을 줄여주고, 사용량 기반의 비용 효율성, 그리고 뛰어난 자동 확장성을 제공합니다.
- 하지만 콜드 스타트 지연, 분산 시스템 모니터링의 복잡성, 벤더 종속성, 실행 시간 제한 등 해결해야 할 과제들도 명확히 존재합니다.
- AWS Lambda 도입 시에는 언어 및 런타임 선택, 메모리 최적화, VPC 설정 등을 신중하게 고려하고, Serverless Framework나 SAM과 같은 IaC(Infrastructure as Code) 도구를 적극적으로 활용하여 효율적인 개발 및 운영 환경을 구축하는 것이 중요합니다.
자주 묻는 질문 (FAQ)
Q: 서버리스는 모든 애플리케이션에 적합한가요? A: 아닙니다. 서버리스는 이벤트 기반의 짧은 실행 시간을 가진 기능(API 엔드포인트, 데이터 처리, 백그라운드 작업 등)에 매우 효과적입니다. 하지만 장시간 실행되는 작업, 특정 하드웨어에 의존하는 애플리케이션, 복잡한 실시간 스트리밍 처리 등에는 적합하지 않을 수 있습니다. 서비스의 특성을 충분히 분석한 후 도입 여부를 결정해야 합니다.
Q: 콜드 스타트 문제를 완전히 없앨 방법은 없나요? A: 완전히 없애기는 어렵지만, 최소화할 수 있습니다. Provisioned Concurrency, Warmer 패턴을 사용하여 함수 인스턴스를 미리 준비해두거나, Go나 Rust와 같이 시작 시간이 빠른 언어를 사용하고, 배포 패키지 크기를 최소화하는 등의 노력을 통해 콜드 스타트의 영향을 크게 줄일 수 있습니다.
Q: 서버리스 환경에서 데이터베이스는 어떻게 관리하나요? A: 서버리스 애플리케이션에서는 주로 관리형 데이터베이스 서비스(RDS Aurora Serverless, DynamoDB)를 많이 사용합니다. Lambda와 마찬가지로 사용한 만큼만 비용을 지불하고 자동으로 확장되기 때문에 서버리스 아키텍처와 시너지가 좋습니다. 기존 관계형 데이터베이스를 사용해야 한다면, RDS Proxy를 활용하여 Lambda의 DB 연결 관리 문제를 해결하는 것이 일반적입니다.
제가 직접 부딪히고 깨지면서 얻은 서버리스, 특히 AWS Lambda에 대한 경험담과 팁들이 여러분의 서버리스 여정에 조금이나마 도움이 되었으면 좋겠습니다. 기술은 결국 목적을 위한 수단임을 잊지 않고, 여러분의 서비스에 가장 적합한 아키텍처를 찾아 성공적인 개발을 이어가시기를 응원합니다.
궁금한 점이나 저와 다른 경험이 있으시다면 언제든지 댓글로 공유해주세요. 함께 이야기 나누는 것은 언제나 환영입니다!
다음 포스팅에서 또 만나요!