Lambda 실행 트러블슈팅
Lambda 실행 트러블슈팅 (Troubleshoot execution issues in Lambda)
Lambda 런타임이 함수 코드를 실행할 때, 이벤트가 한동안 이벤트를 처리해 온 함수 인스턴스에서 처리될 수도 있고 새 인스턴스 초기화가 필요할 수도 있어요. 오류는 함수 초기화 중, 핸들러 코드가 이벤트를 처리할 때, 또는 함수가 응답을 반환(또는 반환하지 못)할 때 발생할 수 있어요.
함수 실행 오류는 코드, 함수 구성, 다운스트림 리소스, 또는 권한 문제로 발생할 수 있어요. 함수를 직접 호출하면 Lambda 응답에서 함수 오류를 볼 수 있어요. 함수를 비동기로, 이벤트 소스 매핑으로, 또는 다른 서비스를 통해 호출하면 로그, 데드 레터 큐, 또는 실패 시 대상에서 오류를 찾을 수 있어요. 오류 처리 옵션과 재시도 동작은 함수 호출 방식과 오류 유형에 따라 달라져요.
함수 코드나 Lambda 런타임이 오류를 반환하면 Lambda 응답의 상태 코드는 200 OK예요. 응답에 오류가 있으면 X-Amz-Function-Error라는 헤더로 표시돼요. 400과 500 시리즈 상태 코드는 호출 오류(invocation errors)용으로 예약돼 있어요.
Topics
- Lambda: Visual Studio Code로 원격 디버깅
- Lambda: 실행이 너무 오래 걸림
- Lambda: 예기치 않은 이벤트 페이로드
- Lambda: 예기치 않게 큰 페이로드 크기
- Lambda: JSON 인코딩·디코딩 오류
- Lambda: 로그나 추적이 나타나지 않음
- Lambda: 함수 로그가 전부 나타나지 않음
- Lambda: 실행이 끝나기 전에 함수가 반환됨
- Lambda: 의도하지 않은 함수 버전이나 별칭 실행
- Lambda: 무한 루프 감지
- 일반: 다운스트림 서비스 불가
- AWS SDK: 버전과 업데이트
- Python: 라이브러리가 올바르게 로드되지 않음
- Java: Java 11에서 Java 17로 업데이트한 후 이벤트 처리 시간이 늘어남
- Kafka: 오류 처리·재시도 구성 문제
본문
Lambda: Visual Studio Code로 원격 디버깅
문제: 실제 AWS 환경에서 복잡한 Lambda 함수 동작을 트러블슈팅하기 어려움
Lambda는 AWS Toolkit for Visual Studio Code를 통한 원격 디버깅 기능을 제공해요. 설정과 일반 지침은 Visual Studio Code로 Lambda 함수 원격 디버깅하기를 참고하세요.
자세한 트러블슈팅 지침, 고급 사용 사례, 리전 가용성은 AWS Toolkit for Visual Studio Code User Guide의 Lambda 함수 원격 디버깅을 참고하세요.
Lambda: 실행이 너무 오래 걸림
문제: 함수 실행이 너무 오래 걸림
코드가 로컬 머신보다 Lambda에서 훨씬 오래 실행된다면 함수에 제공된 메모리나 처리 성능이 제약일 수 있어요. 함수에 추가 메모리 구성으로 메모리와 CPU를 모두 늘려요.
Lambda: 예기치 않은 이벤트 페이로드
문제: 잘못된 JSON이나 부적절한 데이터 검증과 관련된 함수 오류
모든 Lambda 함수는 핸들러의 첫 파라미터로 이벤트 페이로드를 받아요. 이벤트 페이로드는 배열과 중첩 요소를 포함할 수 있는 JSON 구조예요.
잘못된 JSON은 JSON 구조를 확인하는 견고한 프로세스를 사용하지 않는 업스트림 서비스에서 제공될 때 발생할 수 있어요. 서비스가 문자열을 연결하거나 정리되지 않은 사용자 입력을 포함할 때 발생하죠. JSON은 서비스 사이를 전달하기 위해 자주 직렬화되기도 해요. JSON 생산자와 소비자 양쪽에서 항상 JSON 구조를 파싱해 유효한 구조인지 확인하세요.
마찬가지로 이벤트 페이로드의 값 범위를 확인하지 않으면 오류가 발생할 수 있어요. 다음 예시는 세금 원천징수를 계산하는 함수를 보여줘요.
exports.handler = async (event) => {
let pct = event.taxPct
let salary = event.salary
// Calculate % of paycheck for taxes
return (salary * pct)
}
이 함수는 이벤트 페이로드의 급여와 세율로 계산을 수행해요. 그러나 코드는 속성이 존재하는지, 데이터 유형을 확인하지 않으며, 세율이 0과 1 사이인지 같은 경계를 보장하지 않아요. 그 결과 이러한 범위를 벗어난 값은 터무니없는 결과를 만들어요. 잘못된 유형이나 누락된 속성은 런타임 오류를 일으켜요.
함수가 더 큰 페이로드 크기를 처리하는지 확인하는 테스트를 만들어요. Lambda 이벤트 페이로드의 최대 크기는 1MB예요. 내용에 따라 더 큰 페이로드는 함수에 전달되는 항목이 더 많거나 JSON 속성에 포함된 바이너리 데이터가 더 많다는 뜻일 수 있어요. 두 경우 모두 Lambda 함수에 더 많은 처리가 필요할 수 있어요.
더 큰 페이로드는 타임아웃을 일으킬 수도 있어요. 예를 들어 Lambda 함수가 100ms마다 레코드 하나를 처리하고 타임아웃이 3초라면, 페이로드의 0~29개 항목은 처리가 성공해요. 그러나 페이로드에 30개가 넘는 항목이 있으면 함수가 타임아웃되고 오류를 던져요. 이를 피하려면 예상되는 최대 항목 수에 대한 추가 처리 시간을 감당하도록 타임아웃을 설정해야 해요.
Lambda: 예기치 않게 큰 페이로드 크기
문제: 큰 페이로드 때문에 함수가 타임아웃되거나 오류 발생
더 큰 페이로드는 타임아웃과 오류를 일으킬 수 있어요. 함수가 예상하는 최대 페이로드를 처리하는지 확인하는 테스트를 만들고, 함수 타임아웃이 제대로 설정됐는지 확인하는 것을 권장해요.
또한 어떤 이벤트 페이로드는 다른 리소스에 대한 포인터를 포함할 수 있어요. 예를 들어 128MB 메모리의 Lambda 함수가 S3 객체로 저장된 JPG 파일에 이미지 처리를 수행할 수 있어요. 이 함수는 더 작은 이미지 파일에서 예상대로 동작해요.
그러나 더 큰 JPG 파일이 입력으로 제공되면 Lambda 함수가 메모리 부족으로 오류를 던져요. 이를 피하려면 테스트 케이스에 예상 데이터 크기의 상한 예시를 포함해야 해요. 코드도 페이로드 크기를 검증해야 해요.
Lambda: JSON 인코딩·디코딩 오류
문제: JSON 입력을 파싱할 때 NoSuchKey 예외
JSON 속성을 올바르게 처리하고 있는지 확인하세요. 예를 들어 S3가 생성한 이벤트의 s3.object.key 속성은 URL 인코딩된 객체 키 이름을 포함해요. 많은 함수가 이 속성을 텍스트로 처리해 참조된 S3 객체를 로드해요.
예시
const originalText = await s3.getObject({
Bucket: event.Records[0].s3.bucket.name,
Key: event.Records[0].s3.object.key
}).promise()
이 코드는 키 이름 james.jpg로는 동작하지만 james beswick.jpg라는 이름에서는 NoSuchKey 오류를 던져요. URL 인코딩은 키 이름의 공백과 다른 문자를 변환하므로, 함수는 이 데이터를 사용하기 전에 키를 디코딩해야 해요.
예시
const originalText = await s3.getObject({
Bucket: event.Records[0].s3.bucket.name,
Key: decodeURIComponent(event.Records[0].s3.object.key.replace(/\+/g, " "))
}).promise()
Lambda: 로그나 추적이 나타나지 않음
문제: CloudWatch Logs에 로그가 나타나지 않음 문제: AWS X-Ray에 추적이 나타나지 않음
함수에는 CloudWatch Logs와 X-Ray를 호출할 권한이 필요해요. 실행 역할(execution role)을 업데이트해 권한을 부여해요. 로그와 추적을 활성화하려면 다음 관리형 정책을 추가해요.
- AWSLambdaBasicExecutionRole
- AWSXRayDaemonWriteAccess
함수에 권한을 추가할 때는 코드나 구성의 사소한 업데이트도 함께 수행해요. 이렇게 하면 만료된 자격 증명을 가진 실행 중인 함수 인스턴스가 중지되고 교체되도록 강제해요.
참고 함수 호출 후 로그가 나타나려면 5~10분이 걸릴 수 있어요.
Lambda: 함수 로그가 전부 나타나지 않음
문제: 권한은 올바르지만 CloudWatch Logs에서 함수 로그가 누락됨
AWS 계정이 CloudWatch Logs 할당량 한도에 도달하면 CloudWatch가 함수 로깅을 스로틀링해요. 이 경우 함수가 출력한 로그 중 일부가 CloudWatch Logs에 나타나지 않을 수 있어요.
함수가 Lambda가 처리할 수 있는 것보다 너무 높은 비율로 로그를 출력해도 CloudWatch Logs에 로그가 나타나지 않을 수 있어요. Lambda가 함수가 생성하는 속도로 로그를 CloudWatch에 보낼 수 없으면, 함수 실행이 느려지는 것을 막기 위해 로그를 버려요. 단일 로그 스트림에서 로그 처리량이 2MB/s를 초과하면 로그가 계속 버려지는 것을 관찰할 것으로 기대해요.
함수가 JSON 형식 로그를 사용하도록 구성된 경우, Lambda는 로그를 버릴 때 logsDropped 이벤트를 CloudWatch Logs로 보내려 해요. 그러나 CloudWatch가 함수 로깅을 스로틀링하면 이 이벤트가 CloudWatch Logs에 도달하지 못할 수 있으므로, Lambda가 로그를 버릴 때 항상 기록을 볼 수는 없어요.
AWS 계정이 CloudWatch Logs 할당량 한도에 도달했는지 확인하려면 다음을 수행해요.
- Service Quotas 콘솔을 열어요.
- 탐색 창에서 AWS services를 선택해요.
- AWS services 목록에서 Amazon CloudWatch Logs를 검색해요.
- Service quotas 목록에서
CreateLogGroup throttle limit in transactions per second,CreateLogStream throttle limit in transactions per second,PutLogEvents throttle limit in transactions per second할당량을 선택해 사용률을 확인해요.
계정 사용률이 이 할당량에 대해 지정한 한도를 초과할 때 알려주는 CloudWatch 알람을 설정할 수도 있어요. 자세한 내용은 정적 임계값 기반 CloudWatch 알람 만들기를 참고하세요.
CloudWatch Logs의 기본 할당량 한도가 사용 사례에 충분하지 않다면 할당량 증가를 요청할 수 있어요.
Lambda: 실행이 끝나기 전에 함수가 반환됨
문제 (Node.js): 코드 실행이 끝나기 전에 함수가 반환됨
AWS SDK를 포함한 많은 라이브러리는 비동기로 동작해요. 네트워크 호출을 하거나 응답을 기다려야 하는 다른 연산을 수행할 때 라이브러리는 백그라운드에서 연산 진행을 추적하는 promise라는 객체를 반환해요.
promise가 응답으로 해석될 때까지 기다리려면 await 키워드를 사용해요. 이 키워드는 promise가 응답을 포함한 객체로 해석될 때까지 핸들러 코드 실행을 차단해요. 응답의 데이터를 코드에서 사용할 필요가 없다면 promise를 런타임에 직접 반환할 수 있어요.
promise를 반환하지 않는 일부 라이브러리는 promise를 반환하는 코드로 감쌀 수 있어요. 자세한 내용은 Node.js에서 Lambda 함수 핸들러 정의하기를 참고하세요.
Lambda: 의도하지 않은 함수 버전이나 별칭 실행
문제: 함수 버전이나 별칭이 호출되지 않음
콘솔이나 AWS SAM으로 새 Lambda 함수를 게시할 때 최신 코드 버전은 $LATEST로 표현돼요. 기본적으로 버전이나 별칭을 지정하지 않는 호출은 함수 코드의 $LATEST 버전을 자동으로 대상으로 해요.
특정 함수 버전이나 별칭을 사용하면 이들은 $LATEST에 추가된 불변의 게시된 함수 버전이에요. 이 함수들을 트러블슈팅할 때 먼저 호출자가 의도한 버전이나 별칭을 호출했는지 확인해요. 함수 로그를 확인해서 알 수 있어요. 호출된 함수의 버전은 항상 START 로그 줄에 표시돼요.
Lambda: 무한 루프 감지
문제: Lambda 함수와 관련된 무한 루프 패턴
Lambda 함수에는 두 가지 유형의 무한 루프가 있어요. 첫 번째는 함수 자체 안에서 발생하며, 종료되지 않는 루프 때문에 생겨요. 호출은 함수가 타임아웃될 때만 끝나요. 타임아웃을 모니터링하고 나서 루프 동작을 고쳐 식별할 수 있어요.
두 번째 루프 유형은 Lambda 함수와 다른 AWS 리소스 사이에서 발생해요. S3 버킷 같은 리소스의 이벤트가 Lambda 함수를 호출하고, 함수가 같은 소스 리소스와 상호작용해 또 다른 이벤트를 촉발할 때 발생하죠. 이렇게 함수가 다시 호출되고, 같은 S3 버킷과 다시 상호작용하며 계속 반복돼요. 이런 루프 유형은 Amazon SQS 큐와 DynamoDB 테이블을 포함한 다양한 AWS 이벤트 소스 때문에 발생할 수 있어요. 재귀 루프 감지(recursive loop detection)로 이런 패턴을 식별할 수 있어요.
Lambda 함수가 소비 리소스와 다른 리소스에 쓰도록 해서 이 루프들을 피할 수 있어요. 소비 리소스에 데이터를 다시 게시해야 한다면 새 데이터가 같은 이벤트를 촉발하지 않도록 해요. 또는 이벤트 필터링(event filtering)을 사용해요. 예를 들어 S3와 DynamoDB 리소스의 무한 루프에 대한 두 가지 제안된 해결책이 있어요.
- 같은 S3 버킷에 다시 쓴다면 이벤트 트리거와 다른 접두어나 접미어를 사용해요.
- 같은 DynamoDB 테이블에 항목을 쓴다면, 소비 Lambda 함수가 필터링할 수 있는 속성을 포함해요. Lambda가 그 속성을 찾으면 또 다른 호출로 이어지지 않아요.
일반: 다운스트림 서비스 불가
문제: Lambda 함수가 의존하는 다운스트림 서비스를 사용할 수 없음
서드파티 엔드포인트나 다른 다운스트림 리소스에 호출하는 Lambda 함수는 서비스 오류와 타임아웃을 처리할 수 있도록 해야 해요. 이 다운스트림 리소스는 응답 시간이 가변적이거나 서비스 중단으로 사용할 수 없게 될 수 있어요. 구현에 따라, 서비스의 오류 응답이 함수 코드에서 처리되지 않으면 이런 다운스트림 오류가 Lambda 타임아웃이나 예외로 나타날 수 있어요.
함수가 API 호출 같은 다운스트림 서비스에 의존할 때마다 적절한 오류 처리와 재시도 로직을 구현해요. 중요 서비스의 경우 Lambda 함수가 지표나 로그를 CloudWatch에 게시해야 해요. 예를 들어 서드파티 결제 API를 사용할 수 없게 되면 Lambda 함수가 이 정보를 로그로 기록할 수 있어요. 그러면 CloudWatch 알람을 설정해 이런 오류와 관련된 알림을 보낼 수 있어요.
Lambda는 빠르게 확장할 수 있기 때문에 비서버리스 다운스트림 서비스는 트래픽 급증을 처리하는 데 어려움을 겪을 수 있어요. 이를 처리하는 일반적인 접근 방식은 세 가지가 있어요.
- 캐싱(Caching) — 서드파티 서비스가 반환하는 값이 자주 바뀌지 않으면 그 결과를 캐싱하는 것을 고려해요. 이 값을 함수의 전역 변수나 다른 서비스에 저장할 수 있어요. 예를 들어 Amazon RDS 인스턴스의 제품 목록 쿼리 결과를 중복 쿼리를 막기 위해 함수 안에 일정 시간 저장할 수 있어요.
- 큐잉(Queuing) — 데이터를 저장하거나 업데이트할 때 Lambda 함수와 리소스 사이에 Amazon SQS 큐를 추가해요. 큐는 다운스트림 서비스가 메시지를 처리하는 동안 데이터를 내구성 있게 유지해요.
- 프록시(Proxies) — Amazon RDS 인스턴스처럼 긴 수명 연결이 일반적으로 사용되는 곳에서는 프록시 레이어로 연결을 풀링하고 재사용해요. 관계형 데이터베이스의 경우 Lambda 기반 애플리케이션에서 확장성과 복원력을 개선하도록 설계된 Amazon RDS Proxy 서비스를 사용해요.
AWS SDK: 버전과 업데이트
문제: 런타임에 포함된 AWS SDK가 최신 버전이 아님 문제: 런타임에 포함된 AWS SDK가 자동으로 업데이트됨
해석형 언어의 런타임에는 AWS SDK 버전이 포함돼요. Lambda는 최신 SDK 버전을 사용하도록 이 런타임을 정기적으로 업데이트해요. 런타임에 포함된 SDK 버전을 확인하려면 다음 섹션을 참고하세요.
더 새 버전의 AWS SDK를 사용하거나 함수를 특정 버전에 고정하려면 라이브러리를 함수 코드와 번들하거나 Lambda 레이어를 만들 수 있어요. 의존성이 있는 배포 패키지 만들기에 대한 자세한 내용은 다음 항목을 참고하세요.
- Node.js — Node.js Lambda 함수를 .zip 파일 아카이브로 배포하기
- Python — Python Lambda 함수용 .zip 파일 아카이브 작업하기
- Ruby — Ruby Lambda 함수를 .zip 파일 아카이브로 배포하기
- Java — Java Lambda 함수를 .zip 또는 JAR 파일 아카이브로 배포하기
- Go — Go Lambda 함수를 .zip 파일 아카이브로 배포하기
- C# — C# Lambda 함수를 .zip 파일 아카이브로 빌드·배포하기
- PowerShell — PowerShell Lambda 함수를 .zip 파일 아카이브로 배포하기
Python: 라이브러리가 올바르게 로드되지 않음
문제 (Python): 일부 라이브러리가 배포 패키지에서 올바르게 로드되지 않음
C나 C++로 작성된 확장 모듈이 있는 라이브러리는 Lambda(Amazon Linux)와 같은 프로세서 아키텍처의 환경에서 컴파일해야 해요. 자세한 내용은 Python Lambda 함수용 .zip 파일 아카이브 작업하기를 참고하세요.
Java: Java 11에서 Java 17로 업데이트한 후 이벤트 처리 시간이 늘어남
문제 (Java): Java 11에서 Java 17로 업데이트한 후 이벤트 처리 시간이 더 길어짐
JAVA_TOOL_OPTIONS 파라미터로 컴파일러를 튜닝해요. Java 17 이상의 Java 버전용 Lambda 런타임은 기본 컴파일러 옵션을 바꿔요. 이 변경은 단기 실행 함수의 콜드 스타트 시간을 개선하지만, 이전 동작은 계산 집약적이고 더 오래 실행되는 함수에 더 적합해요. JAVA_TOOL_OPTIONS를 -XX:-TieredCompilation으로 설정하면 Java 11 동작으로 되돌릴 수 있어요. JAVA_TOOL_OPTIONS 파라미터에 대한 자세한 내용은 JAVA_TOOL_OPTIONS 환경 변수 이해하기를 참고하세요.
Kafka: 오류 처리·재시도 구성 문제
문제: Kafka 이벤트 소스 매핑이 재시도 설정이나 실패 시 대상을 구성하지 못함
Kafka 재시도 구성과 실패 시 대상은 프로비저닝 모드가 활성화된 이벤트 소스 매핑에서만 사용할 수 있어요. 재시도 구성을 설정하기 전에 ProvisionedPollerConfig에서 MinimumPollers를 구성했는지 확인하세요.
일반적인 구성 오류:
- bisect batch가 있는 무한 재시도 —
MaximumRetryAttempts가 -1(무한)일 때BisectBatchOnFunctionError를 활성화할 수 없어요. 유한 재시도 한도를 설정하거나 bisect batch를 비활성화 해요. - 같은 토픽 재귀 — Kafka 실패 시 대상 토픽은 소스 토픽 중 하나와 같을 수 없어요. 데드 레터 토픽에 다른 토픽 이름을 선택해요.
- 잘못된 Kafka 대상 형식 — Kafka 토픽을 실패 시 대상으로 지정할 때
kafka://<topic-name>형식을 사용해요. - kafka:WriteData 권한 문제 — 실행 역할이 대상 토픽에
kafka-cluster:WriteData권한이 있는지 확인하세요. 토픽이 존재하지 않는다는 타임아웃 예외나 쓰기 API 스로틀링 문제는 계정 한도 증가가 필요할 수 있어요.