서버리스로 바꿨더니 비용이 3배?

서버리스(Serverless)로 전환하면 비용이 줄어든다고 들었습니다. 하지만 저희 팀은 전환 첫 달에 청구서가 3배가 되는 충격을 겪었습니다. 원인을 분석하고 6개월간 최적화한 끝에 비용 70% 절감응답 시간 60% 개선을 달성했습니다. 그 과정을 공유합니다.

왜 비용이 폭발했나?

첫 달 청구서를 분석해보니:

항목 예상 실제 원인
Lambda 실행 $200 $800 메모리 과다 설정, 긴 실행 시간
API Gateway $50 $150 캐싱 미설정, 불필요한 호출
CloudWatch $30 $200 로그 과다 출력
데이터 전송 $20 $100 응답 크기 최적화 안 함

문제의 핵심: Lambda 비용 구조를 이해하지 못한 채 마이그레이션했습니다.

Lambda 비용 구조: 이것만 이해하면 됩니다

Lambda 비용은 크게 4가지로 나뉩니다:

1. 요청 수 (Request)

  • 100만 건당 $0.20
  • 상대적으로 저렴. 최적화 우선순위 낮음

2. 실행 시간 × 메모리 (Duration × Memory)

  • GB-초당 $0.0000166667
  • 가장 중요! 이게 비용의 80% 이상
  • 예: 1GB 메모리, 1초 실행, 100만 회 = $16.67

3. 프로비저닝 동시성 (Provisioned Concurrency)

  • GB-시간당 $0.0000041667
  • Cold Start 방지용. 항상 켜두는 인스턴스
  • 필요한 만큼만 설정

4. 데이터 전송 (Data Transfer)

  • 아웃바운드 GB당 $0.09
  • 큰 응답을 자주 보내면 비용 증가

비용 계산 예시

[code=js]
// Lambda 비용 계산기
// 실제로 사용해보세요!
function calculateLambdaCost({
memoryMB, // 메모리 (MB)
avgDurationMs, // 평균 실행 시간 (ms)
requestsPerMonth, // 월 요청 수
provisionedCount = 0 // 프로비저닝 동시성 수
}) {
// 1. 요청 비용
// 첫 100만 건 무료, 이후 $0.20/백만
const requestCost = Math.max(0, (requestsPerMonth – 1_000_000)) * 0.0000002;
// 2. 실행 비용
// GB-초 = (메모리MB / 1024) × (시간ms / 1000)
const gbSeconds = (memoryMB / 1024) * (avgDurationMs / 1000) * requestsPerMonth;
// 첫 40만 GB-초 무료
const billableGbSeconds = Math.max(0, gbSeconds – 400_000);
const durationCost = billableGbSeconds * 0.0000166667;
// 3. 프로비저닝 비용
// GB-시간 = (메모리MB / 1024) × 24시간 × 30일
const provisionedGbHours = (memoryMB / 1024) * 24 * 30 * provisionedCount;
const provisionedCost = provisionedGbHours * 0.0000041667;
return {
requestCost: requestCost.toFixed(2),
durationCost: durationCost.toFixed(2),
provisionedCost: provisionedCost.toFixed(2),
totalCost: (requestCost + durationCost + provisionedCost).toFixed(2)
};
}
// 예시: 최적화 전
console));
// → $467.00/월
// 예시: 최적화 후
console));
// → $38.89/월 (88% 절감!)
[/code]

최적화 1: 메모리 튜닝 (가장 중요!)

Lambda에서 메모리를 늘리면 CPU도 비례해서 증가합니다. 직관과 다르게, 메모리를 늘리면 비용이 오히려 줄어들 수 있습니다.

왜 그런가?

메모리 CPU 비율 실행 시간 GB-초 비용/실행
128MB ~7% 3,200ms 0.4 $0.0000067
512MB ~28% 850ms 0.43 $0.0000071
1024MB ~55% 420ms 0.43 $0.0000070
2048MB ~100% 380ms 0.76 $0.0000127

