목차
31 장

Lambda 깊이 — 콜드 스타트 · SnapStart · 패키징 · 관측성

17장 Lambda 기초 위에 production 운영 관점을 더합니다. 콜드 스타트와 SnapStart · Provisioned Concurrency, Layers와 컨테이너 이미지 패키징(FastAPI 한 사이클), Lambda Powertools 기반 관측성, Step Functions 결합, 그리고 Lambda vs Fargate 비용 트레이드오프를 정리합니다.

17장 Lambda 기초에서 함수를 만들고 트리거로 호출하는 기본을 잡았고, 18장 API Gateway + Lambda에서 HTTP 앞단을 붙였습니다. 이 챕터는 그 위에서 Lambda를 production으로 운영할 때 부딪히는 깊은 주제를 다룹니다 — 콜드 스타트, 패키징 전략, 관측성, 그리고 언제 Lambda가 ECS Fargate보다 나은가입니다.

5부의 마지막 서비스 챕터입니다. 여기서 다루는 콜드 스타트와 비용 트레이드오프는 본 책의 컨테이너 우선 노선(15장 ECS와 Fargate)과 서버리스 노선의 경계를 분명히 해 줍니다. 6부 캡스톤의 풀스택 배포에서 어느 부분을 Fargate로, 어느 부분을 Lambda로 둘지의 판단 근거가 됩니다.

콜드 스타트 — Lambda의 첫 호출 비용 #

Lambda는 호출이 없으면 인스턴스를 내립니다. 새 호출이 오면 실행 환경을 새로 띄우는데, 이 과정이 콜드 스타트입니다.

콜드 스타트의 구간은 다음과 같습니다.

  1. 실행 환경(마이크로 VM) 생성
  2. 런타임 초기화
  3. 함수 코드 로드 + 초기화 코드(핸들러 바깥) 실행

이후 같은 인스턴스로 오는 호출은 웜 스타트로 빠릅니다. 콜드 스타트를 줄이는 방법은 다음과 같습니다.

  • 초기화 코드 줄이기 — 핸들러 바깥에서 무거운 라이브러리를 import 하거나 큰 연결을 맺으면 매 콜드 스타트에 그 비용이 듭니다. 꼭 필요한 것만 둡니다. (단, 한 번 맺은 DB 커넥션을 핸들러 바깥에 두어 웜 스타트에 재사용하는 것은 권장됩니다.)
  • 패키지 크기 줄이기 — 배포 패키지가 작을수록 로드가 빠릅니다.
  • 언어 선택 — Python / Node는 초기화가 가볍고, JVM 계열은 콜드 스타트가 깁니다(이때 아래 SnapStart 검토).
  • 메모리 늘리기 — Lambda는 메모리에 비례해 CPU가 붙습니다. 메모리를 올리면 초기화도 빨라질 수 있어, 때로는 메모리를 올리는 게 더 싸고 빠릅니다. 적정값은 추측하지 말고 Lambda Power Tuning으로 측정해 고릅니다.

SnapStart — 초기화 스냅샷 복원 #

SnapStart는 함수의 초기화를 미리 한 번 수행해 메모리·디스크 스냅샷을 떠 두고, 호출 시 그 스냅샷을 복원해 초기화 구간을 건너뛰는 기능입니다. 추가 비용 없이 콜드 스타트를 크게 줄입니다.

  • 지원 런타임: Java 11 이상, Python 3.12 이상, .NET 8 이상. (Node.js · Ruby 등은 미지원.)
  • 컨테이너 이미지에는 적용되지 않습니다. zip 패키징 + 지원 런타임 조합에서만 동작합니다. 즉 아래 §“컨테이너 이미지” 패키징을 고르면 SnapStart는 쓸 수 없으니, 콜드 스타트가 중요하면 패키징 선택부터 영향을 받습니다.
  • 초기화가 스냅샷 시점에 한 번만 실행되므로, 난수 시드·시각·일회성 토큰을 초기화 단계에서 굳히면 안 됩니다. 그런 값은 런타임 훅(register_before_snapshot / register_after_restore)으로 복원 시점에 다시 만듭니다.
Python SnapStart 런타임 훅 — 복원 시 커넥션 재생성
from snapshot_restore_py import register_after_restore

@register_after_restore
def reinit_db():
    global conn
    conn = create_db_connection()   # 스냅샷이 아니라 복원 직후 새로 연결

SnapStart와 Provisioned Concurrency는 둘 다 콜드 스타트를 줄이지만, SnapStart는 추가 과금이 없고(복원은 무료) PC는 띄워 둔 만큼 과금됩니다. 그래서 지원 런타임이라면 SnapStart를 먼저 검토하고, 그것으로도 부족한 초저지연 경로에 PC를 얹습니다.

Provisioned Concurrency #

