학습 목표

시나리오

주문이 들어오면 영수증을 만들고 알림을 보내는 후처리를 합니다. 후처리는 몇 초가 걸리고 가끔 외부 서비스 오류로 실패합니다.

마지막 조건 때문에 표준 큐를 고릅니다. 순서와 중복 제거가 필요했다면 FIFO 큐를 골라야 하고, 처리량 특성이 달라집니다.

아키텍처 개요

표준 큐와 FIFO 큐 비교
항목 표준 큐 FIFO 큐
기본값 속성을 지정하지 않으면 표준 큐 FifoQueue 속성을 켜야 한다
순서 최선 노력 — 보장 없음 메시지 그룹 안에서 엄격한 순서
중복 발생할 수 있다 중복 제거
처리량 거의 무제한 배치 없이 초당 300개, 배치 사용 시 초당 3,000개
유형 변경 생성 후 변경 불가 — 새로 만들어야 한다
이 실습에서 다루는 큐 속성과 기본값
속성 범위 기본값 의미
VisibilityTimeout 0초 – 12시간 30초 수신된 메시지가 다른 소비자에게 보이지 않는 시간
MessageRetentionPeriod 60초 – 14일 4일 처리되지 않은 메시지를 큐에 보관하는 기간
DelaySeconds 0 – 900초 0 메시지가 소비 가능해지기까지의 지연
MaximumMessageSize 1 KiB – 1 MiB 1 MiB 메시지 본문의 최대 크기

구성 단계

1 · 큐와 DLQ 만들기

create-queues.sh — DLQ를 먼저 만들고 주 큐의 재구동 정책에 연결
# DLQ 를 먼저 만듭니다. 주 큐가 이 ARN 을 참조하기 때문입니다.
DLQ_URL=$(aws sqs create-queue \
  --queue-name lab07-orders-dlq \
  --attributes '{"MessageRetentionPeriod":"1209600"}' \
  --query 'QueueUrl' --output text)

DLQ_ARN=$(aws sqs get-queue-attributes \
  --queue-url "$DLQ_URL" \
  --attribute-names QueueArn \
  --query 'Attributes.QueueArn' --output text)

QUEUE_URL=$(aws sqs create-queue \
  --queue-name lab07-orders \
  --attributes "{
      \"VisibilityTimeout\": \"180\",
      \"MessageRetentionPeriod\": \"345600\",
      \"RedrivePolicy\": \"{\\\"deadLetterTargetArn\\\":\\\"$DLQ_ARN\\\",\\\"maxReceiveCount\\\":\\\"3\\\"}\"
    }" \
  --query 'QueueUrl' --output text)

aws sqs get-queue-attributes \
  --queue-url "$QUEUE_URL" \
  --attribute-names All \
  --query 'Attributes.{Visibility:VisibilityTimeout,Retention:MessageRetentionPeriod,Redrive:RedrivePolicy}'

DLQ의 보존 기간은 주 큐보다 길게 잡습니다. 실패한 메시지를 조사할 시간이 필요한데, DLQ의 보존 기간이 짧으면 원인을 찾기 전에 사라집니다.

2 · 처리 함수

index.mjs — 배치 항목 실패를 개별로 보고하는 처리기
export const handler = async (event) => {
  const failures = [];

  for (const record of event.Records) {
    try {
      const body = JSON.parse(record.body);
      if (body.simulateFailure) {
        throw new Error("simulated downstream failure");
      }
      // 실제 후처리를 여기서 수행합니다. 멱등하게 작성합니다.
      console.log("processed", body.orderId);
    } catch (err) {
      console.error("failed", record.messageId, err.message);
      failures.push({ itemIdentifier: record.messageId });
    }
  }

  // 실패한 항목만 다시 큐로 돌려보냅니다.
  return { batchItemFailures: failures };
};
create-function.sh — 함수 생성 (제한 시간은 가시성 제한 시간과 함께 정합니다)
zip -q function.zip index.mjs

aws lambda create-function \
  --function-name lab07-order-worker \
  --runtime nodejs22.x \
  --role arn:aws:iam::123456789012:role/Lab07WorkerRole \
  --handler index.handler \
  --zip-file fileb://function.zip \
  --timeout 30 \
  --memory-size 256

실행 역할에는 로그 권한과 함께 sqs:ReceiveMessage, sqs:DeleteMessage, sqs:GetQueueAttributes 권한이 필요합니다. 이벤트 소스 매핑에서 Lambda 서비스가 함수의 역할로 큐를 폴링하기 때문입니다.

3 · 이벤트 소스 매핑

create-mapping.sh — 큐를 함수의 이벤트 소스로 연결
QUEUE_ARN=$(aws sqs get-queue-attributes \
  --queue-url "$QUEUE_URL" \
  --attribute-names QueueArn \
  --query 'Attributes.QueueArn' --output text)

