CertNote/ 출퇴근 학습 노트
로드맵
KOEN
CLF-C02 · FoundationalCloud Practitioner - Foundational
DVA-C02 · AssociateDeveloper - Associate
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
  • Week 1
    • 1.데이터 엔지니어링이란
    • 2.배치 vs 스트리밍
    • 3.AWS 데이터 서비스 조감도
    • 4.데이터 포맷과 모델링
    • 5.Week 1 종합 복습
  • Week 2
  • Week 3
    • 1.스트리밍 처리: Managed Service for Apache Flink와 윈도우 집계
    • 2.수집 신뢰성: 멱등성, 순서 보장, 재시도, 중복 제거, DLQ
    • 3.CDC와 데이터 복제: Database Migration Service(DMS)
    • 4.수집 아키텍처 패턴: Lambda 아키텍처와 이벤트 기반 수집
    • 5.Week 3 종합: 데이터 수집 2 복습
  • Week 4
  • Week 5
  • Week 6
  • Week 7
  • Week 8
  • Week 9
  • Week 10
MLS-C01 · SpecialtyMachine Learning - Specialty
합격 후기

© CertNote · 주간랩스

  • 소개
  • 수정 기록
  • 문의
  • 이용약관
  • 개인정보처리방침
← DEA-C01/Week 3/Day 2

기준: DEA-C01 시험 가이드·오류 제보

DEA-C01· AssociateWeek 3 · Day 2읽기 약 10분

Day 2 - 수집 신뢰성: 멱등성, 순서 보장, 재시도, 중복 제거, DLQ

분산 시스템에서 "메시지가 정확히 한 번 전달된다"는 보장은 사실상 환상에 가깝다. 네트워크는 끊기고, 노드는 죽고, 응답은 유실된다. 프로듀서가 메시지를 보냈는데 응답(ACK)을 못 받으면, 그 메시지가 도착했는지 안 했는지 알 수 없다. 다시 보내면 중복이 생기고, 안 보내면 유실된다. 이 딜레마가 데이터 수집 신뢰성의 출발점이다.

현실적인 분산 시스템은 대부분 At-least-once(최소 한 번)를 보장한다. "유실보다는 중복이 낫다"는 선택이다. 그렇다면 중복을 누가 처리할 것인가? 답은 컨슈머가 멱등성(idempotency)을 갖추는 것이다. 오늘은 이 신뢰성을 구성하는 다섯 개의 축 — 멱등성, 순서, 재시도, 중복 제거, DLQ — 을 다룬다.

전달 보장의 세 등급: 무엇을 선택할 것인가

보장의미비용/복잡도대표
At-most-once최대 한 번, 유실 가능가장 단순재시도 없는 UDP 류
At-least-once최소 한 번, 중복 가능중간SQS Standard, Kinesis
Exactly-once정확히 한 번가장 비쌈SQS FIFO(범위 한정), Flink(상태)

진정한 end-to-end exactly-once는 매우 비싸고, 보통 "at-least-once 전달 + 멱등한 컨슈머"의 조합으로 같은 효과를 낸다. 이것을 effectively-once라고 부른다. 시험에서 "중복이 발생할 수 있는 환경에서 데이터 정합성을 어떻게 보장하는가"의 정답은 거의 항상 멱등성이다.

💡 관련 이론: "Exactly-once delivery는 불가능하지만 exactly-once processing은 가능하다"는 명제는 분산 시스템의 고전적 통찰이다. 전달은 네트워크에 달려 있어 통제 불가능하지만, 처리 결과의 멱등성은 애플리케이션이 통제할 수 있다. 그래서 설계의 무게중심을 "전달을 완벽하게 만들기"에서 "중복 처리를 안전하게 만들기"로 옮긴다.

멱등성: 같은 요청을 여러 번 처리해도 결과가 같게

멱등성이란 "동일한 연산을 한 번 하든 여러 번 하든 결과가 같다"는 성질이다. SET x = 5는 멱등하지만 x = x + 1은 멱등하지 않다. 수집 파이프라인에서 멱등성을 구현하는 표준 방법은 고유 식별자 + 중복 검사다.

# DynamoDB 조건부 쓰기로 멱등성 구현
def process_event(event):
    event_id = event["eventId"]   # 프로듀서가 부여한 고유 ID
    try:
        dynamodb.put_item(
            TableName="processed_events",
            Item={"eventId": {"S": event_id}, "ts": {"N": str(time.time())}},
            ConditionExpression="attribute_not_exists(eventId)"  # 이미 있으면 실패
        )
    except dynamodb.exceptions.ConditionalCheckFailedException:
        return  # 이미 처리된 이벤트 — 조용히 무시(멱등)
    handle_business_logic(event)  # 처음 보는 이벤트만 실제 처리

