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 2

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

DVA-C02· AssociateWeek 11 · Day 2읽기 약 14분

Day 2 - SNS: 한 번 발행하면 모두가 듣는다

앞 글의 SQS는 "한 메시지를 정확히 한 소비자가 가져간다"는 포인트-투-포인트 모델이었다. 그런데 현실의 많은 사건은 "한 번 일어났는데 여러 곳이 반응해야" 한다. 주문 하나가 생기면 재고 시스템도, 결제 시스템도, 알림 시스템도, 데이터 웨어하우스도 동시에 그 사실을 알아야 한다. 이를 SQS로 하려면 생산자가 큐 4개에 똑같은 메시지를 4번 보내야 하고, 새 구독자가 늘 때마다 생산자 코드를 고쳐야 한다. 발행-구독(publish-subscribe) 모델은 이 문제를 뒤집는다. 생산자는 "토픽"에 한 번만 발행하고, 누가 듣는지는 신경 쓰지 않는다. Amazon SNS는 이 모델을 완전 관리형으로 구현한 서비스다.

DVA-C02에서 SNS는 SQS와 짝으로 나오는 경우가 많다. 단독 문제(필터 정책, FIFO 토픽)도 있지만, 중심 주제는 "팬아웃(fanout)", 곧 SNS와 SQS를 결합해 한 이벤트를 여러 큐로 안정적으로 퍼뜨리는 패턴이다. 이번 글은 발행-구독이 왜 생산자와 구독자를 떼어놓는지, SNS의 "푸시·비보존"이 SQS의 "풀·보존"과 어떻게 상호보완하는지, 필터 정책이 어떻게 "구독자 코드 없이" 메시지를 선별하는지를 다룬다.

발행-구독: 생산자가 구독자를 모르게 만드는 것

SNS의 중심 개념은 토픽(topic) 이다. 생산자는 토픽에 메시지를 publish하고, 그것으로 끝이다. 토픽은 등록된 모든 구독자에게 그 메시지를 즉시 푸시(push) 한다. 구독자가 1명이든 1만 명이든 생산자 코드는 동일하고, 구독자가 추가/삭제돼도 생산자는 영향을 받지 않는다. 이 "생산자가 구독자의 존재를 모른다"는 성질이 발행-구독 모델의 본질적 가치다.

import boto3
 
sns = boto3.client('sns')
 
# 토픽 생성 — 생산자는 이 토픽만 안다
topic_arn = sns.create_topic(Name='OrderEvents')['TopicArn']
 
# 다양한 구독자 등록 (생산자 코드와 무관하게 늘릴 수 있음)
sns.subscribe(TopicArn=topic_arn, Protocol='sqs',
              Endpoint='arn:aws:sqs:...:inventory-queue')
sns.subscribe(TopicArn=topic_arn, Protocol='lambda',
              Endpoint='arn:aws:lambda:...:function:notify')
sns.subscribe(TopicArn=topic_arn, Protocol='email',
              Endpoint='ops@example.com')
 
# 발행 — 누가 듣는지 신경 쓰지 않는다
sns.publish(
    TopicArn=topic_arn,
    Message='{"orderId": "O001", "status": "created"}',
    MessageAttributes={'orderType': {'DataType': 'String', 'StringValue': 'premium'}}
)

발행-구독은 소프트웨어 설계의 관찰자 패턴(Observer Pattern) 을 분산 환경으로 확장한 것이다. GoF 디자인 패턴의 관찰자는 "주체(Subject)가 상태 변화를 관찰자(Observer)들에게 통지"하는데, 관찰자 목록을 주체가 직접 들고 있어 결합이 남는다. 메시징의 pub/sub는 그 사이에 토픽(브로커) 을 끼워 주체와 관찰자가 서로를 전혀 모르게 완전히 떼어놓는다(공간적 디커플링). 이 덕분에 시스템을 확장할 때 "새 구독자를 추가"하는 것만으로 기능이 늘어나고 기존 코드는 건드릴 필요가 없다. 이벤트 주도 아키텍처(EDA)의 기반이다.