1024MB가 최적점입니다. 128MB 대비 7배 빠르면서 비용은 거의 같습니다!

AWS Lambda Power Tuning으로 최적점 찾기

AWS에서 공식 제공하는 Power Tuning 도구를 사용하면 자동으로 최적 메모리를 찾아줍니다.

[code=bash]
# Step 1: Power Tuning 배포
# SAM(Serverless Application Model) 사용
git clone

cd aws-lambda-power-tuning
sam deploy –guided
# 배포 후 Step Functions 상태 머신이 생성됨
[/code]
[code=bash]
# Step 2: 테스트 실행
# 128MB ~ 3008MB까지 다양한 메모리로 함수 실행
aws stepfunctions start-execution
–state-machine-arn arn:aws:states:ap-northeast-2:123456789:stateMachine:powerTuningStateMachine
–input ‘{
“lambdaARN”: “arn:aws:lambda:ap-northeast-2:123456789:function:my-api”,
“powerValues”: [128, 256, 512, 1024, 1536, 2048, 3008],
“num”: 50,
“payload”: “{“httpMethod”: “GET”, “path”: “/users”}”
}’
# 실행 결과:
# {
# “power”: 1024, // 최적 메모리
# “cost”: 0.0000070, // 실행당 비용
# “duration”: 420, // 평균 실행 시간(ms)
# “stateMachine”: {…} // 상세 결과 링크
# }
[/code]

팁: 코드 변경 후에는 다시 튜닝하세요. 최적점이 바뀔 수 있습니다.

최적화 2: Cold Start 줄이기

Lambda는 요청이 없으면 인스턴스가 내려갑니다. 다음 요청이 오면 새 인스턴스를 띄우는데, 이게 Cold Start입니다. 수백 ms ~ 수 초가 걸릴 수 있습니다.

Cold Start 원인 분석

요인 영향 해결책
런타임 Java > Python > Node.js > Rust 가벼운 런타임 선택
패키지 크기 크면 클수록 느림 번들 최적화
VPC 연결 ENI 생성 시간 VPC Lambda Hyperplane
초기화 코드 import 많으면 느림 지연 로딩

번들 크기 최적화 (Node.js)