콜드 스타트를 아예 없애야 하는 지연 민감 경로(결제, 사용자 대면 API)에는 **Provisioned Concurrency(PC)**를 씁니다. 지정한 수만큼의 실행 환경을 미리 초기화해 웜 상태로 유지합니다.

별칭 + Provisioned Concurrency + 트래픽 따라 자동 조절
resource "aws_lambda_alias" "live" {
  name             = "live"
  function_name    = aws_lambda_function.api.function_name
  function_version = aws_lambda_function.api.version
}

resource "aws_lambda_provisioned_concurrency_config" "live" {
  function_name                     = aws_lambda_function.api.function_name
  qualifier                         = aws_lambda_alias.live.name
  provisioned_concurrent_executions = 5
}
  • 장점: 콜드 스타트가 사라집니다.
  • 단점: 미리 띄워 둔 만큼 호출이 없어도 과금 됩니다. 상시 트래픽이 일정 수준 이상이면, 이때가 바로 Lambda보다 ECS Fargate가 더 싼 구간입니다. PC 양을 Application Auto Scaling으로 시간대별 스케줄링하면 야간 비용을 줄일 수 있습니다.

판단 기준: 트래픽이 간헐적이면 Lambda(주문형 과금)가, 상시 일정량 이상으로 흐르면 Fargate(상시 과금이지만 단가가 낮음)가 유리합니다. PC를 많이 켜야 한다면 Fargate 전환을 검토할 신호입니다.

패키징 — Layers와 컨테이너 이미지 #

Lambda 코드를 묶는 방법은 세 가지입니다.

방법한도SnapStart적합
인라인 / zip 업로드압축 50MB, 압축 해제 250MB가능작은 함수
Layers공통 의존성을 분리한 zip가능여러 함수가 같은 라이브러리 공유
컨테이너 이미지최대 10GB불가큰 의존성, 기존 Docker 워크플로우 재사용

Layers #

여러 함수가 같은 라이브러리(예: 공통 유틸, SDK 확장, Powertools)를 쓴다면 Layer로 분리합니다. 함수 패키지는 가벼워지고, 라이브러리는 한 번만 관리합니다. 단 Layer도 압축 해제 250MB 한도에 함께 들어갑니다.

컨테이너 이미지 — FastAPI 한 사이클 #

의존성이 크거나(머신러닝 라이브러리 등) 이미 Docker로 빌드하는 팀이라면 컨테이너 이미지가 깔끔합니다. 15장 ECS · 16장 ECR에서 쓴 컨테이너 워크플로우를 그대로 Lambda에 재사용할 수 있습니다. 다만 위에서 본 대로 컨테이너 이미지는 SnapStart를 못 쓰므로, 콜드 스타트가 민감한 함수라면 zip + 지원 런타임을 고려합니다. FastAPI 앱을 Lambda 컨테이너로 올리는 한 사이클은 다음과 같습니다.

Dockerfile — FastAPI on Lambda
FROM public.ecr.aws/lambda/python:3.13

COPY requirements.txt .
RUN pip install -r requirements.txt

COPY app/ ${LAMBDA_TASK_ROOT}/app/
# Mangum 으로 ASGI(FastAPI)를 Lambda 핸들러로 어댑팅
CMD ["app.main.handler"]
app/main.py — Mangum 어댑터
from fastapi import FastAPI
from mangum import Mangum

app = FastAPI()

@app.get("/health")
def health():
    return {"status": "ok"}

handler = Mangum(app)  # API Gateway 이벤트를 ASGI 로 변환
ECR로 빌드·푸시 후 Lambda 생성
docker build -t myapp-lambda .
docker tag myapp-lambda:latest \
  123456789012.dkr.ecr.ap-northeast-2.amazonaws.com/myapp-lambda:latest
docker push 123456789012.dkr.ecr.ap-northeast-2.amazonaws.com/myapp-lambda:latest

aws lambda create-function \
  --function-name myapp \
  --package-type Image \
  --code ImageUri=123456789012.dkr.ecr.ap-northeast-2.amazonaws.com/myapp-lambda:latest \
  --role arn:aws:iam::123456789012:role/myapp-lambda-role

18장 API Gateway + Lambda의 HTTP API를 앞에 붙이면 FastAPI 앱이 서버리스로 돕니다. 같은 FastAPI 앱을 22장 인프라 골격에서는 ECS Fargate로 올렸으니, 둘을 비교하면 “서버리스 vs 상시 컨테이너"의 트레이드오프가 분명해집니다.

관측성 — Lambda Powertools #

Lambda는 짧게 살고 분산되어 있어 디버깅이 어렵습니다. Lambda Powertools는 세 가지를 표준화합니다.

  • 구조화 로깅 — JSON 로그에 요청 ID · 콜드 스타트 여부 등을 자동으로 붙여 7장 CloudWatch Logs Insights에서 쿼리하기 좋게 합니다.
  • 메트릭 — 비즈니스 메트릭을 EMF(Embedded Metric Format)로 남겨 CloudWatch 메트릭으로 자동 집계합니다.
  • 트레이싱 — X-Ray와 연동해 API Gateway → Lambda → DynamoDB의 한 요청 흐름을 추적합니다(26장 모니터링 — CloudWatch 알람과 X-Ray).
