🔥 Lambda 와 SQS 연결: 배치와 동시성
강의 목차

처음 SQS 큐를 Lambda 함수에 붙였을 때, 콘솔에서 누른 버튼은 함수 쪽 Add trigger 한 개뿐이었다. 큐를 고르고 함수를 고르고 한 번 더 확인 버튼을 누르자 다음 화면에 트리거가 등장했다. 그런데 트리거 설정 페이지에는 Batch size, Batch window, Maximum concurrency 세 칸이 기본값으로 채워져 있었다. 그 칸의 의미를 모른 채 기본값으로 두고 운영에 들어가면, 큐가 한가할 때 호출 비용이 어떻게 청구되는지, 큐가 폭주할 때 함수 인스턴스가 어디까지 늘어나 하류 데이터베이스를 어떻게 흔드는지가 그제야 보인다.
큐를 함수에 붙이는 길은 사실 두 서비스를 직접 잇는 길이 아니다. Lambda 가 관리하는 별도 리소스 하나가 큐를 폴링하고, 그 결과를 함수에 동기로 전달한다. 이 글은 그 리소스 위에 놓인 세 가지 설정과, 묶음 처리에서 따라오는 부분 실패와 visibility timeout 권장값을 함께 본다. 호출 모델 분류 자체는 Lambda 호출 모델 에서 다뤘고, 트리거가 누구의 invoke 로 도는지는 Lambda 트리거 종류 에서 따로 다뤘다.
Event Source Mapping 이 큐와 함수 사이에 앉는다

Event Source Mapping 은 큐와 함수를 잇는 별도 리소스다. SQS 자체는 Lambda 를 모른다. Lambda 함수도 큐를 모른다. 두 서비스 사이에서 폴링과 호출과 삭제를 도맡는 리소스가 ESM 이고, Lambda 트리거 종류 에서 본 그 식별자 UUID 와 ARN 을 그대로 쓴다. 이 글에서는 이 리소스가 갖는 세 가지 설정 값을 본다.
BatchSize 는 ESM 이 한 번의 함수 호출에 몰아 넣는 메시지 수다. SQS Standard 큐 기준 한도는 1 부터 10,000 까지이고 기본값은 10 이다. FIFO 큐에서는 한도가 1 부터 10 까지로 좁다. 같은 그룹 안에서 순서를 보존해야 하기 때문에 한 번에 묶는 양을 줄여 둔 결과다. 한 호출의 페이로드 합계는 6 MB 를 넘을 수 없고, 묶음이 그 한도를 넘기면 ESM 이 더 작게 끊어 호출한다 (AWS Developer Guide).
MaximumBatchingWindowInSeconds 는 묶음을 채우려고 ESM 이 기다리는 최대 시간이다. 0 초부터 300 초까지 정할 수 있고, SQS Standard 큐의 기본값은 0 초이며 FIFO 큐는 0 초로 고정이다. 메시지 도착이 드문 큐에서 BatchSize 10 + 0 초 조합을 두면, 메시지가 한 건씩 들어올 때마다 ESM 이 함수를 한 번씩 호출한다. 이 조합이 호출 횟수를 가장 많이 만들고 비용도 그만큼 늘어난다. 같은 큐에서 BatchSize 10 + 5 초 조합을 두면, ESM 이 5 초 동안 메시지를 모아 한 번에 묶어 호출한다. 응답 지연은 5 초 늘지만 호출 횟수가 줄어든다.
이 두 값은 결국 호출 횟수와 묶음 크기 사이에서 어디에 균형을 둘지를 정한다. 묶음을 키우면 한 호출 안 작업량이 늘고 한 메시지 실패의 영향 범위도 같이 커진다. 부분 실패 처리는 뒤에서 따로 짚는다.
MaximumConcurrency 는 ESM 단위 동시성 한도다

