Lambda를 운영하다 보면 CloudWatch 로그에서 이런 메시지를 맞닥뜨리게 된다.
REPORT RequestId: abc123 Duration: 3000.21 ms Billed Duration: 3000 ms
ERROR Task timed out after 3.00 seconds
함수는 실행됐는데 응답 없이 죽어버렸다. 처음엔 당황���럽지만 원인은 거의 정해져 있다.
증상
- CloudWatch 로그에
Task timed out after X.XX seconds출력 - Lambda 함수가 응답을 반환하지 않고 강제 종료됨
- 간헐적으로만 발생하거나, 특정 요청에서 반드시 발생
- API Gateway 연동 시
502 Bad Gateway또는504 Gateway Timeout함께 발생
원인 1: 기본 타임아웃(3초)이 너무 짧다
Lambda 함수의 기본 타임아웃은 3초다. 이게 문제다. DB 쿼리 한 번, 외부 API 호출 한 번만 해도 금방 넘어간다.
확인 방법:
aws lambda get-function-configuration --function-name my-function \
--query 'Timeout'
# 출력: 3
해결: 콘솔에서 [구성] → [일반 구성] → [편집] → 타임아웃 값을 늘린다.
CLI로 변경하려면:
aws lambda update-function-configuration \
--function-name my-function \
--timeout 30
원인 2: DB 연결이 매 요청마다 새로 맺어진다
가장 많이 보는 패턴이다. Lambda 핸들러 쳸안에서 DB 커넥션을 생성하면 요청마다 TCP 핸드셰이크 + 인증 과정을 반복한다. RDS 기준으로 연결 하나 맺는 데 수백 ms가 날아간다.
나쁜 패턴:
def handler(event, context):
conn = psycopg2.connect(host=..., dbname=..., user=..., password=...) # 매번 생성
cursor = conn.cursor()
cursor.execute("SELECT ...")
return cursor.fetchall()
좋은 패턴:
import psycopg2
# 핸들러 밖 — 컨테이너가 살아있는 동안 재사용
conn = psycopg2.connect(host=..., dbname=..., user=..., password=...)
def handler(event, context):
cursor = conn.cursor()
cursor.execute("SELECT ...")
return cursor.fetchall()
Lambda 컨테이너가 재사용되는 Warm 상태에서는 전역 변수가 유지된다. 커넥션을 핸들러 밖에 두면 같은 컨테이너 인스턴스에서 재사용된다.
RDS를 쓰고 있다면 RDS Proxy 도입을 검토한다. 커넥션 풀링을 프록시가 대신 처리해줘서 Lambda와 RDS 사이의 커넥션 폭발 문제도 같이 해결된다.
원인 3: 외부 API 호출 병목 접근하연동 해에서 없다
// 이 코드는 외부 API가 응답 안 하면 Lambda 타임아웃까지 기다린다
const response = await axios.get('https://some-api.example.com/data');
외부 서비스가 느리거나 다운된 경우, 설정된 타임아웃까지 Lambda가 하염없이 대기한다.
수정 (JavaScript):
const response = await axios.get('https://some-api.example.com/data', {
timeout: 5000, // 5초 안에 응답 없으면 에러
});
수정 (Python):
import requests
response = requests.get(
'https://some-api.example.com/data',
timeout=(3.0, 10.0) # (connect timeout, read timeout)
)
원인 4: 순차 API 호출을 직렬로 처리화고 있다
// 이러면 A + B + C 호출 시간이 전부 합산된다
const userInfo = await fetchUser(userId);
const orderInfo = await fetchOrder(userId);
const reviewInfo = await fetchReview(userId);
병렬 처리로 개선:
const [userInfo, orderInfo, reviewInfo] = await Promise.all([
fetchUser(userId),
fetchOrder(userId),
fetchReview(userId),
]);
독립적인 API 호출이라면 Promise.all로 묶으면 가장 느린 요청 시간 하나로 줄어든다.
원인 5: Cold Start + VPC 설정
Lambda를 VPC 안에 넣으면 Cold Start 시간이 크게 늘어난다. CloudWatch Logs에서 Init Duration이 표시되면 Cold Start다.
REPORT RequestId: abc123 Duration: 850.00 ms Billed Duration: 851 ms
Init Duration: 612.38 ms
대응 방법:
- VPC가 필요 없다면 빼는 게 제일 낫다
- 꼭 필요하다면 Provisioned Concurrency 사용 (미리 컨테이너를 띄워둠)
원인 6: 메모리 부족 → CPU 부족
Lambda에서 메모리와 CPU는 연동된다. 메모리를 늘리면 CPU도 더 할당된다. CloudWatch Metrics에서 Max Memory Used를 확인한다.
aws lambda update-function-configuration \
--function-name my-function \
--memory-size 1024
진단 체크리스트
| 확인 항목 | 방법 |
|---|---|
| 타임아웃 설정값 확인 | AWS 콘솔 → 함수 → 일반 구성 |
| Cold Start 여부 | CloudWatch Logs에서 Init Duration 검색 |
| VPC 설정 여부 | 함수 구성 → VPC 섹션 |
| 메모리 사용량 | CloudWatch → Max Memory Used |
| 외부 호출 병목 | AWS X-Ray 트레이싱 활성화 후 확인 |
| DB 커넥션 위치 | 코드에서 커넥션이 핸들러 안에 있는지 확인 |
X-Ray로 병목 찾기
CloudWatch Logs만으로는 어디서 느린지 파악이 어렵다. AWS X-Ray를 활성화하면 함수 내 각 구간의 소요 시간을 추적할 수 있다.
from aws_xray_sdk.core import xray_recorder, patch_all
patch_all() # boto3, requests, psycopg2 등 자동 추적
def handler(event, context):
with xray_recorder.in_subsegment('db-query'):
result = query_database()
return result
정리
Lambda 타임아웃 에러는 대부분 이 순서로 접근하면 해결된다.
- CloudWatch Logs에서
Init Duration유무로 Cold Start 여부 확인 - X-Ray 트레이싱 켜고 어느 구간에서 시간이 먹히는지 파악
- DB 커넥션이 핸들러 안에 있으면 밖으로 빼기
- 외부 API 호출에 명시적 timeout 설정
- 순차 호출을
Promise.all/asyncio.gather로 병렬화 - 그래도 안 되면 메모리 늘리거나 타임아웃 값 상향
타임아웃 값 늘리는 게 제일 빠른 임시방편이긴 하지만, 근본 원인을 안 잡으면 요금만 늘어난다. 특히 RDS 커넥션 관리와 외부 API timeout 설정은 Lambda 쓰면서 기본 중의 기본이다.
댓글 없음:
댓글 쓰기