서버리스로 바꿨더니 비용이 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
  • 큰 응답을 자주 보내면 비용 증가

비용 계산 예시

// 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% 절감!)

최적화 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 도구를 사용하면 자동으로 최적 메모리를 찾아줍니다.

# Step 1: Power Tuning 배포
# SAM(Serverless Application Model) 사용
git clone 
                
                    
                        
                    
                    
                        GitHub - alexcasalboni/aws-lambda-power-tuning: AWS Lambda Power Tuning is an open-source tool that can help you visualize and fine-tune the memory/power configuration of Lambda functions. It runs in your own AWS account - powered by AWS Step Functions - and it supports three optimization strategies: cost, speed, and balanced.
                        AWS Lambda Power Tuning is an open-source tool that can help you visualize and fine-tune the memory/power configuration of Lambda functions. It runs in your own AWS account - powered by AWS Step Fu...
                    
                
            

cd aws-lambda-power-tuning
sam deploy --guided
# 배포 후 Step Functions 상태 머신이 생성됨
# 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": {...}    // 상세 결과 링크
# }

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

최적화 2: Cold Start 줄이기

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

Cold Start 원인 분석

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

번들 크기 최적화 (Node.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'
    })
  ]
};

번들 최적화 결과:

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

Provisioned Concurrency (비용 주의!)

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

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

비용 계산 예시:

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

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

# 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'

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

API Gateway 캐싱

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

# 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

효과:

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

중복 호출 방지 (멱등성)

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

// 멱등성 키를 사용한 중복 방지
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);
}

최종 비용 절감 결과

항목 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