🔥 트리거 종류: API Gateway·SQS·EventBridge·S3
강의 목차

처음에는 답이 명확하지 않았다. 같은 함수를 API Gateway가 부르는 것과 SQS가 부르는 것이 어떻게 다른지 도무지 머릿속에서 가닥을 잡지 못했다. 핸들러 시그니처는 같은데, IAM 정책이 다른 곳에 가고, 재시도가 일어나는 위치도 다르고, 콘솔에서 트리거를 떼면 어디에서 떼는지도 달랐다. 처음 SQS 트리거를 붙이면서 함수 정책에 lambda:InvokeFunction을 추가하려다 한참을 헤맸다. 결국 그 권한은 함수 정책이 아니라 함수의 Execution Role에 가야 했다. 그날 처음으로 누가 InvokeFunction을 부르느냐가 트리거의 가장 먼저 묻는 질문이라는 걸 진지하게 짚었다.
Handler와 실행 모델: 이벤트가 들어오면에서 호출 방식 셋(동기 RequestResponse, 비동기 Event, 폴링 event source mapping)을 한 줄씩 적어 두었다. 그 분류는 호출 결과의 형식을 본 것이다. 응답을 기다리느냐, 즉시 202를 받느냐, batch로 받느냐. 이 글은 그 위에 다른 한 가지 축을 더한다. 누가 InvokeFunction API를 부르느냐, 즉 Push와 Pull 분류다. 두 분류가 같은 곳에서 나뉘지 않는다는 게 처음 헷갈렸던 부분의 답이다.
같은 핸들러, 다른 호출자: Push와 Pull은 누가 부르느냐의 분류
AWS 공식 문서가 트리거를 두 갈래로 구분한다. direct invocation (push) 과 event source mappings (pull) 둘이다. 한 줄로 옮기면 이렇다. Push는 외부 서비스가 Lambda에 Invoke API를 호출한다. Pull은 Lambda가 외부 자원을 polling 한다.
왜 이 분류가 lec0002의 동기/비동기/폴링 분류와 다른지가 처음 헷갈렸던 부분이다. 둘은 같은 그림의 다른 축이다.
- 동기(API Gateway·ALB·Function URL·SDK Invoke) → Push. 호출자가 응답 대기.
- 비동기(S3 EventNotification·SNS·EventBridge 룰·SDK Invoke
InvocationType=Event) → Push. 호출자가 202 받고 끝. - 폴링(SQS·Kinesis Streams·DynamoDB Streams·MSK/Kafka) → Pull. 호출자는 Lambda 자신.
Push 트리거 다섯 갈래는 caller가 Lambda InvokeFunction을 부른다는 점에서 한 묶음이다. 그 caller가 응답을 기다리느냐(동기) 즉시 202를 받느냐(비동기)는 같은 Push 안의 두 분기일 뿐이다. Pull은 그것과 완전히 다른 구조다. 외부 큐·스트림은 가만히 있고, Lambda 서비스 내부의 Event Source Mapping이라는 별도 리소스가 그 큐·스트림을 polling 하다 메시지가 모이면 함수를 invoke 한다.
처음 SQS 트리거를 붙일 때 함수 정책에 lambda:InvokeFunction 권한을 SQS에 주려고 했던 것이 그래서 잘못이었다. SQS는 Lambda를 부르지 않는다. Lambda가 SQS를 읽으러 간다. 그러니까 권한은 반대 방향이다. Lambda의 Execution Role이 sqs:ReceiveMessage · sqs:DeleteMessage · sqs:GetQueueAttributes를 가져야 한다. AWS 관리형 정책 AWSLambdaSQSQueueExecutionRole이 그걸 한 묶음으로 들고 있다. 실행 역할과 최소 권한: Lambda의 IAM에서 정리한 두 게이트, 즉 Resource-based(함수 정책) vs Identity-based(Execution Role)가 트리거 방식마다 어느 쪽이 진짜 게이트가 되느냐를 결정한다.
Event Source Mapping이라는 별도 리소스
Pull 트리거를 붙이면 Lambda 안에 별도 리소스가 하나 더 등장한다. 콘솔에서 SQS 트리거를 추가하면 화면에 안 보이는 한 줄이 같이 따라온다. CLI로 보면 정체가 더 또렷하다.
$ aws lambda list-event-source-mappings --function-name OrderProcessor
{
"EventSourceMappings": [
{
"UUID": "a1b2c3d4-...",
"EventSourceArn": "arn:aws:sqs:ap-northeast-2:111122223333:orders",
"FunctionArn": "arn:aws:lambda:ap-northeast-2:...:function:OrderProcessor",
"BatchSize": 10,
"MaximumBatchingWindowInSeconds": 0,
"State": "Enabled",
"StateTransitionReason": "USER_INITIATED",
"FunctionResponseTypes": ["ReportBatchItemFailures"]
}
]
}$ aws lambda list-event-source-mappings --function-name OrderProcessor
{
"EventSourceMappings": [
{
"UUID": "a1b2c3d4-...",
"EventSourceArn": "arn:aws:sqs:ap-northeast-2:111122223333:orders",
"FunctionArn": "arn:aws:lambda:ap-northeast-2:...:function:OrderProcessor",
"BatchSize": 10,
"MaximumBatchingWindowInSeconds": 0,
"State": "Enabled",
"StateTransitionReason": "USER_INITIATED",
"FunctionResponseTypes": ["ReportBatchItemFailures"]
}
]
}Event Source Mapping(이하 ESM)은 그 자체가 별도 AWS 리소스다. UUID(주 식별자)와 EventSourceMappingArn 둘 다 갖는다. EventSourceArn은 polling 대상(큐·스트림 ARN), FunctionArn은 invoke 대상이다. State는 일곱 값 중 하나를 거친다. Creating → Enabling → Enabled → Disabling → Disabled → Updating → Deleting 순서다. 운영 사고가 났을 때 가장 먼저 손이 가는 곳이 이 State다. aws lambda update-event-source-mapping --uuid <UUID> --no-enabled 한 줄로 ESM을 disable 한다. 다만 polling 정지가 즉시 일어나지는 않는다는 docs 단서가 있다. disable 명령 후 polling이 실제로 멈추는 데 분 단위 지연이 있을 수 있다(스트림 source는 eventual consistency). 함수를 안 죽이고 incoming 트래픽만 끊는 길이지만 순간 차단은 아니라는 점을 같이 든다. 처음 운영 폭주 사고 때 Reserved=0을 떠올렸는데, Pull 트리거에 한해서는 ESM disable이 더 자연스러운 대응이라는 걸 그 다음에 알았다.
BatchSize는 한 번 invoke에 묶을 메시지·레코드 수다. 기본값과 최대값이 source마다 다르다.
- SQS Standard: 기본 10, 최대 10,000 (10 초과 시
MaximumBatchingWindowInSeconds ≥ 1필요) - SQS FIFO: 기본 10, 최대 10
- Kinesis Data Streams: 기본 100, 최대 10,000
- DynamoDB Streams: 기본 100, 최대 10,000
이 숫자가 운영에서 무엇을 가르냐면, 함수의 idempotency 단위가 바뀐다. BatchSize 10이면 한 invoke에 메시지 10개가 들어오고, 그 중 한 개가 실패했을 때 어떻게 신호하느냐가 다음 절의 주제다.