핵심은 "처리 여부를 원자적으로 기록"하는 것이다. DynamoDB의 조건부 쓰기(attribute_not_exists)는 이를 한 번의 원자적 연산으로 보장한다. 중복 이벤트가 와도 조건 검사에서 걸러지므로 비즈니스 로직이 두 번 실행되지 않는다.

멱등성 키(idempotency key)는 프로듀서가 부여해야 한다. 컨슈머가 "내용 해시"로 키를 만들면, 의도된 동일 이벤트(예: 같은 금액 두 번 결제)와 중복 재전송을 구분할 수 없다. API Gateway/결제 시스템이 클라이언트에게 Idempotency-Key 헤더를 요구하는 이유가 이것이다. 키의 출처가 신뢰성의 출발점이다.

순서 보장: 어디까지, 어떤 단위로

"순서대로 처리"는 종종 오해된다. 글로벌 전역 순서(global ordering)는 분산 환경에서 처리량을 심하게 깎아먹는다. 그래서 대부분의 시스템은 파티션/그룹 단위 순서만 보장한다.

서비스순서 보장 단위
Kinesis Data Streams샤드(shard) 내 순서 — 파티션 키가 같으면 같은 샤드
SQS FIFOMessageGroupId 단위 순서
SQS Standard순서 보장 없음(best-effort)
Kafka(MSK)파티션 내 순서
# Kinesis: 같은 사용자의 이벤트를 같은 샤드로 → 사용자별 순서 보장
kinesis.put_record(
    StreamName="user-events",
    Data=json.dumps(event),
    PartitionKey=event["userId"]   # 같은 userId → 같은 샤드 → 순서 유지
)
# SQS FIFO: MessageGroupId 단위 순서 + 5분 중복 제거 윈도우
sqs.send_message(
    QueueUrl=fifo_url,
    MessageBody=json.dumps(order),
    MessageGroupId=order["accountId"],          # 계정별 순서
    MessageDeduplicationId=order["orderId"]     # 5분 내 중복 자동 제거
)

먼저 "순서가 정말 필요한 단위가 무엇인가"를 정해야 한다. "사용자별 이벤트 순서"가 필요하면 userId를 파티션 키/그룹 ID로 쓰면 되고, 전역 순서는 거의 필요 없다.

⚠️ 함정: Kinesis에서 파티션 키 분포가 한쪽으로 쏠리면(hot shard) 특정 샤드만 과부하가 걸린다. 순서 보장을 위해 같은 키를 같은 샤드로 보내는 것과, 부하 분산을 위해 키를 고르게 퍼뜨리는 것은 상충한다. 순서 단위를 너무 크게(예: 단일 키로 전부) 잡으면 병렬성이 무너진다.

재시도: 백오프와 지터, 그리고 가시성 타임아웃

재시도는 일시적 장애(transient failure)를 흡수하는 1차 방어선이다. 하지만 무작정 즉시 재시도하면 장애 중인 시스템에 부하를 더 얹는다(thundering herd). 그래서 지수 백오프 + 지터(jitter)가 표준이다.

# 지수 백오프 + 풀 지터
import random
def retry_with_backoff(fn, max_attempts=5):
    for attempt in range(max_attempts):
        try:
            return fn()
        except TransientError:
            if attempt == max_attempts - 1:
                raise
            base = min(2 ** attempt, 30)       # 1,2,4,8,16... 최대 30초
            delay = random.uniform(0, base)    # 풀 지터: 동시 재시도 분산
            time.sleep(delay)

SQS에서는 가시성 타임아웃(visibility timeout)이 재시도를 자연스럽게 구현한다. 메시지를 받은 컨슈머가 정해진 시간 안에 삭제(DeleteMessage)하지 못하면, 메시지가 다시 큐에 보이게 되어 다른 컨슈머가 재처리한다. 처리 시간이 가시성 타임아웃보다 길면 같은 메시지를 두 컨슈머가 동시에 처리하는 사고가 난다.

지터는 AWS 아키텍처 블로그 "Exponential Backoff And Jitter"(2015)에서 정립됐다. 백오프만 쓰면 모든 클라이언트가 1초, 2초, 4초에 동시에 재시도해 부하가 파동처럼 몰린다. 무작위 지터를 더하면 재시도가 시간축에 고르게 퍼져 시스템 회복을 돕는다. "풀 지터(0~base 사이 랜덤)"가 대체로 가장 효과적이다.

중복 제거: 제거 윈도우의 한계

SQS FIFO는 MessageDeduplicationId로 5분 윈도우 내 중복을 자동 제거한다. 같은 ID로 5분 안에 두 번 보내면 두 번째는 조용히 버려진다. 하지만 5분이 지난 뒤 같은 ID가 다시 오면 중복으로 처리되지 않는다 — 윈도우 밖이기 때문이다.

이 한계 때문에, "장시간에 걸친 멱등성"이 필요하면 SQS 중복 제거만으로는 부족하고, 앞서 본 DynamoDB 기반 멱등성 테이블이 필요하다. 두 방식은 보완 관계다.