Powertools — 로깅 · 메트릭 · 트레이싱 한 번에
from aws_lambda_powertools import Logger, Tracer, Metrics
from aws_lambda_powertools.metrics import MetricUnit

logger = Logger()
tracer = Tracer()
metrics = Metrics()

@logger.inject_lambda_context     # 요청 ID·콜드스타트 자동 부착
@tracer.capture_lambda_handler    # X-Ray 세그먼트
@metrics.log_metrics              # EMF 메트릭 flush
def handler(event, context):
    metrics.add_metric(name="OrderCreated", unit=MetricUnit.Count, value=1)
    logger.info("order created", extra={"order_id": event["id"]})
    return {"statusCode": 201}

Step Functions와의 결합 #

21장 Step Functions에서 봤듯, 여러 단계를 한 Lambda 안에 try/except로 욱여넣지 않고 상태 머신으로 나눕니다. Lambda 깊이 관점의 결합 패턴은 다음과 같습니다.

  • 각 단계를 작은 Lambda로 두고 Step Functions가 순서 · 재시도 · 분기를 맡습니다.
  • 콜드 스타트가 문제인 단계만 SnapStart 또는 Provisioned Concurrency를 켭니다.
  • 긴 대기(외부 승인 등)는 Lambda를 띄워 두지 않고 Step Functions의 대기 상태(Wait / 콜백 토큰)로 처리해 비용을 아낍니다. Lambda 최대 실행 시간은 15분이라, 그보다 긴 흐름은 반드시 Step Functions로 쪼갭니다.

Lambda vs Fargate — 한 줄 결정 #

신호선택
간헐적 / 이벤트 기반 트래픽Lambda (주문형 과금)
상시 일정량 이상 트래픽Fargate (단가 낮음)
15분 넘는 작업Fargate (Lambda 최대 15분)
콜드 스타트 민감 + 상시 트래픽Fargate (또는 SnapStart / PC 비용 검토)
빠른 스파이크 대응Lambda (즉시 확장)
큰 의존성(10GB 이미지)Lambda 컨테이너 또는 Fargate (단 Lambda 컨테이너는 SnapStart 불가)

연습문제 #

  1. 본인 워크로드 하나를 골라 Lambda와 ECS Fargate 중 무엇이 맞는지 §“Lambda vs Fargate” 표의 신호로 판단하고, 근거를 한 단락으로 적어 보세요. Provisioned Concurrency를 많이 켜야 하는 상황이라면 그 결론이 어떻게 바뀌는지도 적습니다.
  2. 콜드 스타트가 민감한 Python 함수가 있습니다. SnapStart와 컨테이너 이미지 패키징을 동시에 쓸 수 있는지 §“SnapStart"와 §“패키징” 표를 근거로 답하고, 둘 중 무엇을 포기해야 하는지와 그 이유를 적어 보세요.
  3. SnapStart를 켠 함수가 “초기화 때 맺은 DB 커넥션이 복원 후 끊겨 있다"는 문제를 겪습니다. §“SnapStart"의 런타임 훅으로 어떻게 해결하는지 코드 흐름으로 적어 보세요.

한 줄 요약: Lambda 콜드 스타트는 실행 환경 생성·런타임·초기화 코드의 비용이며, 초기화 축소·패키지 축소·메모리 증설로 줄인다. 지원 런타임(Java 11+/Python 3.12+/.NET 8+, 단 컨테이너 이미지 불가)이면 추가 과금 없는 SnapStart를 먼저 쓰고, 그래도 부족한 초저지연 경로에 과금되는 Provisioned Concurrency를 얹는다. PC를 많이 켜야 하면 Fargate가 더 싼 신호다. 패키징은 zip / Layers / 컨테이너 이미지(최대 10GB, SnapStart 불가, FastAPI+Mangum으로 한 사이클)이고, 관측성은 Lambda Powertools로 구조화 로깅·EMF 메트릭·X-Ray 트레이싱을 표준화한다. 15분 넘는 흐름은 Step Functions로 쪼개고, 간헐 트래픽은 Lambda·상시 트래픽은 Fargate가 일반 기준이다.

다음 챕터 #

5부를 마칩니다. 다음 32장 풀스택 앱 AWS 배포하기에서는 1~31장의 모든 서비스를 하나로 엮습니다. modern-react의 Next.js 앱과 modern-python의 FastAPI 앱을 ECS Fargate + RDS + S3 + CloudFront + Terraform으로 한 계정에 배포하고, 이 챕터에서 정리한 “어디를 Fargate로, 어디를 Lambda로"의 판단을 실제 시스템에 적용합니다.

X