95 lines
10 KiB
Markdown
95 lines
10 KiB
Markdown
---
|
||
title: official-doc / microservices.io — Idempotent Consumer Pattern (Chris Richardson)
|
||
source_type: official-doc
|
||
status: raw
|
||
confidence: medium
|
||
url: https://microservices.io/patterns/communication-style/idempotent-consumer.html
|
||
archive_url:
|
||
related_branches: [feature-kafka-consumer-inbox-contract]
|
||
related_projects: [ca-skeleton]
|
||
tags: [official-doc, ca-skeleton, messaging, kafka, idempotency]
|
||
created: 2026-07-28
|
||
last_reviewed: 2026-07-28
|
||
---
|
||
|
||
# Idempotent Consumer Pattern — microservices.io (Chris Richardson)
|
||
|
||
> Layer: `raw/official-docs/` — Chris Richardson 의 microservices.io 패턴 카탈로그 중 "Idempotent Consumer" 페이지 verbatim 발췌. at-least-once 재전달로 인한 consumer 중복 실행 문제와 processed-message-ID 기록 기반 해결책의 1차 인용 출처.
|
||
>
|
||
> **강도 주의**: microservices.io 는 Chris Richardson 개인의 패턴 카탈로그다. `raw/official-docs/` 에 두되 strength 는 `engineering-blog` (개인 패턴 카탈로그) 로 표기한다 — 벤더 공식 표준(official-standard / official-vendor-doc / official-reference)으로 격상 금지. sibling `[[raw/official-docs/microservices-io-transactional-outbox]]` 와 동일 등급 정책.
|
||
|
||
## Parent / 활용 branch (필수)
|
||
|
||
| Branch | 이 자료가 정당화하는 결정 |
|
||
|---|---|
|
||
| [[raw/branch-notes/feature-kafka-consumer-inbox-contract]] | ca-skeleton consumer 가 at-least-once 재전달의 중복 실행을 차단하기 위해 처리한 메시지 ID 를 기록하는 inbox(processed-message) 테이블을 비즈니스 write 와 동일 DB 트랜잭션에서 커밋하는 방식을 채택하는 근거, 그리고 "비즈니스 엔티티 자체의 유니크 제약으로 대체" 변형이 언제 성립하는지의 선택 조건 |
|
||
|
||
## 출처
|
||
|
||
- 원본 URL: https://microservices.io/patterns/communication-style/idempotent-consumer.html
|
||
- 아카이브 URL: (미등록)
|
||
- 저자 / 조직: Chris Richardson — microservices.io (personal pattern catalog). 별도 vendor 의 공식 문서 아님.
|
||
- 발행일: rolling docs (페이지에 "Copyright © 2026" 표기, 최초 작성 시점 명시 없음)
|
||
- 마지막 확인일: 2026-07-28
|
||
|
||
## 왜 저장했는지
|
||
|
||
`feature-kafka-consumer-inbox-contract` branch 가 inbox(PROCESSED_MESSAGE류) 테이블 기반 멱등 consumer 를 채택하는 근거이자, 처리한 메시지 ID 기록을 DB 트랜잭션 경계 안에서 수행해야 duplicate INSERT 가 유니크 제약으로 실패·rollback 되는 메커니즘의 1차 출처. 별도 테이블 vs 비즈니스 엔티티 내장이라는 두 변형 중 어느 쪽을 언제 쓰는지 판단할 근거로 보관한다.
|
||
|
||
## 핵심 인용
|
||
|
||
> [§Context] "One side-effect, however, is that the consumer can be invoked repeatedly for the same message."
|
||
|
||
> [§Solution] "Make a consumer idempotent by having it record the IDs of processed messages in the database."
|
||
|
||
> [§Solution] "After starting the database transaction, the message handler inserts the message’s ID into the PROCESSED_MESSAGE table."
|
||
|
||
> [§Solution] "Since the (subscriberId, messageID) is the PROCESSED_MESSAGE table’s primary key the INSERT will fail if the message has been already processed successfully."
|
||
|
||
> [§Solution] "The other option is for the consumer to store the IDs in the business entities that it creates or updates."
|
||
|
||
## Claims Extracted
|
||
|
||
| Claim ID | Claim | Evidence quote | Strength | Applies to | Does not prove |
|
||
|---|---|---|---|---|---|
|
||
| MSIO-IDEMPC-C1 | at-least-once 전달을 보장하는 메시지 브로커를 쓰면, 부작용으로 consumer 가 동일 메시지에 대해 반복 호출될 수 있다 | [§Context] "One side-effect, however, is that the consumer can be invoked repeatedly for the same message." | `engineering-blog` | at-least-once delivery 를 보장하는 모든 메시지 브로커(Kafka 포함) 사용 시 재전달 가능성 일반 | 재전달이 얼마나 자주 발생하는지 정량적 근거 없음; Kafka 고유의 rebalance/재시도·offset 커밋 메커니즘과의 상호작용은 별도 근거 필요 |
|
||
| MSIO-IDEMPC-C2 | 해법의 핵심은 consumer 가 처리한 메시지의 ID 를 DB 에 기록해 멱등성을 확보하는 것 | [§Solution] "Make a consumer idempotent by having it record the IDs of processed messages in the database." | `engineering-blog` | consumer 가 조회 가능한 저장소(RDB 등)에 접근 가능한 아키텍처 | 이 저장소가 반드시 별도 RDB 여야 한다는 뜻은 아님 — 다른 저장 매체(예: 분산 KV)의 적합성은 본문에서 다루지 않음 |
|
||
| MSIO-IDEMPC-C3 | 메커니즘: message handler 는 DB 트랜잭션을 시작한 뒤 그 트랜잭션 안에서 메시지 ID 를 PROCESSED_MESSAGE 테이블에 INSERT 한다 | [§Solution] "After starting the database transaction, the message handler inserts the message’s ID into the PROCESSED_MESSAGE table." | `engineering-blog` | ID 기록을 DB 트랜잭션 경계 안에서 수행하는 구현 일반 | **미발견**: 이 문장 자체는 "비즈니스 엔티티 갱신도 같은 트랜잭션에 포함되어야 한다"고 명시적으로 말하지 않는다. 하나의 message handler 가 트랜잭션을 하나만 시작한다는 것에서 강하게 시사될 뿐, "동일 트랜잭션 요구"를 문장으로 직접 진술하지는 않음 — ca-skeleton 결정으로 넘어갈 때 이 gap 을 명시해야 함 |
|
||
| MSIO-IDEMPC-C4 | 중복 탐지 메커니즘은 (subscriberId, messageID) 복합 기본키(primary key) 이며, 이미 처리된 메시지를 다시 INSERT 하면 그 제약 위반으로 실패한다 | [§Solution] "Since the (subscriberId, messageID) is the PROCESSED_MESSAGE table’s primary key the INSERT will fail if the message has been already processed successfully." | `engineering-blog` | RDB 의 기본키/유니크 제약을 dedup 메커니즘으로 쓰는 구현(PostgreSQL 포함) | 이 제약 기반 방식이 분산 락이나 애플리케이션 레벨 사전 조회보다 우월하다는 비교 평가는 없음; 유니크 제약이 없는 저장소(순수 NoSQL 등)에는 그대로 적용되지 않음 |
|
||
| MSIO-IDEMPC-C5 | 변형: 별도 PROCESSED_MESSAGES 테이블 대신, consumer 가 생성/갱신하는 비즈니스 엔티티 자체에 메시지 ID 를 저장하는 방식도 가능하다 | [§Solution] "The other option is for the consumer to store the IDs in the business entities that it creates or updates." | `engineering-blog` | consumer 가 처리마다 정확히 하나의 특정 business entity 를 생성/갱신하는 경우 (예: `AccountDebited` → `Account` 엔티티) | 이 변형을 **언제** 선택해야 하는지의 판단 기준(예: fan-out 메시지, 엔티티가 없는 처리, 여러 엔티티를 건드리는 처리)은 본문에 없음 — 선택 조건은 이 자료만으로 증명되지 않음 |
|
||
|
||
## Usage Boundaries
|
||
|
||
- **이 자료가 직접 증명하는 것**:
|
||
- `MSIO-IDEMPC-C1`: at-least-once 브로커의 재전달 부작용(중복 invocation) 정의
|
||
- `MSIO-IDEMPC-C2`: 해법의 뼈대 — 처리한 메시지 ID 를 DB 에 기록해 멱등성 확보
|
||
- `MSIO-IDEMPC-C3`: ID INSERT 가 message handler 의 DB 트랜잭션 안에서 일어난다는 것
|
||
- `MSIO-IDEMPC-C4`: (subscriberId, messageID) 복합 PK 유니크 제약이 중복 INSERT 를 실패시키는 구체 메커니즘
|
||
- `MSIO-IDEMPC-C5`: PROCESSED_MESSAGES 별도 테이블의 대안으로 비즈니스 엔티티 자체에 ID 저장이 가능하다는 것(옵션 존재 자체)
|
||
- **이 자료가 증명하지 않는 것**:
|
||
- 본 페이지가 **공식 vendor doc 이나 표준**이라는 점 — microservices.io 는 Chris Richardson 의 personal pattern catalog. 어떤 벤더의 공식 채택도 의미하지 않는다. strength 는 전부 `engineering-blog`.
|
||
- **"메시지 ID 기록과 비즈니스 데이터 갱신이 반드시 같은 트랜잭션이어야 한다"는 명시적 문장** — `C3`의 Does not prove 참고. 원문은 트랜잭션이 하나 시작된다는 것만 말하며, 비즈니스 엔티티 갱신이 그 안에 포함된다는 것은 패턴의 일반 관례로 추정될 뿐 이 페이지에서 직접 진술되지 않는다.
|
||
- PROCESSED_MESSAGES 별도 테이블 vs 비즈니스 엔티티 내장 중 **어느 쪽이 ca-skeleton 에 더 적합한지의 선택 기준** — `C5`의 Does not prove 참고, 본문은 옵션 존재만 언급
|
||
- Kafka 특유의 consumer rebalance / `max.poll` / manual ack 커밋 시점과 이 패턴의 상호작용
|
||
- Eventuate 프레임워크의 실제 구현 코드 세부(이 페이지는 "implements this pattern"이라고만 언급, 코드는 미첨부)
|
||
- **내 프로젝트에 적용하려면 추가 확인이 필요한 것**:
|
||
- ca-skeleton 의 `InboxStorePort` 구현이 (subscriberId, messageID) 복합 PK 와 동등한 유니크 제약을 실제로 갖는지 코드 검증
|
||
- inbox insert 와 비즈니스 write 가 실제로 동일 `@Transactional` 경계 안에서 커밋되는지 — 이 raw 자료만으로는 "그래야 한다"는 관례적 근거이지 ca-skeleton 코드의 검증 결과가 아님
|
||
- "비즈니스 엔티티 자체에 ID 저장" 변형을 채택할지 여부는 ca-skeleton 의 메시지-엔티티 매핑이 1:1 인 케이스에 한해 별도로 결정해야 함
|
||
|
||
## 메모
|
||
|
||
- 본 페이지는 Context / Problem / Solution / See also 4개 섹션으로만 구성된 매우 간결한 패턴 카탈로그 페이지이며, 시퀀스 다이어그램 이미지(`/i/IdempotentConsumer/IdempotentConsumer.png`) 하나를 포함하지만 alt-text 나 대체 설명 텍스트는 없다.
|
||
- "See also" 에 언급된 `Eventuate framework` 와 "blog post about this pattern"(`/post/microservices/patterns/2020/10/16/idempotent-consumer.html`) 은 더 상세한 구현을 담고 있을 가능성이 있음 — 필요 시 별도 raw 로 추가 조사.
|
||
- 동일 저자의 [[raw/official-docs/microservices-io-transactional-outbox]] 와 짝을 이루는 패턴(producer 측 outbox ↔ consumer 측 idempotent consumer). ca-skeleton 의 producer/consumer 양쪽 계약을 함께 볼 때 두 문서를 같이 참조할 것.
|
||
|
||
## Related
|
||
|
||
- 같은 주제 다른 official-doc:
|
||
- [[raw/official-docs/microservices-io-transactional-outbox]] (동일 저자, producer 측 짝 패턴)
|
||
- [[raw/official-docs/event-sourcing-vs-outbox-microservices-io]]
|
||
- [[raw/official-docs/outbox-skip-locked-microservices-io]]
|
||
- 이 자료를 인용하는 branch:
|
||
- [[raw/branch-notes/feature-kafka-consumer-inbox-contract]]
|
||
- 이 자료를 인용한 wiki 요약: (미작성)
|