SNS와 SQS의 가장 중요한 대비는 "메시지 보존"이다. SNS는 메시지를 저장하지 않는다. 발행 순간 구독자에게 푸시하고, 푸시에 실패하면(재시도 정책에 따라 재시도 후) 메시지는 사라진다. 반면 SQS는 메시지를 최대 14일 보존한다.

  • SNS가 Lambda나 HTTP 엔드포인트로 직접 푸시하는데 그 대상이 잠깐 다운돼 있으면, 재시도가 소진된 뒤 메시지는 영영 사라진다.
  • 그래서 "절대 잃으면 안 되는" 이벤트는 SNS가 SQS로 푸시하게 만든다. SNS가 SQS에 한 번 넣으면 그 뒤에는 SQS가 소비자가 살아날 때까지 메시지를 안전하게 보존한다.
  • 팬아웃 패턴이 단순한 편의가 아니라 내구성 설계인 이유다.

팬아웃: SNS의 푸시와 SQS의 보존을 합치다

팬아웃 패턴은 SNS 토픽 → 여러 SQS 큐로 메시지를 퍼뜨리는 구조다. 각 SQS 큐는 독립적인 소비자(보통 Lambda나 처리 서비스)에 연결된다.

팬아웃 아키텍처
================================
[주문 서비스] ──publish──> [SNS 토픽: OrderEvents]
                                  │
                  ┌───────────────┼───────────────┐
                  ▼               ▼               ▼
            [SQS: 재고]      [SQS: 결제]      [SQS: 분석]
                  │               │               │
                  ▼               ▼               ▼
            [Lambda 재고]   [Lambda 결제]   [Firehose→S3]

SNS만으로 여러 Lambda를 직접 호출하지 않고 굳이 SQS를 끼우는 이유는 세 가지다.

  • 내구성: 소비자가 다운돼도 메시지는 각자의 SQS 큐에 안전하게 쌓여 기다린다.
  • 버퍼링: 갑자기 이벤트가 폭증해도 각 큐가 완충재 역할을 해 소비자가 자기 속도로 처리한다.
  • 독립 재시도와 DLQ: 결제 처리가 실패하면 결제 큐의 메시지만 재시도되고 그 큐의 DLQ로 격리된다. 재고 처리에는 영향이 없다.

SNS의 "한 번 발행으로 다중 전달"과 SQS의 "안정적 보존·재시도"가 각자의 장점을 합친 것이 팬아웃이다.

이 패턴의 전형적 사용처가 S3 이벤트 분기다. S3 버킷은 한 이벤트 타입(예: s3:ObjectCreated:*)에 대해 직접 연결할 수 있는 대상이 사실상 하나로 제한적이다. 그래서 "업로드 하나에 썸네일 생성·바이러스 검사·메타데이터 색인을 동시에" 하려면 S3 → SNS → 여러 SQS 팬아웃으로 풀어야 한다. 최근에는 EventBridge가 더 풍부한 라우팅·필터링을 제공해 S3 → EventBridge → 다중 타깃도 쓰이지만, 시험에서 "정석적 팬아웃"을 물으면 SNS+SQS가 기본 답이다. 두 접근의 차이는 "단순 복제 배포"(SNS)냐 "내용 기반 복잡 라우팅"(EventBridge)이냐다.

⚠️ 함정: "하나의 이벤트를 여러 소비자가 동시에 처리"라는 문구가 보이면 SNS(또는 EventBridge)가 답이고, SQS 단독은 오답이다. SQS는 한 메시지를 한 소비자만 가져가는 포인트-투-포인트라 같은 메시지를 여러 소비자가 받을 수 없다. 반대로 "한 메시지를 정확히 한 작업자가 처리(작업 큐)"면 SQS다. SNS/SQS 선택 문제는 대개 "1:N이냐 1:1이냐"로 갈린다.

필터 정책: 구독자 코드 없이 메시지를 선별하다

팬아웃을 하다 보면 "이 큐는 프리미엄 주문만, 저 큐는 일반 주문만 받고 싶다"는 요구가 생긴다. 순진하게 풀면 모든 큐가 모든 메시지를 받고 각 소비자가 "이건 내 게 아니네" 하고 버리는 코드를 짠다. 불필요한 메시지 전달과 처리 비용이 낭비된다. 필터 정책(Filter Policy) 은 이 선별을 SNS가 전달 전에 대신 해 준다.

