목요일

AWS Lambda Cold Start 완벽 해결 가이드 — 지연 원인과 최적화 전략

🔍 검색 키워드: AWS Lambda cold start 해결, Lambda 콜드 스타트 최적화, Lambda 응답 지연 원인, Provisioned Concurrency 설정, Lambda 함수 초기화 시간 단축

AWS Lambda Cold Start 완벽 해결 가이드 — 지연 원인과 최적화 전략


증상: 첫 요청만 유독 느리다

Lambda 기반 API에서 이런 현상이 보인다.

일반 응답: 50~100ms
첫 번째 요청 / 트래픽 없다가 재요청: 1500~3000ms

CloudWatch 로그를 보면:

REPORT RequestId: abc-123  Duration: 1823.45 ms  Billed Duration: 1824 ms
       Memory Size: 512 MB  Max Memory Used: 128 MB
       Init Duration: 1654.23 ms

Init Duration이 실제 함수 실행 시간보다 훨씬 길다. 이것이 콜드 스타트(Cold Start)다.


원인 분석: Lambda 실행 생명주기

Lambda는 요청이 없을 때 컨테이너를 종료한다. 새 요청이 들어오면 다음 단계를 거친다.

  1. 컨테이너 생성 — Lambda 실행 환경(마이크로VM) 할당
  2. 런타임 초기화 — Node.js / Python / JVM 등 런타임 부팅
  3. 코드 초기화 — 핸들러 외부 코드 실행 (import, DB 연결, 설정 로드)
  4. 핸들러 실행 — 실제 요청 처리

1~3 단계가 "콜드 스타트"다. 이후 일정 시간 안에 다시 요청이 오면 4단계만 실행하는 "웜 스타트(Warm Start)"가 된다.

런타임별 콜드 스타트 시간

런타임 평균 콜드 스타트
Python 3.x~200ms
Node.js 18.x~250ms
Go~50ms
Java 17 (JVM)~1000~3000ms
Java 17 (GraalVM Native)~150ms
.NET 6~500ms

JVM 기반이 압도적으로 느리다. Spring Boot on Lambda는 이 문제의 대표적인 사례다.


해결방법

해결 1 — 핸들러 외부에서 초기화 (재사용 최대화)

# ❌ 잘못된 패턴: 매 요청마다 DB 연결
def handler(event, context):
    db = pymysql.connect(host=os.environ['DB_HOST'], ...)  # 매 요청마다 연결
    result = db.execute("SELECT ...")
    db.close()
    return result

# ✅ 올바른 패키-��팭: 요청마 항됐 ₔ DB 연결
import pymysql
import os

# 콜드 스타트 웄 해 벰헐 실행l 이후 재사용볠
db = pymysql.connect(
    host=os.environ['DB_HOST'],
    user=os.environ['DB_USER'],
    password=os.environ['DB_PASSWORD'],
    database=os.environ['DB_NAME'],
    cursorclass=pymysql.cursors.DictCursor
)

def handler(event, context):
    with db.cursor() as cursor:
        cursor.execute("SELECT ...")
        return cursor.fetchall()
// Node.js — SDK 클흼스이 언트도 외부초기화
const { DynamoDBClient } = require('@aws-sdk/client-dynamodb');

// 콜드 스타트 시 읐 번 생성, 이후 재사용트 시 한 번 생성, 이후 재사용
const dynamo = new DynamoDBClient({ region: 'ap-northeast-2' });

exports.handler = async (event) => {
    const result = await dynamo.send(new GetItemCommand({ ... }));
    return result;
};

해결 2 — 배포 패키지 최소화

# Node.js — esbuild로 번들링 (tree-shaking)
esbuild src/handler.ts --bundle --platform=node --target=node18 \
  --outfile=dist/handler.js --minify

# 패키지 크기 확인 (목표: 압축 기준 5MB 이하)
du -sh ./dist

해결 3 — 메모리 최적화 (CPU와 비례)

Lambda는 메모리 설정에 비례해 vCPU를 할당한다. 메모리를 늘리면 초기화도 빨라진다.

# AWS CLI로 메모리 설정 변경
aws lambda update-function-configuration \
  --function-name my-function \
  --memory-size 1024

# 콜드 스타트 비교 (동일 코드)
# 128 MB → Init: 1200ms
# 512 MB → Init: 400ms
# 1024 MB → Init: 180ms

해결 4 — Provisioned Concurrency 설정

콜드 스타트를 완전히 없애는 방법. 미리 초기화된 컨테이너를 대기시킨다.

# Provisioned Concurrency 설정
aws lambda put-provisioned-concurrency-config \
  --function-name my-function \
  --qualifier prod \
  --provisioned-concurrent-executions 5

# Auto Scaling으로 트래픽에 따라 자동 조절
aws application-autoscaling register-scalable-target \
  --service-namespace lambda \
  --resource-id function:my-function:prod \
  --scalable-dimension lambda:function:ProvisionedConcurrency \
  --min-capacity 2 \
  --max-capacity 20

해결 5 — Warm-up 스케줄러 (저비용 대안)

# 핸들러에서 warmup 요청 처리
def handler(event, context):
    if event.get('source') == 'serverless-plugin-warmup':
        print('WarmUp - Lambda is warm!')
        return {'statusCode': 200, 'body': 'warm'}

    # 실제 처리
    return process(event)

정리표

방법 효과 비용 적합한 상황
핸들러 외부 초기화 재사용 극대화 무료 항상 적용
패키지 최소화 초기화 시간 단축 무료 항상 적용
메모리 증가 초기화 속도 향상 미미 128~512MB 구간
Provisioned Concurrency 콜드 스타트 제거 높음 상시 트래픽 API
Warm-up 스케줄러 웜 상태 유지 낮음 간헐적 트래픽
VPC 제거/RDS Proxy ENI 지연 제거 RDS Proxy 비용 DB 접근 Lambda

모니터링

# CloudWatch에서 콜드 스타트 추적
# INIT_DURATION이 있는 로그  = 콜드 스타트 콜드 스타트 발생
aws logs filter-log-events \
  --log-group-name /aws/lambda/my-function \
  --filter-pattern "INIT_DURATION" \
  --start-time $(date -d '1 hour ago' +%s000)

콜드 스타트 최적화는 "없애기"보다 "허용 가능한 수준으로 관리하기"가 현실적 목표다. 핸들러 외부 초기화와 패키지 최소화는 무조건 적용하고, SLA 요구사항에 따라 Provisioned Concurrency 여부를 결정하면 �

댓글 없음:

댓글 쓰기