MaximumConcurrency 는 한 ESM 에서 동시에 도는 함수 인스턴스 수의 상한이다. 2022-11-22 출시된 SQS 전용 옵션이고, 값 범위는 2 부터 1,000 까지다 (AWS News Blog). 함수의 계정 단위 동시성 한도 안에서만 의미가 있고, 더 낮은 쪽이 실제 상한이 된다.
이 값이 함수 단위가 아니라 ESM 단위라는 점이 핵심이다. 함수 단위 동시성 한도는 Lambda 동시성 에서 본 Reserved Concurrency 가 따로 맡는다. 같은 함수가 큐 두 개에 붙어 있다면 큐별 ESM 마다 MaximumConcurrency 를 다르게 둘 수 있고, 그 결과 한 큐가 폭주해도 다른 큐 처리에는 같은 함수의 동시성 여유가 남는다. 한 큐만 데이터베이스에 부담을 주는 무거운 작업이고 다른 큐는 가벼운 알림이라면, 무거운 큐 쪽 ESM 에만 50 같은 낮은 값을 두는 식으로 하류를 보호한다.
값 하한이 1 이 아니라 2 인 이유도 같은 맥락이다. 1 로 두면 ESM 이 한 번에 한 인스턴스만 띄워 한 메시지가 막히는 동안 큐 전체가 멈춘다. ESM 단계의 단일 처리 슬롯은 큐 처리량을 거의 0 으로 만들기 때문에, AWS 가 설정 단계에서 막아 두었다.
MaximumConcurrency 와 함수의 Reserved Concurrency 가 충돌하면 더 낮은 쪽이 우선이다. ESM 폴러는 함수가 throttle 응답을 돌려보내는 동안 backoff 로 느려지고, 메시지는 visibility timeout 이 만료되어 다시 보일 때까지 큐에 남는다.
ReportBatchItemFailures 가 묶음 안 부분 실패를 골라 낸다

묶음 호출은 한 번에 여러 메시지를 함께 처리하기 때문에, 일부만 실패한 경우 그 메시지만 골라 다시 큐로 돌려보내야 한다. 그 신호를 함수가 ESM 에 돌려보내는 방식이 ReportBatchItemFailures 다.
이 옵션을 켠 함수는 응답 페이로드의 batchItemFailures 배열에 실패한 메시지의 itemIdentifier 만 담아 돌려보낸다. ESM 은 그 응답을 받아 성공한 메시지를 DeleteMessage 로 지우고, 실패 식별자에 들어 있는 메시지는 큐에 남긴다 (AWS Developer Guide). 남은 메시지는 visibility timeout 이 만료된 뒤 다시 보이게 되고, 그 시점부터 ESM 이 같은 메시지를 다시 처리에 들어간다.
옵션을 켜지 않은 상태에서 함수가 한 메시지에 대해 예외를 던지면 묶음 전체가 재시도 대상이 된다. 9 개가 정상 처리됐어도 한 개의 poison 메시지가 나머지를 함께 끌고 가는 셈이다. 같은 메시지가 반복적으로 묶음에 끼면 다른 9 개도 매번 같이 다시 처리되기 때문에, 묶음이 큰 큐일수록 이 옵션은 사실상 필수에 가깝다.
ApproximateReceiveCount 가 source 큐의 maxReceiveCount 를 넘기는 순간 메시지는 Dead Letter Queue: 실패 메시지의 격리 에서 다룬 경로로 자동 이동한다. 부분 실패 신호는 그 카운트가 어떻게 올라가는지를 결정하고, DLQ 는 그 카운트가 임계값을 넘긴 뒤를 맡는다.
visibility timeout 권장은 함수 timeout 의 6 배에 batching window 를 더한 값이다

