AWS SQS와 Lambda 동시성의 밀당 (Set Visibility Timeout)

Jeremy
직방 기술 블로그

--

안녕하세요, 호갱노노 백엔드 팀에서 근무 중인 Jeremy입니다. 이번에는 호갱노노에서 사용하는 푸시 서비스와 그 근간을 이루고 있는 AWS SQS 그리고 AWS Lambda에 관해 잠시 이야기해보려고 합니다.

호갱노노 서비스의 가장 굵직한 기능을 꼽으라면 (물론 사람마다 다르겠지만), 저는 주저없이 푸시 서비스를 꼽습니다. 지금 이 순간에도 많은 사용자들이 서비스에서 제공하는 각종 구독 기능을 통해 실거래가, 분양, 아파트 커뮤니티의 게시글 등 여러 종류의 푸시를 받고 이용하는데요. 서비스를 운영하는 입장에서도 사용자에게 정보를 제공함과 동시에 재방문률을 확보할 수 있어서 문제가 생기면 안되는 중요한 기능 중 하나입니다.

호갱노노에서는 푸시를 내보내기 위해 AWS SQS와 Lambda를 사용합니다. 꼭 푸시가 아니더라도 대용량 데이터 처리를 위해 굉장히 효과적인 조합으로 잘 알려져 있습니다. 직방 기술 블로그에서도 이 조합에 관련해 여러 번 다뤄왔습니다.

AWS SQS, Lambda 를 이용하여 쉽게 지연(Delay) 작업 처리하기
Amazon SQS에 숨겨진 단일실패지점

쿨타임이 돌았으니 살짝 재탕을 하면서 몇 가지를 추가적으로 얘기해보겠습니다. 위 블로그 글들도 꼭 한 번 읽어주세요!

푸시 알림은 동일한 메시지를 중복으로 전송하면 안되는 단일성이 보장되어야 합니다. 이 때문에 SQS의 선입선출(FIFO) 메시지 큐를 사용하는데요. 이 메시지 큐의 트리거에 푸시를 발송하는 Lambda가 붙어서 처리하는 구조입니다. 그럼 SQS와 Lambda가 어떻게 작동하는지를 잠시 살펴보면요.

  1. 푸시를 내보내야 할 메시지들이 SQS에 쌓이기 시작합니다.
  2. FIFO 메시지 큐는 처리 중인 메시지의 그룹을 생성하고, 이 그룹을 Lambda 내부 poller가 끌어갑니다.
  3. Lambda가 끌어 가면서 이 메시지 그룹은 단일성을 보장받기 위해 메시지 큐에서 숨김 처리됩니다.
  4. Lambda function이 실행되면서 메시지 그룹을 처리하게 되고 처리에 성공하면 메시지 큐에서 숨겨진 처리된 그룹을 삭제합니다.

Lambda와 SQS의 밀당은 여기서부터 시작됩니다. SQS 입장에서는 Lambda가 빠르고 안정적으로 메시지 큐를 비워줬으면 하지만 Lambda에 있는 동시성 제한 때문에 가끔 빠른 처리가 안될 때가 있습니다. 아무리 많은 메시지가 쌓여 있어도 기본적으로 Lambda는 최대 1000개의 동시 실행만 지원합니다.

그럼 Lambda 입장에서는 역으로 SQS가 메시지 그룹을 처리하는 걸 기다렸다가 끝나면 다음 그룹을 넘겨줬으면 할텐데 그러지 않습니다. 계속 주려고만 합니다. 이 사이를 절묘하게 파고드는 게 Visibility timeout (가시성 제한 시간)입니다.

서로에 대한 배려 없이 독립적으로 돌아가는 SQS와 Lambda 사이에서 Lambda poller가 메시지 그룹을 땡겨가면 잠시 숨김 처리를 해서 다른 poller가 이중으로 땡겨가는 것을 미연에 방지하는데요. 이때, 숨김 처리 시간을 visibility timeout을 변경해 조절할 수 있습니다.

