AWS 고급 #3 Lambda 기초
#1 ECS / Fargate, #2 ECR은 컨테이너가 항상 떠 있는 모델이었습니다. 트래픽이 0일 때도 컨테이너 1개는 살아 있습니다. 트래픽 변동이 크거나, 짧은 처리만 필요하거나, 운영 부담을 더 줄이고 싶을 때는 다른 선택지가 어울립니다. 바로 Lambda입니다.
이번 글은 Lambda의 동작 방식, 모델 (runtime / handler / event), 호출 방식, 콜드 스타트, 동시성과 한도, 로깅까지 한 번에 정리하겠습니다.
Lambda가 하는 일 #
AWS Lambda는 서버리스 함수 실행 플랫폼입니다. 이벤트가 들어오면 그제서야 함수가 깨어나고, 끝나면 다시 사라집니다. 트래픽이 0이면 비용도 0입니다.
이벤트 (HTTP / S3 업로드 / SQS 메시지 / Cron)
│
▼
Lambda가 컨테이너 hot 또는 cold로 띄움
│
▼
내 핸들러 함수 실행 (수 ms ~ 15분)
│
▼
응답 / 결과 → 호출자
│
▼
일정 시간 idle 후 컨테이너 종료언제 Lambda가 어울리는가 #
Lambda가 어울리는 경우 #
- 이벤트 주도. S3 업로드 → 썸네일 생성, SQS 메시지 → 처리, EventBridge 스케줄 → 배치
- 변동 큰 트래픽. 평소 0, 가끔 폭증. 0일 때 비용 안 듦
- 짧은 처리. 보통 수 초 ~ 수 분
- 사이드 / 보조 워크로드. 메인 시스템 옆의 보조 함수
- API의 일부만. 모든 API가 Lambda 일 필요 없음
Lambda가 맞지 않는 경우 #
| 경우 | 이유 |
|---|---|
| 항상 트래픽이 도는 큰 API | 동시성 / 콜드 스타트 / 비용에서 ECS가 유리 |
| 15분 넘는 처리 | Lambda 한 호출은 최대 15분 |
| 매우 큰 메모리 / GPU | Lambda는 최대 10GB 메모리, GPU 없음 |
| Stateful 연결 (WebSocket의 백엔드 등) | 가능하지만 설계 복잡 |
| 항상 켜져있는 DB connection pool | 호출마다 새로 연결되는 방식 |
비교 #
| EC2 | ECS / Fargate | Lambda | |
|---|---|---|---|
| 켜져 있는 시간 | 항상 | 항상 (Service) | 호출마다 |
| 운영 부담 | 큼 | 중간 (Fargate 작음) | 작음 |
| 콜드 스타트 | 없음 | 작음 | 있음 (수십 ms ~ 수 초) |
| 시간 한도 | 무한 | 무한 | 15분 |
| 트래픽 0 비용 | 큼 | 중간 | 0 |
| 동시 처리 | OS 레벨 | 컨테이너 1개에 다중 | 함수 인스턴스 1개당 동시 1개 |
마지막 줄이 중요: Lambda는 함수 인스턴스 1개가 동시에 1개의 호출만 처리. 동시 호출이 N 개면 인스턴스도 N 개로 자동 확장.
첫 Lambda: Hello, World #
함수 만들기 (콘솔) #
콘솔 → Lambda → “함수 생성” → 직접 작성 → Python 3.13.
def lambda_handler(event, context):
return {
"statusCode": 200,
"body": "Hello, Lambda"
}저장하고 “테스트”, 빈 이벤트로 호출. CloudWatch Logs에 자동으로 로그 그룹 (/aws/lambda/<함수이름>) 생성.
CLI로 만들기 #
코드를 zip으로 묶어:
zip function.zip lambda_function.py
aws lambda create-function \
--function-name hello \
--runtime python3.13 \
--role arn:aws:iam::123456789012:role/lambda-basic-role \
--handler lambda_function.lambda_handler \
--zip-file fileb://function.zipIaC (Terraform 살짝) #
resource "aws_lambda_function" "hello" {
function_name = "hello"
runtime = "python3.13"
handler = "lambda_function.lambda_handler"
role = aws_iam_role.lambda.arn
filename = "function.zip"
source_code_hash = filebase64sha256("function.zip")
memory_size = 256
timeout = 10
}모델: Runtime / Handler / Event / Context #
Lambda의 모델을 네 키워드로 정리.
Runtime #
언어 + 버전. 매니지드 runtime:
- Python (3.10 ~ 3.13)
- Node.js (18, 20, 22)
- Java (8, 11, 17, 21)
- .NET, Ruby, Go (
provided.al2023위) - Custom Runtime. Rust / Zig / Swift 등 직접 지원
또는 **컨테이너 이미지 (ECR)**로 배포하는 방법도 있습니다. 위의 #2 ECR과 자연스럽게 연결되며, 큰 의존성이 있을 때 유리합니다 (zip 한도 250MB, 컨테이너는 10GB).
Handler #
Lambda가 호출할 함수의 이름으로, <파일이름>.<함수이름> 형식으로 지정합니다.
def main(event, context):
...→ Handler 설정: myapp.handler.main
Event #
호출자가 보낸 데이터. JSON 객체. 호출 소스마다 모양이 다름.
{
"version": "2.0",
"routeKey": "GET /hello",
"headers": {...},
"queryStringParameters": {...},
"body": "..."
}{
"Records": [
{
"eventSource": "aws:s3",
"s3": {
"bucket": {"name": "my-bucket"},
"object": {"key": "uploads/photo.jpg"}
}
}
]
}Lambda Powertools (Python / TypeScript / Java) 같은 라이브러리가 이 event 들을 타입 안전하게 다루는 데 도움이 됩니다. 실전에서는 적극 활용하세요.
Context #
런타임 정보. 함수 실행 시간 한도 (get_remaining_time_in_millis()), request id, 함수 이름 등.
def handler(event, context):
print(context.aws_request_id)
print(context.function_name)
print(context.get_remaining_time_in_millis()) # 남은 시간 ms호출 방식: 동기 vs 비동기 vs 스트림 #
호출 소스에 따라 Lambda의 동작이 다릅니다.
1) 동기 (Synchronous) #
호출자가 결과를 기다림. 응답을 받을 때까지 블록.
| 호출 소스 |
|---|
| API Gateway |
| ALB |
| Cognito |
직접 Invoke API |
aws lambda invoke \
--function-name hello \
--payload '{"name": "world"}' \
--cli-binary-format raw-in-base64-out \
out.json2) 비동기 (Asynchronous) #
호출자가 “큐에 넣고” 끝. Lambda가 백그라운드로 처리. 실패 시 자동 재시도 (기본 2회) + DLQ (Dead Letter Queue)로 이송.
| 호출 소스 |
|---|
| S3 ObjectCreated |
| SNS |
| EventBridge |
InvocationType=Event의 Invoke |
aws lambda invoke \
--function-name hello \
--invocation-type Event \
--payload '{"key": "value"}' \
--cli-binary-format raw-in-base64-out \
out.json3) 스트림 / 폴링 #
Lambda가 큐 / 스트림을 자동 폴링.
| 호출 소스 |
|---|
| SQS |
| DynamoDB Streams |
| Kinesis |
| MSK (Managed Kafka) |
설정만 해두면 새 메시지가 오는 대로 Lambda가 batch로 받아 처리. 실패하면 retry + DLQ.
동시성 (Concurrency)의 의미 #
Lambda의 가장 중요한 한 가지입니다. 함수 인스턴스 1개가 동시 1 호출만 처리하니, 동시에 들어온 호출 수 = 띄워진 인스턴스 수.
초당 10개 호출, 각 호출 1초 → 동시 ~10개 인스턴스
초당 100개 호출, 각 호출 100ms → 동시 ~10개 인스턴스계정 한도 (Account Concurrency) #
리전별 기본값은 1,000입니다. 운영 워크로드에서는 종종 부족하므로, Service Quotas 콘솔에서 증액을 요청하세요.
Reserved Concurrency #
특정 함수에 “최대 N 개” 를 보장 + 제한.
aws lambda put-function-concurrency \
--function-name hello \
--reserved-concurrent-executions 100용도:
- 위험한 함수의 폭주 차단 (예: 유료 외부 API 호출하는 함수)
- 다른 중요 함수에 동시성 여유 남기기 (이 함수가 1,000 다 차지 못 하게)
- DB connection 폭주 방지 (RDS connection pool 보호)
Provisioned Concurrency: 콜드 스타트 회피 #
미리 N 개 인스턴스를 데워둠. 동시 호출이 N이하면 콜드 스타트 0.
aws lambda put-provisioned-concurrency-config \
--function-name hello \
--qualifier prod \
--provisioned-concurrent-executions 10비용: 데워둔 인스턴스 시간만큼 과금 (약간 저렴한 단가). API의 입구라 콜드 스타트가 사용자 경험에 직접 영향이라면 검토.
콜드 스타트: 가장 자주 만나는 함정 #
함수 인스턴스가 새로 만들어질 때 초기 비용이 발생합니다. 두 단계로 구분됩니다.
[INIT 단계] : 한 번만
├─ 컨테이너 환경 준비
├─ runtime 시작
├─ 핸들러 모듈 import
└─ 핸들러 함수 외부의 코드 실행 (전역)
[INVOKE 단계] : 매 호출마다
└─ 핸들러 함수 실행INIT은 인스턴스의 첫 호출에만 비용. 같은 인스턴스가 다음 호출을 받으면 INIT 건너뜀 (warm).
콜드 스타트 시간 #
대략적인 구간:
| 언어 / 형태 | INIT 시간 |
|---|---|
| Python (작은 의존성) | ~150 ms |
| Python (큰 의존성, e.g. boto3 + pandas) | ~1 ~ 2초 |
| Node.js (작은) | ~100 ms |
| Java | ~500 ms ~ 수 초 |
| 컨테이너 이미지 (큰) | ~수 초 |
콜드 스타트 줄이기 #
- 메모리 늘리기. Lambda는 메모리에 비례해 vCPU도 늘어남. 256MB → 1024MB만 해도 INIT이 빨라짐
- 의존성 슬리밍. 안 쓰는 패키지 빼기, tree-shaking
- 전역 변수 활용. 핸들러 외부에서 한 번 만든 객체 (boto3 client, DB connection 등)를 warm 한 인스턴스가 재사용
- Provisioned Concurrency. 위에서 본 대로
- Lambda SnapStart (Java). INIT 결과를 스냅샷 떠서 빠른 복구. Java 한정
전역 변수 패턴 #
import boto3
# Lambda 인스턴스 한 번만 : INIT 단계
s3 = boto3.client("s3")
def handler(event, context):
# 핸들러는 깨끗하게 : 매 호출마다 client 안 만듦
return s3.list_buckets()def handler(event, context):
# 매 호출마다 boto3 client : 느림
s3 = boto3.client("s3")
return s3.list_buckets()메모리와 시간 한도 #
| 항목 | 한도 |
|---|---|
| 메모리 | 128MB ~ 10,240MB (1MB 단위) |
| 시간 | 1초 ~ 900초 (15분) |
임시 디스크 (/tmp) | 512MB ~ 10GB |
| 환경 변수 | 4KB |
| Payload (동기) | 6MB |
| Payload (비동기) | 256KB |
| zip (소스) | 50MB (압축) |
| zip (압축 해제) | 250MB |
| 컨테이너 이미지 | 10GB |
메모리는 vCPU와 묶여 있습니다. 1,769MB에서 vCPU 1개 분량입니다. 메모리를 늘리면 CPU도 같이 늘어 함수가 빨라지는 경우가 흔합니다. 메모리만 늘려도 비용이 줄 수 있습니다 (Lambda Power Tuning으로 최적값 찾기).
로깅: CloudWatch Logs #
Lambda의 모든 stdout / stderr는 자동으로 CloudWatch Logs의 /aws/lambda/<함수이름> 로그 그룹에 들어갑니다.
import logging
logger = logging.getLogger()
logger.setLevel(logging.INFO)
def handler(event, context):
logger.info("Hello %s", event.get("name"))
return "ok"운영에선 구조화 로그 권장. JSON으로 출력하면 CloudWatch Logs Insights에서 필드별 쿼리 가능.
import json, logging
logger = logging.getLogger()
def handler(event, context):
logger.info(json.dumps({
"request_id": context.aws_request_id,
"user_id": event.get("user_id"),
"action": "process",
"duration_ms": 42
}))Powertools의 Logger가 이걸 한 줄로 깔끔하게 해줍니다.
Layers: 코드 재사용 #
여러 함수가 같은 의존성 / 유틸리티를 쓸 때 Lambda Layer로 분리해 공유합니다.
# python의존성
mkdir -p python
pip install requests -t python/
zip -r layer.zip python
aws lambda publish-layer-version \
--layer-name my-utils \
--zip-file fileb://layer.zip \
--compatible-runtimes python3.13aws lambda update-function-configuration \
--function-name hello \
--layers arn:aws:lambda:ap-northeast-2:123456789012:layer:my-utils:1장점: 함수 zip 작아짐, 의존성 한 번만 업데이트. 단점: 너무 많아지면 추적 어려움. 5개 한도.
Lambda 비용 #
호출당 비용 = (호출 수 × $0.0000002)
+ (실행시간 GB-초 × $0.0000166667)월 100만 호출 + 호출당 100ms + 256MB:
- 호출 비용: 100만 × $0.0000002 = $0.20
- 시간 비용: 100만 × 0.1초 × 0.25GB × $0.0000166667 = $0.42
- 총: ~$0.62 / 월
매우 저렴합니다. 무료 티어로 월 100만 호출 + 400,000GB-초 무료. 작은 워크로드는 사실상 0.
비용이 커지는 경우:
- 호출당 시간이 길고, 메모리가 큰 함수 (e.g. 5분 × 3GB)
- 동시성이 항상 높은 함수. ECS / Fargate가 더 쌀 수 있음
자주 만나는 함정 #
1) 콜드 스타트로 첫 호출 느림 #
API 입구의 Lambda가 0.5 ~ 2초씩 걸리면 사용자가 버티기 어렵습니다. 위의 “콜드 스타트 줄이기” 5가지와 Provisioned Concurrency를 검토하세요.
2) 핸들러 안에서 매번 객체 생성 #
def handler(event, context):
db = create_db_connection()
boto = boto3.client("s3")
...매 호출마다 100ms의 낭비. 전역으로 끌어내기.
3) RDS connection pool 폭주 #
Lambda 동시 100개 → DB 연결 100개 → DB가 connection 한도 초과. 대안:
- RDS Proxy. Lambda 들이 공유하는 connection pool
- Reserved Concurrency로 함수 동시성 제한
- DynamoDB 같은 서버리스 DB 사용
4) 비동기 호출의 실수가 조용히 사라짐 #
S3 → Lambda가 실패해도 호출자는 모름. DLQ (SQS) 또는 Lambda Destination으로 실패 캡처.
aws lambda put-function-event-invoke-config \
--function-name hello \
--maximum-retry-attempts 2 \
--destination-config '{"OnFailure":{"Destination":"arn:aws:sqs:...:dlq"}}'5) 시간 초과로 잘려 나감 #
15분 한도 모르고 큰 처리를 한 함수에. 14분 59초에 강제 종료. 긴 처리는 #7 Step Functions로 분할 또는 ECS / Fargate.
6) Payload가 6MB 넘음 #
API Gateway → Lambda 동기 호출의 한도. 큰 파일은 S3 presigned URL 패턴으로 우회. Lambda가 presigned URL만 발급, 클라이언트가 직접 S3에 업로드.
7) 환경 변수에 비밀 #
평문 환경변수에 DB 비밀번호 → 로그 / 콘솔에서 노출. **#6 Secrets Manager**로 옮기기.
정리 #
이번 글에서 잡은 것:
- Lambda의 쓰임새. 이벤트 주도, 변동 큰 트래픽, 짧은 처리, 사이드 워크로드. 트래픽 0이면 비용 0
- Lambda가 안 맞는 경우. 항상 큰 트래픽, 15분 초과, 큰 메모리 / GPU, stateful
- 모델. Runtime / Handler / Event / Context. Event 모양은 호출 소스마다
- 호출 방식. 동기 / 비동기 / 스트림. 비동기는 자동 retry + DLQ
- 동시성. 함수 인스턴스 1개 = 동시 1 호출. 호출이 늘면 인스턴스 자동 확장
- Reserved Concurrency. 폭주 차단 + 다른 함수 보호
- Provisioned Concurrency. 콜드 스타트 회피
- 콜드 스타트. INIT의 비용. 메모리 늘리기 / 의존성 슬리밍 / 전역 변수 / SnapStart / Provisioned으로 완화
- 한도. 메모리 10GB, 시간 15분, payload 6MB, 컨테이너 10GB
- 로깅. CloudWatch Logs 자동. JSON 구조화 로그 권장
- Layers. 의존성 공유 (5개 한도)
- 비용. 호출 + 실행 시간. 작은 워크로드는 거의 0
- 함정. 콜드 스타트, 핸들러 안 객체 생성, RDS 폭주 (RDS Proxy), 비동기 실패 누락 (DLQ), 15분 한도, Payload 6MB, 환경변수 비밀
다음: API Gateway + Lambda #
함수만 있으면 호출할 경로가 부족합니다. HTTP 요청으로 Lambda를 부르는 가장 흔한 패턴, API Gateway + Lambda를 다음 글에서.
#4 API Gateway + Lambda에서는 REST API와 HTTP API의 차이, Lambda 통합, 라우트 / 메소드, 권한 (IAM / Cognito / Lambda authorizer), 스테이지 / 배포까지, 서버리스 API의 쓰임새를 한 번에 정리하겠습니다.