aws lambda create-event-source-mapping \
  --function-name lab07-order-worker \
  --event-source-arn "$QUEUE_ARN" \
  --batch-size 10 \
  --maximum-batching-window-in-seconds 5 \
  --function-response-types ReportBatchItemFailures

aws lambda list-event-source-mappings \
  --function-name lab07-order-worker \
  --query 'EventSourceMappings[].[UUID,State,BatchSize,EventSourceArn]' \
  --output table

검증 방법

send-and-observe.sh — 정상 메시지와 실패 메시지를 넣고 결과를 관찰
# 정상 처리될 메시지
aws sqs send-message \
  --queue-url "$QUEUE_URL" \
  --message-body '{"orderId":"ord-1001"}'

# 항상 실패하도록 표시한 메시지
aws sqs send-message \
  --queue-url "$QUEUE_URL" \
  --message-body '{"orderId":"ord-9999","simulateFailure":true}'

# 큐에 남은 메시지와 처리 중인 메시지 수
aws sqs get-queue-attributes \
  --queue-url "$QUEUE_URL" \
  --attribute-names ApproximateNumberOfMessages ApproximateNumberOfMessagesNotVisible \
  --query 'Attributes'

# 최대 수신 횟수를 넘긴 메시지는 DLQ 로 넘어갑니다.
aws sqs get-queue-attributes \
  --queue-url "$DLQ_URL" \
  --attribute-names ApproximateNumberOfMessages \
  --query 'Attributes'
관찰 항목과 기대 결과
관찰 기대 결과
정상 메시지 한 번 처리되고 큐에서 사라진다
실패 메시지 가시성 제한 시간이 지날 때마다 재전달되고, 3회를 넘기면 DLQ로 이동한다
배치 부분 실패 같은 배치의 성공 건은 재처리되지 않는다
함수 로그 실패 메시지의 messageId가 반복해서 나타난다
DLQ 실패 메시지 본문이 원본 그대로 남아 있다
inspect-dlq.sh — DLQ 메시지 확인과 재처리
aws sqs receive-message \
  --queue-url "$DLQ_URL" \
  --max-number-of-messages 10 \
  --query 'Messages[].[MessageId,Body]' --output table

# 원인을 고친 뒤에는 DLQ 의 메시지를 원래 큐로 되돌릴 수 있습니다.
aws sqs start-message-move-task \
  --source-arn "$DLQ_ARN"

비용 고려

요금이 발생하는 항목
항목 성격
SQS 요청 송신·수신·삭제 요청 수. 폴링도 요청이다
Lambda 호출과 실행 시간 배치 크기를 키우면 호출 수가 줄어든다
긴 폴링 대기 시간을 두면 빈 응답 요청이 줄어 요청 수가 감소한다
DLQ 보관 메시지가 오래 남을수록 저장 관련 항목이 늘어난다
재시도 실패가 반복되면 호출과 요청이 배로 늘어난다. DLQ가 이 비용을 끊는다

비용과 지연은 배치 크기와 대기 시간에서 맞바꿉니다. 배치를 키우고 대기 시간을 늘리면 호출 수가 줄지만 처리 지연이 커집니다. 지연 요구를 먼저 정하고 그 범위 안에서 배치를 키우는 순서가 맞습니다.

프로덕션 고려사항

정리 — 리소스 삭제

cleanup.sh — 매핑부터 끊고 큐와 함수 삭제
UUID=$(aws lambda list-event-source-mappings \
  --function-name lab07-order-worker \
  --query 'EventSourceMappings[0].UUID' --output text)

aws lambda delete-event-source-mapping --uuid "$UUID"
aws lambda delete-function --function-name lab07-order-worker
aws logs delete-log-group --log-group-name /aws/lambda/lab07-order-worker

aws sqs delete-queue --queue-url "$QUEUE_URL"
aws sqs delete-queue --queue-url "$DLQ_URL"

자주 하는 실수

자주 나오는 실수와 증상
실수 증상 고치는 법
가시성 제한 시간 < 함수 제한 시간 같은 메시지가 여러 번 처리된다 가시성 제한 시간을 충분히 크게 잡는다
DLQ 없이 운영 실패 메시지가 무한히 재시도되며 큐를 막는다 재구동 정책으로 최대 수신 횟수를 정한다
DLQ 보존 기간이 주 큐보다 짧음 조사하기 전에 실패 메시지가 사라진다 DLQ 보존 기간을 길게 잡는다
실행 역할에 큐 권한 없음 매핑이 만들어지지만 메시지가 소비되지 않는다 수신·삭제·속성 조회 권한을 부여한다
배치 전체를 실패 처리 성공한 건까지 중복 처리된다 부분 배치 응답으로 실패 항목만 보고한다
비멱등 처리 중복 전달 시 결제나 알림이 두 번 나간다 처리 이력을 남기고 같은 키를 건너뛴다
큐만 넣고 하류 보호 없음 완충한 부하가 데이터베이스를 그대로 친다 소비자 동시성을 제한한다

이어서 볼 곳

공식 문서 출처