# 이 구독은 orderType이 premium인 메시지만 받는다
sns.set_subscription_attributes(
    SubscriptionArn='arn:aws:sns:...:premium-sub',
    AttributeName='FilterPolicy',
    AttributeValue='{"orderType": ["premium"], "price": [{"numeric": [">", 1000]}]}'
)

필터 정책은 메시지의 속성과 매칭되며, 매칭되지 않는 메시지는 그 구독자에게 아예 전달되지 않는다. 구독자는 자기에게 해당하는 메시지만 받으므로 코드가 단순해지고, 전달되지 않은 메시지에 대한 비용도 들지 않는다.

매칭 연산예시
정확 일치{"store": ["seoul", "busan"]}
부정{"event": [{"anything-but": "test"}]}
숫자 비교{"price": [{"numeric": [">", 100]}]}
존재 여부{"size": [{"exists": true}]}
접두사{"region": [{"prefix": "ap-"}]}

기본 필터링은 메시지 속성(MessageAttributes) 만 검사한다. 메시지 본문(payload) 안의 필드로 필터링하려면 FilterPolicyScope를 MessageBody로 명시적으로 켜야 한다(2022년 추가). 본문 필터링이 기본이 아닌 이유는 속성은 메타데이터라 구조가 단순하고 파싱이 가볍지만, 본문은 임의의 JSON이라 매번 파싱하는 비용이 크기 때문이다. 메시징의 일반 원칙인 "라우팅에 필요한 정보는 헤더(속성)에 두고, 페이로드는 라우팅과 분리"의 반영이다. AMQP의 헤더 익스체인지, HTTP의 헤더 기반 라우팅과 같은 발상이다.

필터 정책은 콘텐츠 기반 라우팅(Content-Based Router) 패턴을 구독자 쪽이 아니라 브로커(SNS) 쪽에서 구현한 것이다.

  • 라우팅 결정을 소비자에게 맡기면 모든 소비자가 모든 메시지를 받아 직접 판단해야 한다(낭비).
  • 별도 라우터 서비스를 두면 그 라우터가 단일 장애점이 된다.
  • SNS는 라우팅 규칙을 구독 속성으로 토픽에 내장시켜, 추가 인프라 없이 브로커가 라우팅하게 했다.
  • 다만 필터의 표현력은 EventBridge의 이벤트 패턴보다 제한적이라, 복잡한 조건 라우팅이 필요하면 EventBridge가 더 적합하다.

SNS FIFO: 순서를 양 끝단에서 보장하다

SQS에 FIFO가 있듯 SNS에도 FIFO 토픽이 있다. 토픽 이름에 .fifo 접미사가 붙고, SQS FIFO 큐에만 구독할 수 있다. 순서 보장이 발행부터 소비까지 끊기지 않으려면 양 끝단이 모두 FIFO여야 하기 때문이다. SNS FIFO가 순서대로 푸시해도 받는 쪽이 표준 SQS면 거기서 순서가 깨진다.

topic_arn = sns.create_topic(
    Name='orders.fifo',
    Attributes={'FifoTopic': 'true', 'ContentBasedDeduplication': 'true'}
)['TopicArn']

SNS FIFO + SQS FIFO 조합은 "순서가 중요한 이벤트를 여러 소비자에게 순서대로 팬아웃"하는 경우에 쓴다. 표준 토픽보다 처리량이 낮고(300 msg/s대) 비용이 더 든다는 트레이드오프는 SQS FIFO와 같다.

전달 실패는 어디로: 재시도와 SNS DLQ

SNS는 비보존이지만 한 번 실패에 바로 포기하지는 않는다. 구독 프로토콜마다 재시도 정책이 다르다. HTTP/HTTPS 엔드포인트는 기본적으로 약 1시간에 걸쳐 최대 50회까지 재시도한다(지수 백오프 포함). SQS·Lambda 같은 AWS 내부 대상은 더 공격적으로 재시도한다. 그럼에도 재시도가 모두 소진되면 메시지는 사라지는데, 이때 SNS 구독 단위 DLQ(redrive policy)를 설정하면 전달 실패 메시지를 SQS DLQ로 보내 보존할 수 있다.

