CertNote/ 출퇴근 학습 노트
로드맵
KOEN
CLF-C02 · FoundationalCloud Practitioner - Foundational
DVA-C02 · AssociateDeveloper - Associate
  • Week 1
    • 1.개발자 시점에서 본 AWS: 리전, 인프라, 그리고 책임의 경계
    • 2.IAM의 4대 엔터티: User, Group, Role, Policy
    • 3.STS와 정책 조건: 임시 자격증명의 깊은 세계
    • 4.AWS CLI와 SDK: 자격증명 체이닝부터 SigV4까지
    • 5.Week 1 종합: 인프라와 IAM이 만드는 신뢰의 사슬
  • Week 2
  • Week 3
  • Week 4
  • Week 5
  • Week 6
  • Week 7
  • Week 8
  • Week 9
  • Week 10
  • Week 11
    • 1.SQS: 큐 하나가 어떻게 시스템을 떼어놓는가
    • 2.SNS: 한 번 발행하면 모두가 듣는다
    • 3.Kinesis: 흐르는 데이터를 붙잡아두는 로그
    • 4.Step Functions와 AppSync: 흐름을 코드 밖으로 꺼내다
    • 5.Week 11 종합: 메시징의 모델을 가르는 질문들
  • Week 12
  • Week 13
SAA-C03 · AssociateSolutions Architect - Associate
SOA-C02 · AssociateCloudOps Engineer - Associate
SAP-C02 · ProfessionalSolutions Architect - Professional
DOP-C02 · ProfessionalDevOps Engineer - Professional
SCS-C03 · SpecialtySecurity - Specialty
MLA-C01 · AssociateMachine Learning Engineer - Associate
AIF-C01 · FoundationalAI Practitioner - Foundational
DEA-C01 · AssociateData Engineer - Associate
MLS-C01 · SpecialtyMachine Learning - Specialty
합격 후기

© CertNote · 주간랩스

  • 소개
  • 수정 기록
  • 문의
  • 이용약관
  • 개인정보처리방침
← DVA-C02/Week 11/Day 1

기준: DVA-C02 시험 가이드·오류 제보

DVA-C02· AssociateWeek 11 · Day 1읽기 약 18분

Day 1 - SQS: 큐 하나가 어떻게 시스템을 떼어놓는가

분산 시스템을 처음 설계할 때 가장 빨리 부딪히는 벽은 "두 컴포넌트가 같은 속도로 일하지 않는다"는 사실이다. 주문을 받는 웹 서버는 초당 수천 건을 받아낼 수 있지만, 그 주문으로 결제를 거는 처리기는 초당 수십 건밖에 못 한다. 둘을 직접 연결하면 빠른 쪽이 느린 쪽을 압사시킨다. 메시지 큐(message queue)는 이 둘 사이에 완충재를 끼워 넣어 "보내는 쪽"과 "받는 쪽"을 시간적으로 떼어놓는다(decoupling). Amazon SQS는 이 오래된 아이디어를 "큐를 직접 운영하지 않아도 되는 완전 관리형 서비스"로 만든 것이고, AWS의 첫 공개 서비스라는 역사적 무게까지 지고 있다.

DVA-C02에서 SQS는 메시징 섹션의 출발점이다. 단독 문제(가시성 타임아웃, FIFO 처리량 같은 숫자)도 있지만, "Lambda가 메시지를 중복 처리하는 이유", "처리 실패 메시지를 어디로 보내나", "큐가 비었을 때 비용이 새는 이유" 같은 동작 원리 시나리오로 묻기 쉽다. 이번 글은 SQS가 왜 "최소 1회 전달"이라는 불편한 보장을 택했는지, 가시성 타임아웃이라는 임시 잠금이 어떻게 분산 합의 없이 중복을 줄이는지, 표준 큐와 FIFO 큐가 갈라지는 근본 이유가 무엇인지를 다룬다.

SQS가 출발한 자리: AWS의 첫 서비스

