기준: DVA-C02 시험 가이드오류 제보
Lambda의 동시성 모델은 처음 보면 단순해 보인다. "함수가 동시에 몇 개 실행되느냐"의 문제처럼 보인다. 하지만 실제로는 계정 전체 한도, 함수별 Reserved 동시성, Provisioned 동시성, 버스트 한도라는 네 개의 다른 메커니즘이 중첩되어 있고, 이 계층 구조를 이해하지 못하면 "내 Lambda가 왜 429를 뱉는지", "왜 다른 팀 함수 때문에 내 함수가 느려지는지"를 설명할 수 없다.
에러 처리도 마찬가지다. 동기, 비동기, ESM 세 가지 호출 방식은 에러가 발생했을 때 완전히 다르게 반응한다. 이걸 모르면 Kinesis 샤드가 통째로 막히거나, SQS 메시지가 영원히 재처리되는 사고로 이어진다.
Lambda 동시성은 특정 시점에 동시에 실행 중인 함수 인스턴스 수다. 공식은 간단하다.
동시성 = 초당 요청 수(RPS) × 평균 실행 시간(초)
예:
- RPS = 1,000, 평균 실행 시간 = 200ms(0.2초)
- 동시성 = 1,000 × 0.2 = 200
- RPS = 100, 평균 실행 시간 = 5초
- 동시성 = 100 × 5 = 500
이 공식에서 보이는 것처럼, 실행 시간이 길수록 동시성 소비가 크다. 타임아웃을 15분으로 설정한 Lambda가 100개 동시 실행되고 있다면, 그 15분 동안 1,500 동시성을 차지한다 — 계정 기본 한도(1,000)를 이미 초과한다.
계정/리전 전체 한도: 1,000 (기본, 증가 요청 가능)
│
├── [함수 A] Reserved: 300 → 최대 300개 인스턴스
│ └── Provisioned: 20 → 항상 20개 미리 초기화
│
├── [함수 B] Reserved: 200 → 최대 200개 인스턴스
│
└── [나머지 함수들] 공유 풀: 500 (1000 - 300 - 200)
└── Reserved 없는 모든 함수가 이 500을 두고 경쟁
Reserved Concurrency(예약 동시성):
Provisioned Concurrency(프로비저닝된 동시성):
$LATEST 불가)# Reserved Concurrency 설정
aws lambda put-function-concurrency \
--function-name payment-api \
--reserved-concurrent-executions 300
# 완전 차단 (모든 요청 즉시 ThrottlingException)
aws lambda put-function-concurrency \
--function-name maintenance-mode-function \
--reserved-concurrent-executions 0
# Provisioned Concurrency (별칭에)
aws lambda put-provisioned-concurrency-config \
--function-name payment-api \
--qualifier prod \
--provisioned-concurrent-executions 20⚠️ 함정: Reserved Concurrency를 설정하면 계정 전체 풀에서 그만큼이 차감된다. 함수 A에 Reserved=500을 설정하면 나머지 함수들은 500만 공유한다. 함수들이 많이 동시에 실행되는 피크타임에 "왜 갑자기 다른 함수들이 스로틀링 되지?"라는 상황이 Reserved 설정 때문에 발생하는 경우가 있다.
Lambda는 트래픽 급증 시 자동으로 동시성을 늘린다. 하지만 무한정 빠르게 늘릴 수는 없다.
스케일링 속도: 함수마다, 리전마다 10초에 실행 환경 1,000개까지 늘어난다(초당 요청 기준으로는 10초마다 10,000 RPS). 2023년 말 이 방식으로 바뀌었다.
이전 방식: 계정 단위로 첫 1분에 리전별 500~3,000개를 늘리고, 그 뒤 분당 500개씩 늘렸다. 오래된 자료와 문제집에 이 수치가 남아 있으니 구분한다.
이 의미는: 갑자기 5,000 동시성이 필요한 트래픽이 들어오면, 계정 한도가 5,000 이상일 때 약 50초(10초마다 +1,000)에 걸쳐 늘어난다. 계정 한도가 기본값 1,000이면 1,000에서 멈춘다. 속도 한도는 함수 단위라 함수마다 따로 적용된다. 쓰지 않은 속도 여유분은 다음 10초로 이월되지 않는다.
이 속도나 한도를 넘는 요청은 스로틀링 에러(429)를 받는다.
| 메트릭 | 네임스페이스 | 의미 | 높으면 조치 |
|---|---|---|---|
ConcurrentExecutions | AWS/Lambda | 현재 동시 실행 수 | Reserved 한도에 근접하면 증가 고려 |
Throttles | AWS/Lambda | 스로틀된 호출 수 | Reserved/계정 한도 증가 |
ProvisionedConcurrencyUtilization | AWS/Lambda | PC 사용률 | 80% 이상 지속 시 PC 추가 |
IteratorAge | AWS/Lambda | Kinesis/DDB Streams 처리 지연 | 계속 증가 → ParallelizationFactor 늘리기 |
DeadLetterErrors | AWS/Lambda | DLQ 전송 실패 | DLQ 권한(IAM) 확인 |
# CloudWatch에서 동시성 메트릭 조회
aws cloudwatch get-metric-statistics \
--namespace AWS/Lambda \
--metric-name ConcurrentExecutions \
--dimensions Name=FunctionName,Value=payment-api \
--start-time 2026-05-31T00:00:00Z \
--end-time 2026-05-31T23:59:59Z \
--period 300 \
--statistics Maximum💡 관련 이론:
IteratorAge는 현재 시간과 Kinesis/DynamoDB Streams에서 처리 중인 레코드의 Put 시간 차이다. 이 값이 계속 증가하면 Lambda가 스트림 속도를 따라잡지 못하는 것이다. 해결책은 Kinesis 샤드 수 증가(처리량 분산), LambdaParallelizationFactor증가(샤드당 병렬 Lambda 수), 또는 Lambda 실행 시간 최적화다.
에러가 발생했을 때 "누가 재시도하는가", "어디로 실패 이벤트가 가는가"가 호출 방식마다 완전히 다르다.
동기 호출 (API Gateway, ALB, 직접 invoke):
FunctionError 헤더로 응답비동기 호출 (S3, SNS, EventBridge, 직접 invoke Event 타입):
ESM 폴링 (SQS, Kinesis, DDB Streams):
maxReceiveCount 초과 시 SQS DLQMaximumRetryAttempts로 제한 필수호출 방식별 에러 처리 흐름
[동기 - API Gateway]
클라이언트 → API GW → Lambda → 에러 발생
↓
API GW에 FunctionError 응답
↓
클라이언트에 5XX 반환 (API GW 설정에 따라)
재시도 = 클라이언트 코드가 결정
[비동기 - S3]
S3 업로드 → Lambda 내부 큐 → Lambda 실행 → 에러 발생
↓
1분 후 자동 재시도
↓ (또 실패)
2분 후 자동 재시도
↓ (또 실패)
→ DLQ 또는 Destinations OnFailure
[ESM - SQS]
메시지 → Lambda 폴링 → Lambda 실행 → 에러 발생
↓
가시성 타임아웃 종료 후 메시지 큐 복귀
↓ (maxReceiveCount 초과)
→ SQS DLQ (함수 DLQ 아님!)
| 상황 | 권장 |
|---|---|
| 비동기 호출 실패 이벤트 보존 | Destinations OnFailure |
| 비동기 호출 성공 이벤트도 감사 필요 | Destinations OnSuccess |
| SQS-Lambda ESM에서 최종 실패 처리 | SQS 큐 자체에 DLQ 설정 |
| 기존 레거시 코드가 DLQ를 이미 사용 중 | DLQ 유지 (마이그레이션 여유 있을 때 Destinations로) |
Destinations가 DLQ보다 나은 이유:
# Destinations OnFailure가 전달하는 이벤트 구조 (예시)
{
"version": "1.0",
"timestamp": "2026-05-31T09:30:00Z",
"requestContext": {
"requestId": "abc-123",
"functionArn": "arn:aws:lambda:...:function:payment-api:prod",
"condition": "RetriesExhausted",
"approximateInvokeCount": 3 # 원본 1회 + 재시도 2회
},
"requestPayload": {
"customerId": "C001",
"amount": 9999
},
"responseContext": {
"statusCode": 200, # Lambda API 수준, 함수 에러가 있어도 200
"executedVersion": "$LATEST",
"functionError": "Unhandled"
},
"responsePayload": {
"errorMessage": "Payment gateway timeout",
"errorType": "TimeoutError"
}
}시험에서 헷갈리기 쉬운 부분이다.
SQS DLQ(Dead Letter Queue):
maxReceiveCount 횟수를 초과해 소비됐을 때 SQS DLQ로 이동maxReceiveCount 초과 → SQS DLQLambda DLQ:
시나리오: SQS → Lambda ESM에서 Lambda가 계속 실패
Lambda DLQ 설정 여부와 무관!
→ 메시지가 가시성 타임아웃 후 SQS 큐로 복귀
→ maxReceiveCount(기본 3) 초과 후
→ SQS 큐에 설정된 DLQ로 이동
Lambda DLQ는 S3/SNS 등 비동기 호출에서 동작
시나리오: 금융 API (결제 처리)
- 평소 트래픽: 50 동시성
- 피크 트래픽: 200 동시성
- 콜드 스타트 허용 불가 (응답 시간 SLA: p99 < 500ms)
설정:
Reserved Concurrency = 300 (계정 한도에서 300 전용 할당)
Provisioned Concurrency = 50 (항상 50개 웜 상태 대기)
동작:
- 평소: 50개 Provisioned 인스턴스가 처리, 콜드 스타트 없음
- 트래픽 급증 시: 50개를 초과하는 요청은 새 인스턴스 생성(콜드 스타트 발생 가능)
- 트래픽이 300 초과 시: ThrottlingException (Reserved가 상한선 역할)
- 다른 함수들이 이 300을 쓸 수 없음 (전용 할당)
Auto Scaling 설정:
PC를 80% 사용 시 자동으로 PC 추가
# Application Auto Scaling으로 Provisioned Concurrency 자동 조절
aws application-autoscaling register-scalable-target \
--service-namespace lambda \
--resource-id function:payment-api:prod \
--scalable-dimension lambda:function:ProvisionedConcurrency \
--min-capacity 10 \
--max-capacity 200
aws application-autoscaling put-scaling-policy \
--service-namespace lambda \
--resource-id function:payment-api:prod \
--scalable-dimension lambda:function:ProvisionedConcurrency \
--policy-name pc-tracking-policy \
--policy-type TargetTrackingScaling \
--target-tracking-scaling-policy-configuration '{
"TargetValue": 0.7,
"PredefinedMetricSpecification": {
"PredefinedMetricType": "LambdaProvisionedConcurrencyUtilization"
}
}'Lambda에서 에러를 모두 같은 방식으로 처리하면 안 된다.
시스템 에러(재시도 가능): DB 연결 실패, HTTP 타임아웃, 일시적 503. 예외를 던져서 Lambda 재시도 메커니즘을 활용한다.
비즈니스 에러(재시도 불필요): 유효하지 않은 데이터, 권한 없음, 도메인 규칙 위반. 재시도해도 결과가 같으므로 예외를 던지지 말고 성공 응답을 반환하거나 에러 이벤트를 다른 큐로 라우팅한다.
import logging
import json
from botocore.exceptions import ClientError
logger = logging.getLogger()
logger.setLevel(logging.INFO)
class BusinessError(Exception):
"""도메인 규칙 위반 — 재시도해도 의미 없음"""
pass
class RetryableError(Exception):
"""일시적 시스템 오류 — 재시도하면 성공할 수 있음"""
pass
def lambda_handler(event, context):
try:
order_id = event.get('orderId')
if not order_id:
# 비즈니스 에러 — 재시도해도 소용 없음
# 예외 대신 에러 응답을 반환하거나 SQS에 직접 보내기
logger.error(f"유효하지 않은 주문 ID: {event}")
return {'status': 'BUSINESS_ERROR', 'reason': 'orderId missing'}
result = process_order(order_id)
return {'status': 'SUCCESS', 'orderId': order_id}
except RetryableError as e:
# 재시도 필요 — 예외를 다시 던져서 Lambda 재시도 유도
logger.warning(f"일시적 오류, 재시도 예정: {str(e)}")
raise # Lambda 비동기 재시도 메커니즘이 동작
except Exception as e:
# 예상치 못한 에러 — 재시도하면서 상황 파악
logger.error(f"예상치 못한 에러: {str(e)}", exc_info=True)
raise동기 호출에서 429를 받는 클라이언트:
import boto3
import random
import time
from botocore.exceptions import ClientError
lambda_client = boto3.client('lambda')
def invoke_with_retry(function_name, payload, max_retries=3):
for attempt in range(max_retries):
try:
return lambda_client.invoke(
FunctionName=function_name,
InvocationType='RequestResponse',
Payload=payload
)
except ClientError as e:
if e.response['Error']['Code'] == 'TooManyRequestsException':
wait_time = (2 ** attempt) + (random.random() * 0.5) # jitter
logger.warning(f"스로틀링. {wait_time:.2f}초 후 재시도 (시도 {attempt+1}/{max_retries})")
time.sleep(wait_time)
else:
raise
raise Exception("최대 재시도 횟수 초과")SQS를 완충재로 사용: 급격한 트래픽 급증이 예상된다면, 호출자가 직접 Lambda를 호출하는 대신 SQS에 넣고, Lambda ESM이 처리 속도를 조절하도록 한다. SQS가 완충재(buffer) 역할을 한다.
[트래픽 급증] → [직접 Lambda 호출] → 429 스로틀링
↓
[트래픽 급증] → [SQS 큐] → [Lambda ESM] → 처리 속도 자동 조절
Lambda 동시성 제어는 네 개의 다른 메커니즘이 중첩된 시스템이다. Reserved는 함수별 격리와 상한선을 제공하고, Provisioned는 콜드 스타트를 제거하며, 버스트 한도는 스케일아웃 속도를 제한하고, 계정 한도는 전체 상한을 정한다. 에러 처리는 호출 방식마다 완전히 다른 계층에서 일어나며, "어디서 재시도하고 어디로 실패 이벤트가 가는지"를 호출 방식별로 정확히 알아야 한다.
다음 글에서는 이 Week 3 전체를 복습하면서 Lambda의 모든 개념을 시나리오 기반 문제로 점검한다.
선택지를 클릭하면 정답·해설이 펼쳐집니다.
문제 1
Lambda 함수의 초당 요청이 2,000개이고 평균 실행 시간이 300ms일 때 필요한 동시성은?
문제 2
Lambda 함수의 Reserved Concurrency를 0으로 설정했을 때의 결과는?
문제 3
Kinesis Data Streams ESM에서 하나의 레코드가 계속 실패할 때 발생하는 문제와 가장 효과적인 해결책 조합은?
문제 4
SQS DLQ와 Lambda DLQ의 차이점으로 옳은 것은?
문제 5
다음 중 Lambda Destinations의 장점이 아닌 것은?
문제 6
현재 Lambda 함수의 기본 동시성 확장 속도로 옳은 것은?