visibility timeout 자체 동작은 Visibility Timeout: 메시지가 보이지 않는 이유 에서 다뤘다. 이 글에서는 ESM 위에서의 권장값 하나만 본다.
AWS 가 권하는 값은 함수 timeout 의 6 배에 MaximumBatchingWindowInSeconds 를 더한 값이다. 함수 timeout 이 30 초이고 batching window 가 5 초라면, 큐의 visibility timeout 은 185 초 정도로 잡으라는 뜻이다 (AWS Developer Guide). 6 배라는 비율은 ESM 폴러가 함수 throttle 에 부딪혀 backoff 로 다시 호출하는 동안에도 같은 메시지가 두 번째 소비자에게 다시 보이지 않도록 충분한 여유를 두는 값이다.
이 값을 짧게 잡으면 ESM 이 backoff 중인 동안 메시지가 다시 visible 상태로 돌아오고, 다른 폴러나 다른 ESM 이 같은 메시지를 또 받아 중복 처리가 늘어난다. 반대로 너무 길게 잡으면 실패 메시지가 큐에 묶여 있는 동안 재시도 시각이 늦어지고, DLQ 이동까지 시간도 같이 늦어진다. visibility timeout 은 SQS 쪽 attribute 라 ESM 설정과 따로 관리해야 한다는 점도 운영에서 잊기 쉬운 부분이다.
같은 계정 같은 리전이 공식 경로다

ESM 이 공식적으로 지원하는 조합은 같은 계정 그리고 같은 리전 SQS 큐다. 서울 리전(ap-northeast-2) 함수에 다른 계정 큐를 붙이려면 큐 정책에 함수의 실행 역할 ARN 을 명시해야 ESM 이 폴링을 시작할 수 있다. 큐 정책은 SQS 쪽이고 실행 역할은 IAM 쪽이라, 두 서비스 권한 모델을 같이 만져야 한다. cross-account 가 잘 안 되는 경우 가장 자주 막히는 단계도 이 정책 매칭이다.
다른 리전 큐를 직접 ESM 으로 붙이는 길은 2026-05 기준 공식 지원에 들어 있지 않다. 다른 리전 큐를 처리해야 한다면 같은 리전에 작은 Lambda 를 하나 두고 SDK 로 cross-region SQS 를 직접 호출하거나, EventBridge Pipes 같은 다른 다리를 거치는 방식이 일반적이다. cross-region 직결처럼 보이는 설정을 콘솔이 허용한다 해도 운영에서 폴링이 도는 경로는 다르다.
권한 자체는 AWS 관리형 정책 AWSLambdaSQSQueueExecutionRole 한 줄로 묶을 수 있다. 이 정책은 sqs:ReceiveMessage, sqs:DeleteMessage, sqs:GetQueueAttributes, sqs:ChangeMessageVisibility 네 가지 권한을 묶어 두었다. 권한 모델 자체 구성은 Lambda 실행 역할 에서 다뤘다.
관리형이 감추는 비용은 기본값 선택이다

ESM 이 대신해 주는 것은 폴링 루프와 backoff 와 동기 호출의 반복이다. BatchSize, MaximumBatchingWindowInSeconds, MaximumConcurrency 세 값은 그대로 운영자 몫으로 남는다.
기본값을 그대로 두면 호출 횟수와 동시 인스턴스 수가 큐 크기와 같이 자란다. 큐가 한가할 때는 한 메시지마다 한 번씩 함수가 도는 비용이 보이고, 큐가 폭주할 때는 동시 인스턴스 수가 1,000 까지 자라며 데이터베이스 커넥션 풀을 모두 차지한다. 두 상황 사이에서 균형을 잡는 결정은 ESM 이 대신해 주지 않는다.
ReportBatchItemFailures 응답 형식, visibility timeout 비례식, cross-account 정책 매칭, 권한 묶음 선택까지 합치면 ESM 한 줄짜리 트리거가 감춰 둔 결정 항목은 적어도 다섯 가지가 된다. SQS 와 Lambda 가 한 화면에서 부드럽게 이어진 모습이라고 해도, 그 아래에서 하나씩 결정을 내리는 사람은 결국 운영자다. 이 다리 위에서 자주 같이 등장하는 SNS 와 SQS 팬아웃은 별도의 주제다.