⚠️ 함정: SNS의 "메시지 보존 없음"과 "구독 DLQ"를 혼동하면 안 된다. SNS 자체는 메시지를 저장하지 않지만, 특정 구독의 전달이 실패했을 때 그 실패 메시지를 잡아두는 안전망이 구독 DLQ다. "SNS DLQ"와 "Lambda DLQ"는 다른 계층이다. SNS DLQ는 "SNS가 구독자에게 전달하는 데 실패"한 경우, Lambda DLQ는 "Lambda가 비동기 호출을 처리하는 데 실패"한 경우를 잡는다. 시험에서 둘을 바꿔 출제할 수 있다.

SNS는 모바일 푸시 알림의 백엔드로도 쓰인다. iOS는 APNs, Android는 FCM(구 GCM), Amazon 기기는 ADM으로 푸시하는데, SNS가 이들 플랫폼별 프로토콜 차이를 추상화해 하나의 publish로 통합한다. 디바이스 토큰을 SNS에 등록하면 플랫폼 엔드포인트 ARN이 생기고, 이후에는 플랫폼을 의식하지 않고 알림을 보낼 수 있다. "여러 모바일 플랫폼에 통합 푸시 알림"이라는 시나리오의 정답이 SNS Mobile Push다.

정리

SNS를 관통하는 한 문장은 "한 번 발행하면 생산자가 구독자를 모른 채 모두에게 푸시된다"이다. 발행-구독 모델은 관찰자 패턴을 토픽이라는 브로커로 완전히 분리한 것이고, 그 대가는 "메시지 비보존"이다. 그래서 SNS의 즉시 푸시와 SQS의 안정적 보존을 결합한 팬아웃이 단순 편의가 아니라 내구성 있는 1:N 배포의 표준이 된다. 필터 정책은 콘텐츠 기반 라우팅을 브로커에 내장해 구독자 코드 없이 선별을 처리하고, FIFO 토픽은 양 끝단에서 순서를 지키며, 구독 DLQ는 비보존 모델의 안전망이 된다. SNS와 SQS는 경쟁이 아니라 "푸시·비보존·1:N"과 "풀·보존·1:1"로 역할을 나눠 함께 쓰일 때 가장 강력하다.

다음 글에서는 메시지를 "처리하면 사라지는" SQS·SNS와 달리, 데이터를 스트림에 보존해 여러 소비자가 각자의 위치에서 반복해 읽을 수 있는 Kinesis로 넘어간다.

📝 연습 문제

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

문제 1

S3 객체 업로드 하나에 대해 썸네일 생성, 바이러스 검사, 메타데이터 색인을 각각 독립적으로 처리해야 한다. 가장 적절한 패턴은?

문제 2

SNS를 통해 Lambda로 직접 푸시하는 구성에서, Lambda가 잠시 다운된 동안 발행된 메시지가 유실됐다. 내구성을 확보하려면?

문제 3

SNS 팬아웃에서 특정 SQS 큐는 orderType=premium인 메시지만 받아야 한다. 추가 인프라 없이 구현하려면?

문제 4

SNS와 SQS의 근본적 차이로 가장 정확한 것은?

문제 5

SNS 메시지 본문(payload) 안의 JSON 필드 값으로 필터링하려 한다. 기본 설정으로 동작하지 않는 이유와 해결은?

문제 6

순서가 중요한 주문 이벤트를 여러 소비자에게 순서대로 팬아웃해야 한다. 올바른 구성은?

문제 7

SNS가 HTTPS 엔드포인트로의 전달에 반복 실패할 때 메시지를 보존해 나중에 분석하려면?

이전SQS: 큐 하나가 어떻게 시스템을 떼어놓는가Week 11 · Day 1다음 Kinesis: 흐르는 데이터를 붙잡아두는 로그Week 11 · Day 3

참고 공식 자료

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

이 페이지

  • 발행-구독: 생산자가 구독자를 모르게 만드는 것
  • 팬아웃: SNS의 푸시와 SQS의 보존을 합치다
  • 필터 정책: 구독자 코드 없이 메시지를 선별하다
  • SNS FIFO: 순서를 양 끝단에서 보장하다
  • 전달 실패는 어디로: 재시도와 SNS DLQ
  • 정리
  • 연습 문제