트리거별 재시도 위치는 다 다른 곳에 있다
여기가 가장 자주 혼동하는 부분이다. "Lambda가 알아서 재시도한다"라는 한 줄이 어떤 트리거에서는 거짓말이고 어떤 트리거에서는 절반만 진실이다. 재시도가 어디서 일어나는지를 트리거마다 따로 정리해 둬야 운영에서 어디를 살펴야 할지 알 수 있다.
- API Gateway (Push 동기): Lambda는 한 번 부르고 끝. 함수가 에러를 던지면 API Gateway가 그 에러를 사용자에게 그대로 돌려준다. 재시도는 caller(브라우저·앱) 책임이다. Lambda 안에는 retry가 없다.
- S3 EventNotification (Push 비동기): 호출자(S3)가 Lambda를 비동기로 invoke 한다. Lambda 서비스 내부의 큐가 메시지를 안고 있고, 함수가 에러를 던지면 기본 두 번 재시도한다. 1분, 다시 2분 간격. 그래도 실패하면 6시간(
MaximumEventAgeInSeconds기본값) 안에 폐기 또는 DLQ/on-failure destination으로 보낸다. 재시도는 Lambda가 한다. - EventBridge 룰 (Push 비동기): 호출자(EventBridge)가 Lambda를 비동기로 invoke. 위 S3와 같은 Lambda 내부 retry가 일어난다. 다만 EventBridge 자체가 한 단계 더 있다. 룰의 target invoke가 실패하면 EventBridge가 24시간 안에 자체적으로 retry 한다. 두 곳에서 retry가 돈다. EventBridge → Lambda → 함수 핸들러 세 단계 중 어디서 깨졌는지 로그로 가르려면 EventBridge의 DLQ를 따로 깔아 둔다.
- SQS (Pull/ESM): retry가 두 곳에 분산돼 있다. ESM이 batch를 invoke 했고 함수가 에러를 출력하면 Lambda가 자체 backoff로 다시 시도하지만, 처리되지 않은 메시지는 큐의 visibility timeout이 끝나면 다시 떠올라 SQS의 redrive policy(
maxReceiveCount)가 카운트를 센다. 카운트 한도에 닿으면 SQS가 메시지를 DLQ로 보낸다. 즉 Lambda·SQS 둘 다 retry 메커니즘을 갖고 있고, 둘이 협업한다. - Kinesis Data Streams (Pull/ESM): 같은 Pull이지만 큐와 달리 순서가 있다. shard별로 메시지가 ordered 라서 한 batch가 실패하면 ESM이 default로 그 batch를 record expire까지 무한 재시도한다. 손잡이 셋, 즉
MaximumRetryAttempts(-1이 기본 = 무한, 명시값은0~10,000),MaximumRecordAgeInSeconds(-1이 기본 = 무한, 명시값은60~604800초 = 7일),BisectBatchOnFunctionError(true 시 batch를 반으로 쪼개 다시 시도),OnFailuredestination을 같이 깔아 두지 않으면 한 메시지가 shard 하나를 영원히 막는다. poison pill 사고가 자주 새는 길이다.
같은 "재시도"라는 단어가 트리거마다 다섯 곳에 흩어져 있다는 점이 처음 봤을 때 머릿속을 복잡하게 했다. 운영에 들어가면 머리가 더 복잡하다. 같은 함수가 두 트리거에 붙어 있을 때 한 메시지가 한 트리거 측에서 2회, 다른 측에서 무한 재시도하는 구조가 따라온다. 그것이 "Lambda가 알아서 한다"는 한 줄이 가린 비용이다.
부분 batch 실패 신호. ReportBatchItemFailures라는 옵션을 ESM에 켜 두면 함수가 batch 안에서 어떤 메시지만 실패했는지 식별자(itemIdentifier)로 돌려줄 수 있다. 안 켜면 한 메시지 실패에 batch 10개가 전부 큐로 돌아간다. 즉 9개의 성공한 메시지가 또다시 처리 대상이 된다. 운영 첫 일주일에 가장 많이 깨지는 부분이다. 한 번 처리한 메시지를 두 번 처리하는 패턴이 자라난다. idempotency가 안 짜인 핸들러에서 청구서가 두 번 나가는 사고가 발생한다.