푸시를 기준으로 생각해보면, 10개의 푸시 발송이 담긴 메시지 그룹에 대해 Lambda가 처리하는데 만약 1초가 걸린다고 가정할 때, visibility timeout을 1초 이상으로만 잡으면 되겠죠. 1초 이상만 숨겨 놓으면 Lambda 성공 이후 삭제가 될테니까요!

하지만 대량의 메시지가 들어오게 되는 상황에서는 순간적으로 메시지가 폭증하는 경우가 발생할 수 있습니다. 이때, 확보된 모든 Lambda들이 돌아가게 되고 가끔 쓰로틀링이 걸리게 될 수 있습니다. 쓰로틀링이 걸리게 되면 메시지 그룹을 처리하는데 지연이 발생하게 되는데, 이 시간만큼 visibility timeout 시간을 초과하게 될 가능성이 높아집니다. 그럼 숨겨졌던 메시지 그룹이 다시 보여지면서 다른 Lambda poller가 메시지 그룹을 땡겨 갈 수 있게 되는 것이죠. 이러한 상황은 단일성에 큰 위협이 될 수 있습니다.

이를 막기 위해서는 서로 간의 적절한 밀당이 필요합니다. Lambda 기준으로 보면, 쓰로틀링이 문제가 되니 reserved concurrency의 갯수를 늘려 동시성을 확보하는 것이 좋습니다. 또한 Lambda 자체의 성능을 개선하기 위해 메모리 및 CPU를 증설하는 방법도 있지만, 이는 비용과도 직접적인 영향이 있기 때문에 상시적으로 발생하지 않는 쓰로틀링 대응으로 적용하기는 적절하지 않습니다.

SQS 기준에서는 Set Visibility Timeout을 늘려 쓰로틀링에 대비할 수 있습니다. 또한 maxReceiveCount를 늘려 재시도 횟수를 증가시키고 Dead Letter Queue(DLQ)로 메시지를 보내는 것도 방지할 수 있습니다. 이번에는 Set Visibility Timeout에 집중해봅시다.

위에서 설명한대로 visibility timeout은 Lambda 함수가 SQS 메시지를 처리하는 동안 메시지가 보이지 않게 하는 기능입니다. 이는 Lambda 함수가 작업을 완료할 때까지 다른 프로세스가 이 메시지를 처리하지 않도록 보장합니다. 하지만, 이 값을 너무 길게 설정하면 메시지 처리가 느려질 수 있고, 너무 짧게 설정하면 다른 프로세스가 동일한 메시지를 처리할 가능성이 있습니다.

AWS에서는 안정적으로 Lambda 함수 timeout의 최소 6배를 권장합니다. 이는 메시지를 처리하는 동안 예상치 못한 쓰로틀링으로 인해 메시지 처리에 필요한 추가 시간을 보장하기 위함입니다. 따라서 Lambda 함수의 timeout 값을 고려하여 visibility timeout 값을 설정하는 것이 중요합니다.

또한, SQS에서는 maxReceiveCount를 늘려서 재시도 횟수를 증가시키고 Dead Letter Queues (DLQ)를 활용하여 메시지 처리 실패 시에 대처할 수 있습니다. maxReceiveCount를 증가시키면 SQS가 메시지를 자동으로 DLQ로 보낼 때까지 메시지를 여러 번 처리할 수 있습니다. DLQ를 사용하면 메시지 처리에 실패하더라도 중요한 메시지가 유실되지 않고 별도의 큐에 저장됩니다.

따라서, SQS를 사용할 때는 visibility timeout, maxReceiveCount, 그리고 DLQ를 고려하여 메시지 처리 실패에 대비하는 것이 좋습니다.

마치며

푸시는 항상 쉽지 않은 것 같습니다. 대규모로 발송이 되다 보면 항상 예상하지 못한 곳에서 문제가 터지는 일도 다반사구요. SQS와 Lambda를 활용해 푸시와 씨름하시는 모든 개발자분들 항상 힘내시길 바랍니다. 긴 글 시간내서 읽어주셔서 감사합니다.

--

--