SQS는 2006년에 정식 출시됐다. S3(2006년 3월)와 함께 AWS의 가장 오래된 서비스 중 하나이고, 베타는 2004년부터 돌고 있었다. 이는 우연이 아니다. 아마존이 2000년대 초 거대한 단일 모놀리식 시스템을 수백 개 서비스로 쪼개는 과정(이른바 "API 의무화" 시기)에서 서비스들끼리 안전하게 통신할 비동기 채널이 절실했다. 그 내부 도구가 외부 상품이 된 것이 SQS다. 그래서 SQS의 설계 철학에는 "서비스 지향 아키텍처(SOA)에서 컴포넌트를 떼어놓는다"는 목적이 깊이 박혀 있다.

큐를 끼우면 좋은 점은 세 가지다.

  • 버퍼링: 트래픽이 순간 폭증해도 큐가 메시지를 받아 두고 소비자는 자기 속도로 빼간다.
  • 장애 격리: 소비자가 잠깐 죽어도 메시지는 큐에 남아 있다가 소비자가 살아나면 처리된다. 생산자는 소비자의 생사를 알 필요가 없다.
  • 독립 확장: 큐 길이가 늘면 소비자를 더 띄우고, 줄면 줄인다. SQS 큐 깊이 기반 Auto Scaling의 근거다.

SQS가 구현하는 것은 메시징 패턴 중 포인트-투-포인트(point-to-point) 모델이다. 한 메시지는 정확히 하나의 소비자가 가져간다. 한 메시지를 여러 구독자가 동시에 받는 발행-구독(pub/sub) 모델(SNS, Day 2)과 대조된다. 엔터프라이즈 통합 패턴(Gregor Hohpe의 Enterprise Integration Patterns, 2003)에서 정리한 이 두 모델은 메시징 미들웨어의 양대 축이다. JMS(Java Message Service)의 Queue 대 Topic, AMQP의 direct 대 fanout exchange가 모두 같은 구분이다. SQS=큐=P2P, SNS=토픽=Pub/Sub로 묶어 두면 둘의 조합(팬아웃)도 자연스럽게 이해된다.

SQS는 메시지를 여러 가용 영역(AZ)의 여러 서버에 중복 저장한다. 이것이 내구성을 주지만 동시에 SQS의 대표 특성인 "순서 미보장"과 "중복 가능"의 근본 원인이다.

  • 메시지가 여러 서버에 흩어져 있고 소비자가 ReceiveMessage를 호출하면, SQS는 그 서버들 중 일부를 샘플링해서 가진 메시지를 돌려준다.
  • 모든 서버를 매번 다 뒤지면 분산 시스템의 확장성이 죽는다. 그래서 SQS는 의도적으로 "전체 순서를 보장하지 않는 대신 무한히 확장된다"를 택했다.
  • 표준 큐의 "불편한" 특성은 모두 이 "무제한 처리량"이라는 목표의 대가다.

왜 "최소 1회 전달"인가: 분산 시스템의 근본 트레이드오프

SQS 표준 큐가 "최소 1회 전달(at-least-once delivery)"을 보장한다는 문장은 시험에서 접하기 쉽지만, 왜 "정확히 1회"가 아닌지는 놓치기 쉽다. 이는 SQS의 한계가 아니라 분산 메시징의 근본 정리다.

소비자가 메시지를 받아 처리하고 DeleteMessage를 호출하는 흐름은 다음과 같다. 소비자가 처리를 끝내고 삭제 요청을 보냈는데 그 삭제 ACK가 네트워크에서 사라졌다고 가정한다. 큐는 "이 메시지가 삭제됐나?"를 모른다.

  • 큐가 "삭제됐겠지" 하고 지우면, 실제로 소비자가 처리 도중 죽었을 때 메시지가 영영 사라진다(at-most-once, 유실 위험).
  • 큐가 "확실하지 않으니 다시 보낸다"를 택하면 메시지는 절대 잃지 않지만 중복될 수 있다(at-least-once).

"정확히 1회"는 생산자-네트워크-소비자 전 구간에서 양쪽이 완벽히 합의해야 가능하다. 네트워크가 언제든 패킷을 잃는 현실에서는 그 비용이 천문학적이다. 그래서 대부분의 실용 메시징 시스템은 "절대 잃지 않는" at-least-once를 택하고, 중복 처리는 소비자가 멱등성(idempotency)으로 흡수하게 맡긴다.