방식범위장점한계
SQS FIFO 중복 제거5분 윈도우인프라가 자동 처리윈도우 밖 중복은 못 막음
DynamoDB 멱등성 테이블TTL로 지정한 기간임의 기간, 비즈니스 단위 제어직접 구현·운영 필요

DLQ: 독약 메시지를 격리하는 곳

재시도를 아무리 해도 처리되지 않는 메시지가 있다 — 형식이 깨졌거나, 영원히 실패하는 독약 메시지(poison message)다. 이걸 계속 재시도하면 큐가 막히고 정상 메시지까지 지연된다. Dead-Letter Queue(DLQ)는 일정 횟수 실패한 메시지를 따로 격리한다.

{
  "RedrivePolicy": {
    "deadLetterTargetArn": "arn:aws:sqs:...:orders-dlq",
    "maxReceiveCount": 5
  }
}

maxReceiveCount: 5는 "5번 수신(처리 시도)되도록 실패하면 DLQ로 보내라"는 뜻이다. DLQ로 간 메시지는 사라지지 않는다. 알람을 걸어두고, 원인을 분석한 뒤, 코드를 고쳐서 재구동(redrive)으로 원래 큐로 되돌려 재처리할 수 있다.

🎯 시나리오: 주문 처리 Lambda가 가끔 특정 메시지에서 무한 실패한다. 진단·대응 순서는 (1) SQS에 DLQ를 연결하고 maxReceiveCount=5 설정 → (2) DLQ 깊이에 CloudWatch 알람 → (3) DLQ 메시지를 조사해 깨진 필드/스키마 위반 발견 → (4) 컨슈머 코드에 검증·예외 처리 추가 → (5) DLQ redrive로 재처리. 독약 메시지가 정상 큐를 막지 않게 격리하는 것이 핵심이다.

DLQ는 Kinesis에도 개념적으로 존재한다. Lambda가 Kinesis 소스를 처리하다 실패하면, OnFailure destination(SQS/SNS)을 지정해 실패 레코드 메타데이터를 보낼 수 있다. 또 BisectBatchOnFunctionError로 실패 배치를 절반씩 쪼개 독약 레코드만 격리하는 기법도 있다. 스트림과 큐는 메커니즘이 다르지만 "실패 격리"라는 철학은 같다.

정리: 신뢰성은 조합으로 만든다

오늘의 다섯 축은 따로 노는 게 아니다. at-least-once 전달을 전제로, 재시도(백오프+지터)가 일시 장애를 흡수하고, 멱등성이 그로 인한 중복을 무해하게 만들며, 순서는 필요한 단위로만 보장하고, DLQ가 영구 실패를 격리한다. 어느 하나만으로는 신뢰성이 완성되지 않는다. 내일은 운영 중인 데이터베이스를 멈추지 않고 변경분을 실시간으로 복제하는 CDC와 DMS로 넘어간다.

📝 연습 문제

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

문제 1

결제 API가 네트워크 타임아웃으로 같은 요청을 두 번 보낼 수 있다. 중복 결제를 방지하기 위한 가장 견고한 방법은?

문제 2

Kinesis Data Streams에서 "같은 사용자의 이벤트는 발생 순서대로 처리"되어야 한다. 어떻게 보장하는가?

문제 3

SQS 표준 큐에서 컨슈머가 처리에 평균 90초가 걸리는데 가시성 타임아웃이 30초로 설정돼 있다. 어떤 문제가 생기는가?

문제 4

일시적 장애가 발생한 다운스트림에 다수 클라이언트가 동시에 재시도하면서 부하가 파동처럼 몰린다. 이를 완화하는 표준 기법은?

문제 5

특정 메시지가 형식 오류로 영원히 처리에 실패해 정상 메시지 처리까지 막고 있다. 가장 적절한 대응은?

이전스트리밍 처리: Managed Service for Apache Flink와 윈도우 집계Week 3 · Day 1다음 CDC와 데이터 복제: Database Migration Service(DMS)Week 3 · Day 3

참고 공식 자료

  • DEA-C01 공식 시험 안내(새 탭에서 열림)
  • Amazon SQS 공식 문서(새 탭에서 열림)
  • Amazon Kinesis 공식 문서(새 탭에서 열림)
  • Amazon DynamoDB 공식 문서(새 탭에서 열림)

이 페이지

  • 전달 보장의 세 등급: 무엇을 선택할 것인가
  • 멱등성: 같은 요청을 여러 번 처리해도 결과가 같게
  • 순서 보장: 어디까지, 어떤 단위로
  • 재시도: 백오프와 지터, 그리고 가시성 타임아웃
  • 중복 제거: 제거 윈도우의 한계
  • DLQ: 독약 메시지를 격리하는 곳
  • 정리: 신뢰성은 조합으로 만든다
  • 연습 문제