Lambda + SQS (Dead Letter Queue)

Falcon·2021년 8월 12일
1

aws

목록 보기
14/33

🎯 Goal

  • 람다 비동기 호출시 에러를 핸들링할 수 있다.

비동기로 호출된 람다는 어떻게 처리되는가?


비동기 람다는 자체적인 event queue 를 가지고있다.
비동기식으로 람다를 호출하면 이 요청(event)이 event queue 로 들어가고 자체적으로 구현된 poller 에 의해서 polling 방식으로 이벤트를 람다로 실행시킨다.

그럼 반환은 언제?

event가 event queue에 들어가자마자
200 OK 와 함께 빈 페이로드를 반환한다.

Error 종류

비동기 람다호출시 람다 함수 내에서 발생하는 오류 유무와 상관없이 event queue 에 넣기만하면 200 OK 를 받기 때문에 정확한 디버깅이 어렵다.
어떤 케이스에 대해서는 따로 모아서 람다를 재실행하고 싶을 수 있다.

오류 케이스 분류를 위해 2가지 종류를 살펴보자.

호출 오류

함수가 호출 요청을 수신하기 전에 호출 요청이 거부되는 경우

원인

  • 동시성이 충분하지 않은 경우
  • Invalid Parameter
  • Permission

함수 오류 (Runtime Error)

함수 내에서 로직상 나타나는 오류

원인

  • 함수 코드에 예외 발생 (Throw Exception)
  • 구문 오류
  • JSON.stringify() || JSON.parse() 파싱 실패 오류

호출 오류와 달리 함수 오류로 인해 4XX, 5XX 상태 코드를 반환하는 경우는 없다.
앞서 말했듯 event가 event queue에 들어가자마자 즉시 200 OK 를 반환하기 때문이다.
함수 오류 반환시, Lambda는 X-Amz-Function-Error라는 헤더와 오류 메시지 및 기타 세부 정보가 들어 있는 JSON 형식 응답을 포함함으로써 이를 알린다.

- AWS Document

⚠️ 429 TooManyRequests || 500 Internal Server Error 의 경우 event queue로 다시 반환하고 최대 6시간동안 재 polling 이 가능하다.
이때 polling Interval 은 1초 ~ 5분까지 지수 증가한다.

Error Handling 예제

상황

"Throttling 에 의한 에러만 DLQ로 보내고 다시 따로 처리하고싶다" 고 가정해보자
Throttling 발생시 람다는 429 TooManyRequestsException 를 throw 한다.
=> 따라서 ErrorCode가 429 인것만 취하고 나머지는 버리자.

ex) 429 외의 다른 코드는 Throw Error 대신 console.error 로 로깅만한다.

동시성 부족으로인한 람다 쓰로틀링일 때만 Dead Letter Queue로 보내고
함수 오류는 어차피 재시도해도 같은 결과일테니 '버린다'. 어떻게 해야할까?

요청 직후 SQS 메시지를 폴링하여 받아보자.

쓰로틀링된 람다 이벤트를 '핸들링'하려면 트리거 람다함수 등록이 필요하다.
이는 SQS 에 모인 메시지를 처리하는 람다로, 이역시 마찬가지로 polling 방식을 따른다.

핸들링 방법

호출 이벤트가 최대 기간을 초과 || 모든 재시도 실패시
Lambda는 해당 이벤트를 폐기함. => 폐기물을 따로 보내는 곳이 Dead Letter Queue 이다.

Lambda triggered by SQS (event source mapping)

실패된 람다 이벤트가 DLQ에 모였으니 이제 이 모인 메시지를 '받아서' 실행해보자.

선결조건

Lambda 실행 역할에 다음 권한이 포함되어야 한다.

  • sqs:DeleteMessage
  • sqs:GetQueueAttributes
  • sqs:ReceiveMessage

여기서 들수 있는 의문점이 "DeleteMessage 는 왜 필요한가?" 이다. 큐의 속성과 메시지를 받아오는 것은 당연히 해야하는데, 왜 삭제까지?

트리거된 람다가 오류없이 받아온 메시지를 처리하고나면 Poller해왔던 메시지를 DLQ 로부터 '삭제'시킨다. // 그래서 sqs:DeleteMessage 가 필요한것이다.
<-> 반대로 핸들링 도중에 에러가 재발생하면 다시 DLQ로 넣는다.

const retryer: SQSHandler = async (event) => {

  // 이 안의 body 내용이 모두 JSON타입의 '메시지' 라 파싱을 미리 해줘야함.
  const records = 
        event.Records.map(({body})=>JSON.parse(body));

  console.dir(records);
  console.log('========');

  records.forEach(record=>{
    const {channelName, name, count, msg} : MessageBody = record.body;
    console.log(`receive agian!\nchannelName: ${channelName}\nname:${name},count:${count},msg:${msg}`);

  });

};

편-안

⚠️ can't retry about synchronous lambda invocation

애석하게도, 동기적으로 호출한 람다함수에 대해선 자체적으로 재시도할 수 있는 매커니즘이 없다.
예를 들어, 쓰로틀링이 발생해도 해당 람다에 대해 cloudwatch log에 남지도 않고, DLQ로 보내지지도 않는다.

이럴 때는 API Gateway + Lambda 조합을 사용하여 API Gateway Error 429 Too Many Requests 발생시에 SQS로 해당 요청 페이로드를 전송하는 로직을 구현할 수 있다.
이는 다음 시간에 다루겠다.


🔗 Reference

profile
I'm still hungry

2개의 댓글

comment-user-thumbnail
2021년 12월 23일

질문 있습니다.

비동기 람다는 자체적인 event queue를 가진다고 하셨는데,
이 event queue라는 것은, AWS SQS와는 다른 건가요?

1개의 답글