여기서 나오는 실무 규칙이 "소비자를 멱등하게 짜라"다. 같은 메시지가 두 번 와도 결과가 같도록 설계한다는 뜻이다. 예를 들어 "주문 O001 결제"를 처리할 때 이미 처리된 주문이면 무시한다. SQS FIFO 큐는 5분 윈도우 안에서 중복을 제거해 주지만(exactly-once processing), 5분을 넘는 중복이나 소비자 측 재처리까지는 막지 못한다.

"정확히 1회"가 사실상 불가능하다는 것은 분산 시스템 이론의 오래된 결론이다. 두 장군 문제(Two Generals' Problem)는 신뢰할 수 없는 채널 위에서 두 노드가 어떤 행동에 대해 확실히 합의하는 것이 불가능함을 보여 준다. 메시지 전달도 같은 구조라, "잃지 않되 중복 가능"(at-least-once)과 "중복 없되 유실 가능"(at-most-once) 중 하나를 골라야 한다. Kafka가 "exactly-once semantics"를 내세우지만, 그것도 트랜잭션과 멱등 프로듀서를 결합해 "처리 결과가 정확히 1회처럼 보이게" 만드는 것이지 전달 자체가 1회가 되는 것은 아니다. SQS의 at-least-once는 정직한 선택이다.

⚠️ 함정: 시험에서 "표준 큐가 메시지를 중복 전달했다, 어떻게 해결하나?"의 정답은 대개 "소비자를 멱등하게 설계" 또는 "FIFO 큐로 전환"이다. "재시도를 끈다", "가시성 타임아웃을 0으로 한다" 같은 선택지는 오답이다. 중복은 SQS 표준의 설계상 특성이지 버그가 아니라서 끄는 옵션이 없다.

가시성 타임아웃: 분산 잠금 없이 중복을 줄이는 법

여러 소비자가 같은 큐에서 동시에 메시지를 빼간다면, 두 소비자가 같은 메시지를 동시에 받아 둘 다 처리해 버리는 문제가 생긴다. 데이터베이스라면 행 잠금(row lock)을 걸겠지만, SQS는 초당 수만 건이 흐르는 환경에서 모든 메시지에 분산 잠금을 거는 비용을 감당할 수 없다. 대신 SQS는 가시성 타임아웃(Visibility Timeout) 이라는 가볍고 영리한 메커니즘을 쓴다.

소비자가 메시지를 ReceiveMessage로 받는 순간, 그 메시지는 큐에서 사라지지 않고 일정 시간 동안 다른 소비자에게 안 보이게 가려진다(기본 30초). 이 시간 안에 소비자가 처리를 끝내고 DeleteMessage를 호출하면 메시지는 영구 삭제된다. 시간이 지나도록 삭제 호출이 오지 않으면("소비자가 죽었나 보다"), SQS는 메시지를 다시 보이게 만들어 다른 소비자가 처리할 수 있게 한다.

import boto3
 
sqs = boto3.client('sqs')
 
# 메시지 수신 — 받는 순간 가시성 타임아웃 시작
messages = sqs.receive_message(
    QueueUrl=queue_url,
    MaxNumberOfMessages=10,
    WaitTimeSeconds=20,        # 롱 폴링: 메시지 없으면 최대 20초 대기
    VisibilityTimeout=60       # 이 메시지들을 60초간 가린다
)
 
for message in messages.get('Messages', []):
    process(message['Body'])   # 처리에 60초 넘게 걸리면 → 중복 위험
    sqs.delete_message(        # 처리 끝나야 삭제
        QueueUrl=queue_url,
        ReceiptHandle=message['ReceiptHandle']
    )

대표적인 운영 사고는 "처리 시간이 가시성 타임아웃보다 길 때"다. 메시지를 받아 60초짜리 작업을 시작했는데 가시성 타임아웃이 30초라면, 30초가 지난 시점에 메시지가 다시 보이게 되어 두 번째 소비자가 같은 메시지를 또 처리한다. 첫 소비자는 한참 뒤에 DeleteMessage를 호출하지만 그때는 이미 두 번 처리된 뒤다. 그래서 "처리 시간 + 여유 > 가시성 타임아웃"이 철칙이고, 처리가 길어질 것 같으면 중간에 ChangeMessageVisibility로 잠금을 연장한다.

가시성 타임아웃은 분산 시스템의 임대(lease) 패턴이다. "이 자원을 N초 동안 빌려줄게, 그 안에 끝내고 반납(삭제)하거나, 못 끝내면 연장 요청을 해. 아무 말 없으면 임대가 만료되어 다른 사람에게 넘어간다."

  • 장점: 소비자가 죽어도 시스템이 알아서 복구된다(데드락 없음).
  • 단점: 임대 기간 설정이 까다롭다. 너무 짧으면 정상 처리 중에 중복이 나고, 너무 길면 소비자가 죽었을 때 그 메시지가 임대 만료까지 묶여 처리가 지연된다.
  • 분산 락 매니저, 쿠버네티스 리더 선출, DHCP 임대가 모두 같은 원리다.

Lambda가 SQS를 소비할 때(이벤트 소스 매핑), AWS는 가시성 타임아웃을 Lambda 함수 타임아웃의 최소 6배로 설정하라고 권고한다. Lambda 이벤트 소스 매핑은 배치로 메시지를 가져오고, 배치 안 일부가 실패하면 재시도가 일어나며, 거기에 폴러의 내부 지연까지 겹친다. 함수 타임아웃이 30초인데 가시성 타임아웃도 30초면, 배치 처리가 끝나기 전에 메시지가 다시 보여 중복 호출이 늘어날 수 있다. "6배" 규칙은 이 여러 지연을 흡수하기 위한 안전 마진이다.

표준 큐 대 FIFO 큐: 순서를 보장하는 비용

표준 큐와 FIFO 큐의 차이는 단순 암기 항목처럼 보이지만, 그 뒤에는 "순서를 보장하려면 무엇을 포기해야 하는가"라는 분산 시스템의 근본 트레이드오프가 있다.

특성표준 큐FIFO 큐
순서보장 없음(best-effort)그룹 내 선입선출 보장
중복가능(at-least-once)5분 윈도우 내 제거
처리량무제한300 msg/s, 배치 시 3,000, 고처리량 모드 70,000+
가격저렴약간 비쌈
이름 규칙자유.fifo 접미사 필수
용도높은 처리량, 디커플링금융 거래, 순서 필수

표준 큐가 무제한 처리량을 내는 비결은 앞서 본 "메시지를 여러 서버에 흩뿌리고 샘플링"이다. 순서를 보장하려면 메시지들을 한 줄로 세워야 하고, 한 줄로 세운다는 것은 그 줄을 관리하는 단일 지점이 생긴다는 뜻이며, 단일 지점은 곧 처리량의 천장이다. 그래서 FIFO 큐는 300 msg/s라는 한도를 가진다.

AWS는 이 한도를 메시지 그룹 ID(MessageGroupId) 로 우회한다. 같은 그룹 ID끼리만 순서를 보장하고, 다른 그룹은 병렬로 처리한다. 고객별로 그룹 ID를 나누면 고객 A의 주문은 A 안에서만 순서가 맞으면 되므로 고객 수만큼 병렬화가 가능해진다(고처리량 모드의 원리).

# FIFO 큐: 그룹 단위 순서 + 중복 제거
sqs.send_message(
    QueueUrl='https://sqs.../orders.fifo',
    MessageBody='{"orderId": "O001"}',
    MessageGroupId='customer-123',         # 이 고객의 메시지끼리만 순서 보장
    MessageDeduplicationId='order-O001'    # 5분 내 같은 ID는 중복 제거
)

FIFO의 중복 제거는 MessageDeduplicationId라는 멱등성 키로 작동한다. 5분 안에 같은 dedup ID를 가진 메시지가 또 오면 조용히 버린다. ContentBasedDeduplication을 켜면 메시지 본문의 SHA-256 해시를 자동으로 dedup ID로 쓴다. 생산자가 네트워크 오류로 같은 메시지를 두 번 보내도 큐가 한 번만 받는다. 다만 5분 윈도우라는 한계가 중요하다. 6분 간격으로 같은 메시지를 보내면 둘 다 들어간다. 시험에서 "FIFO인데 왜 중복이 생기나?"의 답이 이 윈도우 초과인 경우가 있다.

⚠️ 함정: "초당 5,000건의 주문을 순서대로 처리"라는 시나리오에서 단순 FIFO(300/s)를 고르면 처리량이 부족하다. 정답은 "고처리량 FIFO(High-Throughput FIFO) + 메시지 그룹 ID를 잘게 나눔"이거나, 순서가 그룹 단위로만 필요하다면 "그룹별 병렬화"다. "전역 엄격 순서"가 정말 필요하면 단일 그룹을 쓸 수밖에 없고, 그때는 300/s가 천장이라는 점을 받아들여야 한다.

실패한 메시지는 어디로 가는가: DLQ와 재구동

처리에 계속 실패하는 메시지("독이 든 메시지", poison pill)를 그냥 두면, 그 메시지가 큐 맨 앞에서 무한 재시도되며 뒤의 정상 메시지들을 막거나 소비자를 계속 죽인다. 데드 레터 큐(Dead Letter Queue, DLQ) 는 이런 메시지를 격리하는 별도 큐다.

sqs.set_queue_attributes(
    QueueUrl='https://sqs.../main-queue',
    Attributes={
        'RedrivePolicy': json.dumps({
            "deadLetterTargetArn": "arn:aws:sqs:...:my-dlq",
            "maxReceiveCount": "3"   # 3번 받았는데도 삭제 안 되면 DLQ로
        })
    }
)

설정의 중심은 maxReceiveCount다. 메시지가 소비자에게 ReceiveMessage로 전달된 횟수(가시성 타임아웃 만료로 재출현한 횟수)가 이 값을 넘으면, SQS가 그 메시지를 DLQ로 옮긴다. DLQ에 쌓인 메시지에 CloudWatch 알람을 걸어 운영자에게 알리고, 원인을 고친 뒤 재구동(redrive) 으로 원본 큐에 되돌려 재처리한다.

DLQ에는 두 가지 제약이 있다.

  • DLQ는 반드시 원본 큐와 같은 유형이어야 한다(표준→표준, FIFO→FIFO). 메시지 속성 호환성 때문이다. FIFO 메시지는 MessageGroupId/DeduplicationId를 갖는데 표준 DLQ는 이를 처리할 메커니즘이 없다.
  • DLQ에 들어간 메시지의 보존 시계는 원본 큐에 처음 들어온 시각 기준으로 계속 흐른다(DLQ 도착 시각으로 리셋되지 않는다). 원본 큐 보존이 4일이고 메시지가 3일째 DLQ로 갔다면 DLQ에서는 하루만 더 살아남는다. 이를 모르면 "DLQ에서 분석하려 했는데 메시지가 사라졌다"는 일이 생긴다.

메시지 크기 한도도 걸림돌이 된다. SQS 메시지 최대 크기는 2025년 8월 256KiB에서 1MiB로 늘었다(표준·FIFO 모두). 그보다 큰 페이로드(이미지, 대용량 JSON)를 큐에 직접 넣을 수 없을 때 표준 패턴은 SQS Extended Client Library다. 실제 데이터는 S3에 저장하고 큐에는 S3 객체 참조만 넣어 최대 2GB까지 우회한다. 메시징 시스템의 일반적 패턴인 클레임 체크(Claim Check) 패턴(큰 데이터는 별도 저장소에 두고 메시지에는 영수증만 전달)의 구현이다. 시험에서 "메시지 크기 한도를 초과하는 메시지"가 보이면 정답 후보는 Extended Client(또는 S3 참조)다. 오래된 자료에는 이전 한도인 256KB로 적혀 있다.

빈 큐에서 비용이 새는 이유: 폴링 모델

SQS는 소비자가 능동적으로 메시지를 가지러 가는 풀(pull) 기반이다. 그래서 "큐가 비었을 때 어떻게 행동하느냐"가 비용과 직결된다.

짧은 폴링(Short Polling) 은 ReceiveMessage 호출이 즉시 응답한다. 메시지가 있으면 주고, 없으면 빈 응답을 바로 돌려준다. 소비자가 메시지를 기다리느라 이 호출을 끊임없이 반복하면, 큐가 비어 있는 동안에도 빈 응답을 받는 API 호출이 계속 과금된다. 롱 폴링(Long Polling) 은 WaitTimeSeconds(최대 20초)를 줘서 메시지가 도착할 때까지(또는 20초까지) 한 번의 호출이 기다리게 한다. 빈 응답이 거의 사라지고 API 호출 수가 급감해 비용이 줄며, 메시지 도착 지연도 짧아진다.

항목Short PollingLong Polling
WaitTimeSeconds0 (기본)1~20초
빈 응답잦음거의 없음
API 비용높음낮음
권장아니오예

롱 폴링은 웹에서 오래 쓰인 롱 폴링 패턴(서버가 응답을 보낼 게 생길 때까지 HTTP 연결을 잡아두는 기법)을 큐에 적용한 것이다. 폴링의 두 비효율, 곧 "너무 자주 물으면 낭비, 너무 드물게 물으면 지연"을 "응답이 생길 때까지 한 번의 요청을 잡아둔다"로 동시에 푼다. 실무에서는 큐 속성에 ReceiveMessageWaitTimeSeconds=20을 설정해 그 큐의 모든 수신을 기본 롱 폴링으로 만드는 것이 권장 패턴이다. 대부분의 경우 롱 폴링이 적합하다.

정리

SQS를 관통하는 한 문장은 "큐를 끼워 보내는 쪽과 받는 쪽을 시간적으로 떼어놓고, 그 대가로 순서와 정확히-1회를 양보한다"이다. AWS의 첫 서비스답게 SQS의 모든 특성은 분산 시스템의 근본 트레이드오프에서 직접 흘러나온다.

  • 여러 AZ 중복 저장에서 순서 미보장과 중복이 나온다.
  • at-least-once는 정직한 선택이다.
  • 가시성 타임아웃은 임대 기반 중복 방지 장치다.
  • FIFO는 처리량을 양보하고 순서 보장을 얻는다.

DLQ는 독이 든 메시지를 격리하고, Extended Client는 클레임 체크로 크기 한도를 우회하며, 롱 폴링은 빈 큐의 비용 누수를 막는다. 시험 함정은 대개 이 파이프라인 위 어느 한 지점의 "왜 그렇게 설계됐나"를 묻는다.

다음 글에서는 한 메시지를 정확히 한 소비자가 가져가는 SQS와 정반대로, 한 메시지를 모든 구독자에게 동시에 밀어내는 SNS의 발행-구독 모델로 넘어간다.

📝 연습 문제

선택지를 클릭하면 정답·해설이 펼쳐집니다.

문제 1

SQS 표준 큐가 같은 메시지를 두 번 전달했다. 가장 적절한 대응은?

문제 2

Lambda가 SQS 큐를 소비할 때 같은 메시지가 반복 처리된다. 함수 타임아웃은 30초다. 가장 적절한 조치는?

문제 3

초당 약 5,000건의 주문을 고객별로 순서를 보장하며 처리해야 한다. 어떤 구성이 적절한가?

문제 4

SQS 메시지가 처리에 3번 실패하면 격리하여 나중에 분석하려 한다. 올바른 설정은?

문제 5

처리해야 할 메시지 페이로드가 1.5MB로 SQS 한도를 초과한다. 가장 적절한 방법은?

문제 6

SQS 소비자가 큐가 자주 비어 있는데도 API 비용이 높게 나온다. 원인과 해결로 옳은 것은?

문제 7

SQS의 "최소 1회 전달(at-least-once)" 보장에 대한 설명으로 가장 정확한 것은?

이전Week 10 종합: 모니터링이라는 하나의 이야기Week 10 · Day 5다음 SNS: 한 번 발행하면 모두가 듣는다Week 11 · Day 2

참고 공식 자료

  • DVA-C02 공식 시험 안내(새 탭에서 열림)
  • Amazon SQS 공식 문서(새 탭에서 열림)
  • Amazon S3 공식 문서(새 탭에서 열림)
  • AWS Lambda 공식 문서(새 탭에서 열림)
  • Amazon SNS 공식 문서(새 탭에서 열림)

이 페이지

  • SQS가 출발한 자리: AWS의 첫 서비스
  • 왜 "최소 1회 전달"인가: 분산 시스템의 근본 트레이드오프
  • 가시성 타임아웃: 분산 잠금 없이 중복을 줄이는 법
  • 표준 큐 대 FIFO 큐: 순서를 보장하는 비용
  • 실패한 메시지는 어디로 가는가: DLQ와 재구동
  • 빈 큐에서 비용이 새는 이유: 폴링 모델
  • 정리
  • 연습 문제