[code=js]
// webpack.config.js
// Lambda용 번들 최적화 설정
const TerserPlugin = require(‘terser-webpack-plugin’);
const BundleAnalyzerPlugin = require(‘webpack-bundle-analyzer’).BundleAnalyzerPlugin;
module,
// AWS SDK는 Lambda 런타임에 이미 포함되어 있음
// 번들에 포함하면 용량만 커짐
externals: [
‘aws-sdk’, // v2
‘@aws-sdk/*’ // v3
],
optimization: {
minimize: true,
minimizer: [new TerserPlugin({
terserOptions: {
keep_fnames: false, // 함수명 난독화
mangle: true // 변수명 축소
}
})]
},
plugins: [
// 번들 분석 리포트 생성 (어떤 라이브러리가 큰지 확인)
new BundleAnalyzerPlugin({
analyzerMode: ‘static’,
reportFilename: ‘bundle-report.html’
})
]
};
[/code]

번들 최적화 결과:

항목 Before After 개선
번들 크기 45MB 2.8MB -94%
Cold Start 2.8초 0.4초 -86%

Provisioned Concurrency (비용 주의!)

Cold Start를 완전히 없애려면 Provisioned Concurrency를 사용합니다. 항상 켜져 있는 인스턴스를 유지하는 것입니다.

[code=yaml]
# serverless.yml
functions:
api:
handler: src/handler.main
memorySize: 1024
timeout: 30
# 항상 5개 인스턴스 유지
provisionedConcurrency: 5
# 하지만 24시간 유지하면 비용 폭발!
# 트래픽 패턴에 맞춰 스케줄링 권장
[/code]

비용 계산 예시:

  • 1GB × 5개 × 24시간 × 30일 = 3,600 GB-시간
  • 3,600 × $0.0000041667 = $15/월
  • 트래픽 적으면 On-Demand보다 비쌀 수 있음!

권장 방식: 피크 시간에만 Provisioned Concurrency 활성화

[code=yaml]
# CloudWatch Events로 스케줄링
# 오전 9시에 5개로 늘리고, 밤 10시에 0개로
resources:
Resources:
ScaleUpRule:
Type: AWS::Events::Rule
Properties:
ScheduleExpression: cron(0 9 * * ? *) # 매일 9시
Targets:
– Id: scale-up
Arn: !GetAtt ScaleLambda’
ScaleDownRule:
Type: AWS::Events::Rule
Properties:
ScheduleExpression: cron(0 22 * * ? *) # 매일 22시
Targets:
– Id: scale-down
Arn: !GetAtt ScaleLambda’
[/code]

최적화 3: 불필요한 호출 제거

API Gateway 캐싱

같은 요청에 대해 매번 Lambda를 호출하면 낭비입니다. API Gateway 레벨에서 캐싱하면 Lambda 호출 자체를 줄일 수 있습니다.

[code=yaml]
# serverless.yml
functions:
getProducts:
handler: src/products.list
events:
– http:
path: /products
method: GET
# API Gateway 캐싱 활성화
caching:
enabled: true
ttlInSeconds: 300 # 5분간 캐시
# 캐시 키: 이 파라미터가 다르면 다른 캐시
cacheKeyParameters:
– name: request.querystring.category
– name: request.querystring.page
[/code]

효과:

  • 캐시 히트 시 Lambda 호출 안 함
  • 응답 시간: 500ms → 20ms
  • Lambda 비용 50-80% 절감 가능

중복 호출 방지 (멱등성)

네트워크 문제로 같은 요청이 여러 번 올 수 있습니다. 중복 처리를 방지해야 합니다.

[code=js]
// 멱등성 키를 사용한 중복 방지
const { DynamoDB } = require(‘@aws-sdk/client-dynamodb’);
const crypto = require(‘crypto’);
const dynamodb = new DynamoDB();
async function processWithIdempotency(event, processor) {
// 1. 요청 내용을 해시하여 고유 키 생성
const idempotencyKey = crypto
.createHash(‘sha256’)
.update(JSON.stringify(event.body))
.digest(‘hex’);
// 2. DynamoDB에 키 저장 시도
try {
await dynamodb,
ttl: { N: String(Math.floor(Date.now() / 1000) + 3600) } // 1시간 후 만료
},
// 이미 키가 있으면 실패
ConditionExpression: ‘attribute_not_exists(pk)’
});
} catch (e) {
if (e;
}
throw e;
}
// 3. 중복이 아니면 실제 처리
return await processor(event);
}
[/code]

최종 비용 절감 결과

항목 Before After 절감
월 Lambda 비용 $2,400 $720 -70%
평균 응답 시간 850ms 340ms -60%
Cold Start 비율 15% 2% -87%
월 요청 수 5천만 3천만 -40% (캐싱)

최적화 체크리스트

  1. 메모리 튜닝: Power Tuning으로 최적점 찾기
  2. 번들 최적화: 외부 의존성 최소화, Tree Shaking
  3. 캐싱 적용: API Gateway, CloudFront 레벨
  4. Provisioned Concurrency: 피크 시간만 적용
  5. 모니터링: CloudWatch로 이상 징후 감지

결론: 서버리스가 항상 저렴하진 않다

서버리스는 트래픽 패턴에 따라 비용 효율이 달라집니다:

  • 유리한 경우: 트래픽 변동이 큼, 야간/주말 트래픽 적음
  • 불리한 경우: 24시간 일정한 트래픽, 장시간 실행 작업

무작정 “서버리스 = 저렴”이라고 생각하지 마세요. 비용 구조를 이해하고, 최적화를 적용해야 진정한 비용 절감을 달성할 수 있습니다.

 

aws lambda