한 함수에 여러 트리거가 붙으면: IAM 게이트가 트리거마다 다르다
한 함수에 동기 트리거 둘과 폴링 트리거 한 개를 붙인 적이 있다. API Gateway, EventBridge 룰, SQS. 권한 설정이 세 곳으로 흩어져 있어서 콘솔이 한 화면에 다 보여 주지 않는다. 그 셋을 같이 본 그날 비로소 실행 역할과 최소 권한: Lambda의 IAM에서 정리한 두 게이트가 트리거마다 어디로 가는지 한눈에 들어왔다.
- Push 트리거: 함수 정책(Resource-based) statement 한 줄. API Gateway·S3·SNS·EventBridge 각각이
lambda:InvokeFunction을 부를 수 있도록 함수 정책에 statement를 추가한다. 콘솔에서 트리거를 붙이면 자동으로 들어가지만, IaC(CDK·Terraform)에서는 손으로 명시한다.aws:SourceArn·aws:SourceAccount조건키로 confused deputy 막는 statement도 같은 함수 정책 안에 들어간다. - Pull 트리거: Execution Role 권한. SQS면
sqs:ReceiveMessage·DeleteMessage·GetQueueAttributes. Kinesis면kinesis:DescribeStream·DescribeStreamSummary·GetRecords·GetShardIterator·ListShards·SubscribeToShard. 함수 정책에는 한 줄도 안 들어간다.
게이트 위치가 다른 게 보안 점검에서도 결정 변수다. 함수 정책 audit 한 번으로는 Pull 트리거를 못 잡는다. ESM 리스트와 Execution Role을 따로 살펴야 한다. 운영 1년차에 가장 많이 놓치는 부분이다.

