금요일

AWS Lambda Task Timed Out 에러 해결 — 타임아웃 원인 6가지와 실무 대응법

🔍 검색 키워드: AWS Lambda timeout 해결, Lambda Task timed out after 해결, Lambda 타임아웃 에러, Lambda 함수 느림 원인, AWS Lambda 3초 제한 해결, Lambda cold start 해결

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 타임아웃 에러는 대부분 이 순서로 접근하면 해결된다.

  1. CloudWatch Logs에서 Init Duration 유무로 Cold Start 여부 확인
  2. X-Ray 트레이싱 켜고 어느 구간에서 시간이 먹히는지 파악
  3. DB 커넥션이 핸들러 안에 있으면 밖으로 빼기
  4. 외부 API 호출에 명시적 timeout 설정
  5. 순차 호출을 Promise.all / asyncio.gather로 병렬화
  6. 그래도 안 되면 메모리 늘리거나 타임아웃 값 상향

타임아웃 값 늘리는 게 제일 빠른 임시방편이긴 하지만, 근본 원인을 안 잡으면 요금만 늘어난다. 특히 RDS 커넥션 관리와 외부 API timeout 설정은 Lambda 쓰면서 기본 중의 기본이다.


관련 글: Kubernetes OOMKilled 에러 해결 — exit code 137 원인과 메모리 설정

댓글 없음:

댓글 쓰기