트리거 결정: 마음속에 두는 한 갈래
Lambda가 받는 트리거 카탈로그는 점점 자란다. 다 외울 필요는 없다. 새 함수를 만들 때 마음속에 두는 갈래는 셋이다.
- 사용자 응답에 노출되는가. 노출되면 Push 동기(API Gateway·ALB·Function URL). 콜드스타트가 SLA에 그대로 영향을 준다는 한 줄을 같이 든다.
- 이벤트가 들어오는데 응답을 기다릴 필요가 없는가. 그러면 Push 비동기(S3·SNS·EventBridge). retry는 Lambda가 안고, 동시성이 폭주할 함수에는 reserved concurrency로 격벽을 친다.
- 큐·스트림에 메시지가 쌓이고 있는가. 그러면 Pull/ESM(SQS·Kinesis·DynamoDB Streams). retry 위치가 큐·스트림 쪽이고, IAM은 Execution Role 쪽.
ReportBatchItemFailures켜고 visibility timeout은 함수 timeout × 6 + batch window.
이게 모든 케이스를 커버하지는 않는다. EventBridge Pipes(2022-12 GA)는 source가 큐·스트림인데 enrichment + filter + transform을 한 단계 끼워 넣어서, 같은 SQS를 source로 두더라도 Lambda가 ESM으로 받느냐 Pipes로 받느냐에 따라 처리 흐름이 다르다. MSK·Self-managed Kafka도 ESM이지만 partition·consumer group 추상이 따로 들어온다.
운영 첫 한 달에 가장 자주 PagerDuty 알림을 부르는 함수는 Pull 트리거에 ReportBatchItemFailures 안 켠 함수와 EventBridge 룰 + Lambda 비동기 retry를 둘 다 활성화해 둔 함수다. 그 두 갈래만 처음 함수 만들 때 챙겨 두면 첫 달에 잘 마주칠 함정 절반은 지나간다. 그래서 트리거를 붙일 때 동기/비동기/폴링 셋이 아니라 Push/Pull 둘로 먼저 묻는 습관이 그 다음 모든 결정을 한 번에 정리해 준다.
참고 자료
- Creating event-driven architectures with Lambda (AWS Lambda Developer Guide): push vs pull 모델 공식 정의
- How Lambda processes records from stream and queue-based event sources: ESM polling, BatchSize, Reporter 옵션
- Invoking Lambda with events from other AWS services: push 트리거 카탈로그
- CreateEventSourceMapping API: UUID·State 정의, BatchSize 기본/최대값
- Understanding retry behavior in Lambda: 비동기 invocation 재시도 정책 일반
- How Lambda handles errors and retries with asynchronous invocation:
MaximumRetryAttempts0~2, 1·2분 간격, 6h MaximumEventAge - Handling errors for an SQS event source in Lambda:
ReportBatchItemFailures, visibility timeout, DLQ 운영 - Best practices for implementing partial batch responses: 부분 batch 실패 신호 디테일










