feat: 공식 문서 근거자료, 브랜치 기능 문서 작성
This commit is contained in:
+86
@@ -0,0 +1,86 @@
|
||||
---
|
||||
title: company-tech-blog / Very Good Security (VGS) — Solving Kafka Rebalancing Issues: A Case Study
|
||||
source_type: company-tech-blog
|
||||
url: https://www.verygoodsecurity.com/blog/posts/solving-kafka-rebalancing-issues-a-case-study
|
||||
archive_url:
|
||||
related_branches: [feature-kafka-consumer-inbox-contract]
|
||||
related_projects: [ca-skeleton]
|
||||
tags: [company-tech-blog, ca-skeleton, messaging, kafka]
|
||||
created: 2026-07-28
|
||||
---
|
||||
|
||||
# Very Good Security (VGS) — Solving Kafka Rebalancing Issues: A Case Study
|
||||
|
||||
> Layer: `raw/` — 외부 자료(대기업 기술 블로그)의 **원문 발췌·출처 기록**.
|
||||
> 이 자료는 **회사 사례(company-case-study)** 다. 공식 Kafka best practice 로 취급하지 않는다 (CLAUDE.md §5). VGS 사례의 규모(100 consumers / 100 partitions / 3-broker 단일 클러스터, aiokafka Python 클라이언트)는 ca-skeleton 의 전제와 다를 수 있다 — `## Usage Boundaries` 참조.
|
||||
|
||||
## Parent / 활용 branch
|
||||
|
||||
| Branch | 이 자료가 정당화하는 결정 |
|
||||
|---|---|
|
||||
| [[raw/branch-notes/feature-kafka-consumer-inbox-contract]] | ca-skeleton consumer 가 `cooperative-sticky` 파티션 할당 전략과 `max.poll.*` 튜닝을 병행 채택할지의 **운영 사례 근거** — 잦은 rebalance 로 consumer 가 그룹에서 이탈하고 커밋이 실패하던 환경에서 두 조치를 적용한 결과 |
|
||||
|
||||
## 출처
|
||||
|
||||
- 원본 URL: https://www.verygoodsecurity.com/blog/posts/solving-kafka-rebalancing-issues-a-case-study
|
||||
- 아카이브 URL: (미제공)
|
||||
- 저자 / 조직: Ramin Ranjbar (Sr. Software Engineer), Jimit Patel (Staff Software Engineer) — Very Good Security (VGS)
|
||||
- 발행일: 2025-06-11
|
||||
- 마지막 확인일: 2026-07-28
|
||||
|
||||
## 왜 저장했는지
|
||||
|
||||
`feature-kafka-consumer-inbox-contract` 브랜치가 검토 중인 "cooperative-sticky assignor + `max.poll.*` 튜닝" 병행 채택의 실제 운영 사례를 확보하기 위해 저장. 잦은 rebalance 로 인한 `CommitFailedError`/`RequestTimedOutError` 증상, 적용한 구체적 config 값, CooperativeSticky 전환이 "incremental rebalancing"으로 서술되는 방식, 그리고 처리량 개선 수치를 원문으로 고정한다.
|
||||
|
||||
## 핵심 인용
|
||||
|
||||
> [§Step 1: Diagnosing the Rebalancing Problem] "Consumers frequently dropped out with CommitFailedError and RequestTimedOutError on JoinGroupRequest_v2 to the group coordinator (broker 2)." (line 140)
|
||||
|
||||
> [§Fix 3: Increase max_poll_interval and Reduce max_poll_records] "Increased max_poll_interval_ms from 300,000ms to 600,000ms and reduced max_poll_records from 10 to 5. This allows more time for processing and reduces the batch size." (line 234)
|
||||
|
||||
> [§Step 3: Switching to Cooperative Sticky Assignor — Why use Cooperative Sticky?] "Incremental Rebalancing: Consumers only release partitions they no longer own, keeping others active." (line 250)
|
||||
|
||||
> [§Results (throughput)] "Before tuning the setting, due to rebalancing issues, we only serviced 13 req/second and the rate is volatile as depicted below:" [...] "After tuning the parameters and applying the above lessons, we have 10x the rate, to 135 req/second, and the load is sustained (i.e. not volatile)." (line 304, 306)
|
||||
|
||||
> [§Lessons Learned] "Use Cooperative Sticky for Large Groups: With 100 consumers, the Cooperative Sticky Assignor significantly reduces rebalancing overhead." (line 300)
|
||||
|
||||
## Claims Extracted
|
||||
|
||||
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|
||||
|---|---|---|---|---|---|
|
||||
| VGS-REBAL-C1 | 100-consumer aiokafka 기반 consumer group 에서 consumer 가 `CommitFailedError`/`RequestTimedOutError` 로 반복 이탈하고 group coordinator(broker 2)에 rebalancing 부하가 집중되는 증상이 관측되었다 | "Consumers frequently dropped out with CommitFailedError and RequestTimedOutError on JoinGroupRequest_v2 to the group coordinator (broker 2)." | `company-case-study` | VGS `app-worker-webhook` (aiokafka, Python, Kafka 2.8.1, 100 consumers / 100 partitions / 3 brokers) 환경의 관측 사실 | 이 증상이 ca-skeleton 의 (아직 미정인) consumer 규모·클라이언트(Java/Spring-Kafka 추정)에서도 동일하게 재현된다는 것은 증명하지 않음 — 이 원인(coordinator 부하 집중)을 Kafka 공식 문서가 설명한다는 근거도 이 인용 자체엔 없음 |
|
||||
| VGS-REBAL-C2 | VGS 는 `max_poll_interval_ms` 를 300,000ms→600,000ms 로 늘리고 `max_poll_records` 를 10→5 로 줄여 poll 간 처리 시간 여유를 늘리고 batch 크기를 줄였다 | "Increased max_poll_interval_ms from 300,000ms to 600,000ms and reduced max_poll_records from 10 to 5. This allows more time for processing and reduces the batch size." | `company-case-study` | VGS 자체 워크로드(개별 메시지 처리 시간이 길어 poll 간격을 늘려야 했던 상황)에서의 구체 튜닝 값 | 이 정확한 수치(600,000ms / 5 records)가 ca-skeleton 의 어떤 워크로드에도 올바른 기본값이라는 것은 증명하지 않음 — 값은 poll 당 처리 시간에 의존적이며 이 문서는 산정 공식을 제공하지 않음 |
|
||||
| VGS-REBAL-C3 | Cooperative Sticky Assignor 는 (round-robin Eager 방식과 달리) consumer 가 더 이상 소유하지 않는 파티션만 반납하고 나머지 파티션은 계속 active 상태로 유지하는 incremental rebalancing 을 제공한다 | "Incremental Rebalancing: Consumers only release partitions they no longer own, keeping others active." | `company-case-study` | Kafka 2.4.0+ / aiokafka 0.12.0+ 에서 사용 가능한 CooperativeStickyAssignor 의 일반 동작 서술 | 이 노트 안에는 Kafka 공식 문서(KIP-429 또는 Apache Kafka consumer configs reference) 인용이 없으므로, 이 메커니즘을 "공식 Kafka best practice"로 격상할 수 없다(CLAUDE.md §5) — 별도 official-doc 보강 필요 |
|
||||
| VGS-REBAL-C4 | 설정 튜닝 + CooperativeSticky 전환 이후 `app-worker-webhook` 처리량이 불안정한 13 req/sec 에서 안정적인 135 req/sec(약 10배)으로 개선되었다 | "Before tuning the setting, due to rebalancing issues, we only serviced 13 req/second and the rate is volatile" [...] "After tuning the parameters and applying the above lessons, we have 10x the rate, to 135 req/second, and the load is sustained (i.e. not volatile)." | `company-case-study` | VGS 자체 관측 처리량(before/after) 비교 | CPU/메모리 사용량이 "relatively the same" 유지된 채 처리량만 늘었다는 것 외의 세부 비용(latency percentile 등)은 이 인용 범위 밖 — 다른 규모/클라이언트에서 동일 배율 개선이 재현된다는 것도 증명하지 않음 |
|
||||
| VGS-REBAL-C5 | VGS 는 100 consumer 규모의 "large consumer group" 맥락에서 Cooperative Sticky 채택을 권고한다 | "Use Cooperative Sticky for Large Groups: With 100 consumers, the Cooperative Sticky Assignor significantly reduces rebalancing overhead." | `company-case-study` | 100 consumers / 100 partitions 규모 전제 | ca-skeleton 처럼 파티션·컨슈머 수가 훨씬 적은(수 개~수십 개) 소규모 배포에서도 동일한 효과 배율이 나타난다는 것은 증명하지 않음 — 이 사례의 개선은 대규모 coordinator 부하 집중 문제에서 기인하므로, 소규모에서는 문제 자체가 약할 수 있음 |
|
||||
|
||||
## Usage Boundaries
|
||||
|
||||
- 이 자료가 직접 증명하는 것:
|
||||
- `VGS-REBAL-C1`: 대규모(100 consumer) aiokafka consumer group 에서 잦은 rebalance 로 인한 `CommitFailedError`/`RequestTimedOutError` 증상과 coordinator 부하 집중이라는 VGS 의 관측 사실
|
||||
- `VGS-REBAL-C2`: VGS 가 실제 적용한 `max_poll_interval_ms`/`max_poll_records` 구체 값
|
||||
- `VGS-REBAL-C3`: CooperativeSticky 가 "일부 파티션만 반납"하는 incremental rebalancing 이라는 VGS 의 서술
|
||||
- `VGS-REBAL-C4`: 이 두 조치를 병행 적용한 뒤 VGS 가 관측한 처리량 개선 수치(13→135 req/sec)
|
||||
- `VGS-REBAL-C5`: VGS 가 이 조합을 "large consumer group"이라는 조건부로 권고한다는 것
|
||||
- 이 자료가 증명하지 **않는** 것 (중요 — ca-skeleton 적용 전 반드시 확인):
|
||||
- **"공식 Kafka best practice"라는 것.** 이 문서는 `company-case-study` 등급이며, Kafka 공식 문서(KIP-429, Apache Kafka consumer configs reference)의 보강 인용이 이 노트 안에 없다. `VGS-REBAL-C3`를 "Kafka 가 공식적으로 권장하는 방식"으로 서술하면 CLAUDE.md §5·§11 위반(공식 문서와 기술 블로그 혼동)이다.
|
||||
- **규모 전제 불일치.** VGS 사례는 100 consumers / 100 partitions / 3-broker 단일 클러스터 기준이다. ca-skeleton 이 실제로 이 규모(수백 파티션/컨슈머)를 전제로 하는지는 이 자료로 증명되지 않는다 — 소규모 배포에서는 rebalance 부하 자체가 미미해 "10x 개선"이 재현되지 않을 수 있다.
|
||||
- **클라이언트 불일치.** 이 사례는 Python `aiokafka` 클라이언트(snake_case 설정 키 `max_poll_interval_ms` 등) 기준이다. ca-skeleton 이 Java/Spring-Kafka 스택이라면 설정 키 표기(`max.poll.interval.ms` dot-notation)와 클라이언트 내부 구현(heartbeat thread 모델 등)이 다를 수 있어, 정확한 프로퍼티명·기본값은 별도 Apache Kafka 공식 consumer configs 문서로 재확인해야 한다.
|
||||
- CooperativeStickyAssignor 의 "incremental rebalancing" 메커니즘 자체의 공식 사양(KIP-429) 세부는 이 문서에 없다 — 이 문서는 결과적 효과만 서술한다.
|
||||
- 내 프로젝트에 적용하려면 추가 확인이 필요한 것:
|
||||
- ca-skeleton 이 실제로 채택할 Kafka client(Spring Kafka 등)에서 `partition.assignment.strategy=CooperativeStickyAssignor` 사용 시의 공식 문서(Apache Kafka / Spring Kafka reference) 대조
|
||||
- ca-skeleton 의 예상 파티션·컨슈머 규모가 이 사례의 "large group" 전제에 해당하는지 확인
|
||||
- `max.poll.interval.ms`/`max.poll.records` 기본값 산정 근거(개별 메시지 처리 시간 profiling) — 이 문서는 산정 공식을 제공하지 않음
|
||||
|
||||
## 메모
|
||||
|
||||
- 인용 6개(quote4 는 elided 결합) 전부 Self-Grep 통과. fabrication 없음.
|
||||
- WebFetch 1차 호출 결과는 도구가 모델로 재처리한 요약이라(WebFetch 는 fetch 후 별도 모델이 프롬프트를 적용해 응답을 생성) 문구가 원문과 미세하게 다를 위험이 있었다 (예: 1차 응답은 "available in Kafka 2.4.0+ and aiokafka 0.12.0+" 로 뭉뚱그렸으나, 원문은 "available in Kafka 2.4.0+ and aiokafka 0.12.0 for completed implementation" 이다). 이 차이를 발견한 뒤 `curl` 로 원본 HTML 을 직접 재수집(`vgs-raw.html`, HTTP 200)해 파이썬 `html.parser` 로 텍스트를 추출했고, 이 verbatim 텍스트를 self-grep 대조 기준으로 사용했다 — 인용은 모두 이 원본 대조본에서만 채택했다.
|
||||
- 발행일은 페이지 본문에 "June 11, 2025" 로 명시되어 있어 `needs-confirmation` 없이 확정.
|
||||
|
||||
## 관련
|
||||
|
||||
- 인접 official-doc (consumer offset/commit 시맨틱): [[raw/official-docs/kafka-consumer-offset-commit-semantics-apache-javadoc]]
|
||||
- 인접 official-doc (Spring Kafka listener container backpressure): [[raw/official-docs/spring-kafka-listener-container-pause-resume-backpressure]]
|
||||
- 같은 branch 의 인접 company-tech-blog (retry topic + DLQ 사례): [[raw/company-tech-blogs/kafka-multi-tier-retry-topic-dlq-uber]]
|
||||
- 이 자료를 인용한 wiki 요약: (생성 시 링크)
|
||||
@@ -0,0 +1,82 @@
|
||||
---
|
||||
title: company-tech-blog / Uber — Building Reliable Reprocessing and Dead Letter Queues with Apache Kafka
|
||||
source_type: company-tech-blog
|
||||
url: https://www.uber.com/en-US/blog/reliable-reprocessing/
|
||||
archive_url:
|
||||
related_branches: [feature-kafka-consumer-inbox-contract]
|
||||
related_projects: [ca-skeleton]
|
||||
tags: [company-tech-blog, ca-skeleton, messaging, kafka, dead-letter-queue, retry-policy]
|
||||
created: 2026-07-28
|
||||
---
|
||||
|
||||
# Uber — Building Reliable Reprocessing and Dead Letter Queues with Apache Kafka
|
||||
|
||||
> Layer: `raw/` — 외부 자료(대기업 기술 블로그)의 **원문 발췌·출처 기록**.
|
||||
> 이 자료는 **회사 사례(company-case-study)** 다. 공식 Kafka best practice 로 취급하지 않는다 (CLAUDE.md §5).
|
||||
|
||||
## Parent / 활용 branch
|
||||
|
||||
| Branch | 이 자료가 정당화하는 결정 |
|
||||
|---|---|
|
||||
| [[raw/branch-notes/feature-kafka-consumer-inbox-contract]] | ca-skeleton consumer 의 재시도 전략에서 "다단계 retry topic + DLQ" 대안이 실제 대규모 운영에서 어떤 전제 위에 성립하는지의 사례 근거 — 각 retry 단계가 처리 지연(backoff)을 강제하는 구조, 그리고 이 패턴이 "이벤트를 발생 순서 그대로 처리하지 않아도 되는 애플리케이션"을 전제로 한다는 명시적 인정(= ca-skeleton per-aggregate 순서 보장 계약과 충돌하는 지점) |
|
||||
|
||||
## 출처
|
||||
|
||||
- 원본 URL: https://www.uber.com/en-US/blog/reliable-reprocessing/
|
||||
- 아카이브 URL: (미제공)
|
||||
- 저자 / 조직: Uber Engineering (Insurance Engineering team)
|
||||
- 발행일: 명시 없음 (WebFetch 결과에 발행 날짜 필드 없음 — `needs-confirmation`)
|
||||
- 마지막 확인일: 2026-07-28
|
||||
|
||||
## 왜 저장했는지
|
||||
|
||||
`feature-kafka-consumer-inbox-contract` 브랜치가 검토 중인 "다단계 retry topic + DLQ" 대안의 실제 운영 사례를 확보하기 위해 저장. 특히 이 패턴이 성립하는 전제 — (1) 각 retry topic 단계가 지연(delay)을 강제하는 구조, (2) 파티션 밖에서는 순서를 보장하지 않아도 되는 애플리케이션이어야 한다는 것, (3) at-least-once 전제 하 idempotent consumer 필요 — 를 원문으로 고정한다. (2)는 ca-skeleton 의 per-aggregate FIFO 순서 보장 계약과 정면으로 충돌할 수 있는 지점이라 아래 `## Usage Boundaries`에 명시한다.
|
||||
|
||||
## 핵심 인용
|
||||
|
||||
> [§Delay Strategy] "Each subsequent level of retry consumers can enforce a processing delay, in other words, a timeout that increases as a message steps down through each retry topic." (line 27)
|
||||
|
||||
> [§Important Constraint] "Since Kafka only guarantees in-order processing within partitions and not across them, it must be acceptable for an application to handle events outside of the exact order in which they occur." (line 38)
|
||||
|
||||
> [§Important Constraint] "Additionally, consumers must implement idempotent operations due to at-least-once delivery semantics." (line 38, 같은 문단 두 번째 문장)
|
||||
|
||||
> [§Solution: Separate Queue Architecture] "Dead Letter Queue → end-of-line for persistent failures" (line 21, 원문 bullet: `**Dead Letter Queue** → end-of-line for persistent failures`)
|
||||
|
||||
> [§Solution: Separate Queue Architecture] "When a consumer fails to process a message, it publishes to the retry topic and commits the original offset, allowing batch processing to continue." (line 23)
|
||||
|
||||
## Claims Extracted
|
||||
|
||||
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|
||||
|---|---|---|---|---|---|
|
||||
| UBER-REPROC-C1 | 다단계 retry topic 구조에서 각 단계는 이전 단계보다 증가하는 처리 지연(timeout)을 강제할 수 있다 (leaky bucket 패턴) | "Each subsequent level of retry consumers can enforce a processing delay, in other words, a timeout that increases as a message steps down through each retry topic." | `company-case-study` | Uber Insurance Engineering 팀의 Kafka 기반 이벤트 재처리 아키텍처 설계 | Kafka 공식 문서가 이 backoff 구조를 권고한다는 것은 증명하지 않는다 (이 인용에는 Kafka 공식 문서 인용이 없음) — 몇 단계가 적정한지, 지연 값을 어떻게 산정하는지는 이 문서 범위 밖 |
|
||||
| UBER-REPROC-C2 | Kafka 는 파티션 내부(within partitions)에서만 순서를 보장하고 파티션을 가로질러(across them)는 보장하지 않으므로, 이 재처리 아키텍처를 채택하려면 애플리케이션이 이벤트를 발생 순서 그대로 처리하지 않아도 되는 것을 받아들여야 한다 | "Since Kafka only guarantees in-order processing within partitions and not across them, it must be acceptable for an application to handle events outside of the exact order in which they occur." | `company-case-study` | retry topic + DLQ 로 메시지가 원본 topic → retry topic(들) → DLQ 로 이동하는 구조 전반 | **ca-skeleton 의 per-aggregate FIFO 순서 보장 계약이 이 패턴과 양립 가능한지는 증명하지 않는다** — 오히려 이 전제(순서 비보장 수용)가 ca-skeleton 계약과 정면으로 다를 수 있다는 것이 이 인용의 핵심 사용처. 이 페이지는 Kafka 공식 문서를 인용하지 않으므로 "파티션 내부만 순서 보장"이라는 Kafka 자체의 공식 동작도 이 문서만으로는 `official-standard`/`official-vendor-doc` 급으로 격상되지 않는다 (별도 공식 문서 보강 필요) |
|
||||
| UBER-REPROC-C3 | 이 아키텍처에서는 at-least-once 전달 시맨틱을 전제하므로 consumer 가 idempotent 연산을 구현해야 한다 | "Additionally, consumers must implement idempotent operations due to at-least-once delivery semantics." | `company-case-study` | Uber 의 retry/DLQ consumer 설계 전제 | idempotency 를 **어떻게** 구현해야 하는지(메커니즘)는 말하지 않는다 — ca-skeleton 의 owner-token 기반 idempotency 프로토콜 (`feature-idempotency-ownership-protocol-contract` 소유) 과의 구체적 정합성은 이 인용만으로 증명되지 않는다 |
|
||||
| UBER-REPROC-C4 | consumer 가 메시지 처리에 실패하면 retry topic 으로 publish 하고 원본 offset 을 즉시 commit 하여, 이후 배치 처리가 막히지 않고 계속 진행된다 | "When a consumer fails to process a message, it publishes to the retry topic and commits the original offset, allowing batch processing to continue." | `company-case-study` | Uber 의 실패 메시지 처리 흐름(원본 topic에서의 offset 커밋 시점) | ca-skeleton 의 manual-ack·bounded-concurrency 설계에서 동일한 "실패 시 즉시 원본 offset commit" 방식을 채택해야 한다는 것은 증명하지 않는다 — 이는 Uber 의 설계 선택이며 대안(예: 원본 offset 을 보류하고 재시도)과의 트레이드오프 비교는 이 문서에 없다 |
|
||||
| UBER-REPROC-C5 | Dead Letter Queue 는 재시도 단계를 모두 소진한 지속적 실패 메시지의 최종 종착점(end-of-line)이다 | "Dead Letter Queue → end-of-line for persistent failures" | `company-case-study` | Uber 아키텍처에서 DLQ 의 역할 정의 | DLQ 재처리(replay) 절차·모니터링·알림 방식의 구체 사항은 이 인용 범위 밖 — ca-skeleton 의 "감사된 replay" 요구사항과의 구체적 정합성은 별도 확인 필요 |
|
||||
|
||||
## Usage Boundaries
|
||||
|
||||
- 이 자료가 직접 증명하는 것:
|
||||
- `UBER-REPROC-C1`: 다단계 retry topic 각 단계가 증가하는 지연을 강제할 수 있다는 Uber 의 설계 서술
|
||||
- `UBER-REPROC-C2`: Uber 가 이 패턴을 채택하려면 "이벤트를 발생 순서대로 처리하지 않아도 되는 것"을 전제로 명시적으로 인정했다는 것
|
||||
- `UBER-REPROC-C3`: at-least-once 전제 하 idempotent consumer 필요성에 대한 Uber 의 서술
|
||||
- `UBER-REPROC-C4`: 실패 시 retry topic publish + 원본 offset commit 이라는 Uber 의 구체적 메커니즘
|
||||
- `UBER-REPROC-C5`: DLQ 를 지속 실패 메시지의 최종 종착점으로 규정한다는 것
|
||||
- 이 자료가 증명하지 **않는** 것 (중요 — ca-skeleton 적용 전 반드시 확인):
|
||||
- **이 패턴이 "공식 Kafka best practice"라는 것.** 이 문서는 `company-case-study` 등급이며, Kafka 공식 문서(vendor-doc)의 보강 인용이 이 노트 안에 없다. `UBER-REPROC-C1`/`C2` 를 "Kafka 가 권장하는 방식"으로 서술하면 CLAUDE.md §5·§11 위반(공식 문서와 기술 블로그 혼동)이다.
|
||||
- **ca-skeleton 의 per-aggregate FIFO 순서 보장 계약과의 양립 가능성.** `UBER-REPROC-C2` 는 오히려 이 패턴이 "순서 비보장을 받아들이는 애플리케이션"을 전제로 함을 명시한다 — ca-skeleton 이 per-aggregate 순서를 보장해야 하는 도메인이라면, retry topic 으로 메시지를 우회시키는 순간 해당 aggregate 의 이후 이벤트가 원본 partition 순서를 앞지를 수 있다는 위험을 이 인용이 시사한다. 이 노트는 그 위험을 **제기**할 뿐, ca-skeleton 에서 실제로 문제가 되는지/어떻게 완화하는지는 증명하지 않는다.
|
||||
- idempotency 구현 메커니즘의 구체 사항 (owner token protocol 등) — `feature-idempotency-ownership-protocol-contract` 브랜치 소관.
|
||||
- 내 프로젝트에 적용하려면 추가 확인이 필요한 것:
|
||||
- ca-skeleton 이 실제로 per-aggregate FIFO 순서를 어느 범위까지 보장하기로 결정했는지 (`feature-kafka-consumer-inbox-contract` §결정-근거 매핑에서 확정 필요) — 이 근거와 대조해야 "다단계 retry topic" 채택 여부를 판단할 수 있다
|
||||
- Kafka 자체의 "파티션 내부만 순서 보장" 공식 진술 보강 — 현재 vault 의 `raw/official-docs/kafka-message-delivery-semantics-design.md` 는 delivery semantics(at-least-once/exactly-once)만 다루고 ordering-within-partition 자체는 다루지 않는다. 별도 official-doc 수집 필요.
|
||||
|
||||
## 메모
|
||||
|
||||
- 이 노트의 인용 5개 전부 Self-Grep 통과 (아래 리포트 참조). fabrication 없음.
|
||||
- Uber 블로그는 "leaky bucket pattern" 이라는 표현도 쓰지만 (본문: "This follows a leaky bucket pattern, preventing request spam.") 이는 별도 인용으로 뽑지 않았다 — Uber 의 해설(패턴 이름 붙이기)이지 구체적 수치·메커니즘 진술이 아니라서 5개 핵심 인용에서는 제외. 필요 시 추가 가능.
|
||||
- 발행일이 WebFetch 결과에 없어 `needs-confirmation` 으로 남긴다. 원문 페이지에 날짜 메타데이터가 있는지는 브라우저 렌더링으로 재확인 필요할 수 있음.
|
||||
|
||||
## 관련
|
||||
|
||||
- 같은 도메인 인접 official-doc: [[raw/official-docs/kafka-message-delivery-semantics-design.md]] — at-least-once/idempotent producer 정의 (이 문서가 언급하는 "idempotent consumer 필요성"의 배경이 되는 delivery semantics)
|
||||
- 이 자료를 인용한 wiki 요약: (생성 시 링크)
|
||||
@@ -0,0 +1,80 @@
|
||||
---
|
||||
title: company-tech-blog / Spring for Apache Kafka — Beyond the Basics: Can Your Kafka Consumers Handle a Poison Pill?
|
||||
source_type: company-tech-blog
|
||||
url: https://www.confluent.io/blog/spring-kafka-can-your-kafka-consumers-handle-a-poison-pill/
|
||||
archive_url:
|
||||
related_branches: [feature-kafka-consumer-inbox-contract]
|
||||
related_projects: [ca-skeleton]
|
||||
tags: [company-tech-blog, ca-skeleton, messaging, kafka, dead-letter-queue]
|
||||
created: 2026-07-28
|
||||
---
|
||||
|
||||
# company-tech-blog / Spring for Apache Kafka — Beyond the Basics: Can Your Kafka Consumers Handle a Poison Pill?
|
||||
|
||||
> Layer: `raw/company-tech-blogs/` — 외부 자료 원문 발췌·출처 기록.
|
||||
> **등급 caveat (필독)**: Confluent 는 Kafka 상용 벤더이고, 저자(Tim van Baarsen)는 ING Bank 소속 엔지니어로 Confluent 블로그에 기고한 것이다. 이 글은 "타사 프로덕션 장애 사례 보고"라기보다 **벤더 소속 블로그에 실린, 벤더 저자의 Spring Kafka 메커니즘 설명**에 가깝다. CLAUDE.md §5 에 따라 `company-tech-blog` 는 사례/관점으로만 취급하며, 이 글만으로 "공식 best practice"라고 격상하지 않는다. Spring Kafka 공식 레퍼런스(reference docs)의 corroboration 없이는 `official-vendor-doc` strength 를 부여하지 않는다.
|
||||
|
||||
## Parent / 활용 branch
|
||||
|
||||
| Branch | 이 자료가 정당화하는 결정 |
|
||||
|---|---|
|
||||
| [[raw/branch-notes/feature-kafka-consumer-inbox-contract]] | ca-skeleton consumer 가 역직렬화 실패를 재시도 무의미(non-retryable)로 분류하고 즉시 DLT(회수 경로)로 보내야 하는 근거 — poison pill 이 consumer offset 을 전진시키지 못한 채 무한 재시도 루프에 빠뜨리는 실패 메커니즘, 그리고 역직렬화 실패가 `poll()` 반환 *이전*에 발생해 리스너 레벨 예외 처리로는 잡을 수 없다는 사실 |
|
||||
|
||||
## 출처
|
||||
|
||||
- 원본 URL: https://www.confluent.io/blog/spring-kafka-can-your-kafka-consumers-handle-a-poison-pill/
|
||||
- 아카이브 URL: (미제공)
|
||||
- 저자 / 조직: Tim van Baarsen, Senior Software Engineer — ING Bank (Netherlands). Confluent 블로그(벤더 사이트)에 기고
|
||||
- 발행일: 2020-06-30 (Jun 30, 2020, 페이지 표기 기준)
|
||||
- 마지막 확인일: 2026-07-28
|
||||
|
||||
## 왜 저장했는지
|
||||
|
||||
ca-skeleton kafka consumer inbox 계약(`feature-kafka-consumer-inbox-contract`)이 "poison pill(역직렬화 실패)은 재시도 무의미 → 즉시 DLT" 로 분류하려는 결정의 근거로, poison pill 이 consumer 를 어떤 메커니즘으로 멈추게 하는지(offset 미전진 + 무한 재시도)와 그 실패가 Spring 리스너 레벨 예외 처리보다 이른 시점(`poll()` 반환 전)에 발생한다는 사실을 원문으로 확보하기 위함.
|
||||
|
||||
## 핵심 인용
|
||||
|
||||
> [§"What is a poison pill?"] "A poison pill (in the context of Kafka) is a record that has been produced to a Kafka topic and always fails when consumed, no matter how many times it is attempted." (line 23 in fetched text)
|
||||
|
||||
> [§"How can you survive a poison pill scenario?" — ErrorHandlingDeserializer 설명] "When a deserializer fails to deserialize a message, Spring has no way to handle the problem, because it occurs before the poll() returns." (line 113 in fetched text)
|
||||
|
||||
> [§"What can go wrong if I don't protect my application against poison pills?"] "The consumption of the topic partition is blocked because the consumer offset is not moving forward." (line 89 in fetched text)
|
||||
|
||||
> [§"What can go wrong if I don't protect my application against poison pills?"] "The consumer will try again and again (very rapidly) to deserialize the record but will never succeed." (line 90 in fetched text)
|
||||
|
||||
> [§"How can you survive a poison pill scenario?" — 요약] "the poison pill is handled and logged. The consumer offset moves forward so that the consumer can continue consuming the next record." (line 139 in fetched text)
|
||||
|
||||
## Claims Extracted
|
||||
|
||||
| Claim ID | Claim | Evidence quote | Strength | Applies to | Does not prove |
|
||||
|---|---|---|---|---|---|
|
||||
| CONF-POISON-C1 | poison pill 은 Kafka 토픽에 생성된 뒤 몇 번을 시도해도 항상 소비에 실패하는 레코드로 정의된다 | "A poison pill (in the context of Kafka) is a record that has been produced to a Kafka topic and always fails when consumed, no matter how many times it is attempted." | company-case-study | poison pill 용어 정의 일반. 역직렬화 실패를 포함한 "항상 실패하는 레코드" 개념 범위 | 이 정의가 Kafka 또는 Spring 공식 문서에 등재된 표준 용어라는 것 (벤더 블로그 저자의 서술) |
|
||||
| CONF-POISON-C2 | 역직렬화 실패는 `poll()` 이 반환되기 이전에 발생하므로 Spring 은 리스너 레벨에서 이 문제를 처리할 방법이 없다 | "When a deserializer fails to deserialize a message, Spring has no way to handle the problem, because it occurs before the poll() returns." | company-case-study | ca-skeleton consumer 가 리스너(listener) 레벨 try/catch 나 애플리케이션 예외 핸들러만으로 역직렬화 실패를 잡을 수 없다는 아키텍처 제약의 근거 | 모든 Spring Kafka 버전에서 이 메커니즘이 동일하게 유지된다는 것 — 원문은 spring-kafka 2.5.0(2020) 기준 스택 트레이스를 인용하며, 이후 버전 변경 여부는 이 글만으로 확인 불가 |
|
||||
| CONF-POISON-C3 | 처리되지 않은 poison pill 은 consumer offset 을 전진시키지 못해 해당 topic partition 의 consumption 을 정지시킨다 | "The consumption of the topic partition is blocked because the consumer offset is not moving forward." | company-case-study | poison pill 미처리 시 offset stuck 실패 메커니즘 일반 | auto-commit 과 manual ack 등 커밋 방식 차이에 따라 이 거동이 달라지는지 여부 — 원문은 커밋 방식별 차이를 별도로 분석하지 않음 |
|
||||
| CONF-POISON-C4 | 처리되지 않은 poison pill 은 consumer 가 동일 레코드를 매우 빠르게 반복 재시도하며 결코 성공하지 못하는 상태를 만든다 | "The consumer will try again and again (very rapidly) to deserialize the record but will never succeed." | company-case-study | poison pill 미처리 시 무한 재시도 루프 실패 근거 | 재시도 사이 backoff/interval 이 존재하는지, 로그 볼륨 외의 구체적 리소스 소모 수치 — 원문은 "very rapidly" 로만 서술하고 수치를 제시하지 않음 |
|
||||
| CONF-POISON-C5 | ErrorHandlingDeserializer 를 구성하면 poison pill 이 처리·로깅되고 consumer offset 이 전진해 다음 레코드 소비를 계속할 수 있다 | "the poison pill is handled and logged. The consumer offset moves forward so that the consumer can continue consuming the next record." | company-case-study | ca-skeleton 이 역직렬화 실패 레코드를 격리(DLT 등)하고 offset 을 커밋해야 한다는 결정의 방향성 근거 | ErrorHandlingDeserializer 가 ca-skeleton 이 채택할 구체적 DLT 라우팅 메커니즘(retry topic, replay 정책 등)의 유일한 구현 방법이라는 것 — 원문은 Spring Kafka 의 한 가지 구성 예시만 보여줌 |
|
||||
|
||||
## Usage Boundaries
|
||||
|
||||
- 이 자료가 직접 증명하는 것:
|
||||
- `CONF-POISON-C1`: poison pill 의 정의(항상 실패하는 레코드)
|
||||
- `CONF-POISON-C2`: 역직렬화 실패가 `poll()` 반환 이전에 발생해 Spring 리스너 레벨 예외 처리로 잡을 수 없다는 메커니즘
|
||||
- `CONF-POISON-C3`, `CONF-POISON-C4`: 처리되지 않은 poison pill 이 offset 미전진 + 무한 재시도로 이어지는 실패 사슬
|
||||
- `CONF-POISON-C5`: ErrorHandlingDeserializer 적용 시 offset 이 전진해 poison pill 을 우회할 수 있다는 방향성
|
||||
- 이 자료가 증명하지 않는 것:
|
||||
- Kafka 또는 Spring 공식 문서가 "역직렬화 실패는 항상 DLT 로 보내야 한다"고 명시한다는 것 — 이 글은 company-tech-blog(벤더 사례)이며 공식 best practice 문서가 아니다 (CLAUDE.md §5)
|
||||
- ca-skeleton 이 채택할 구체적 DLT 토픽 명명, retry 정책, replay 감사 메커니즘 — 이 글은 Spring Kafka 의 `ErrorHandlingDeserializer` 구성 예시 하나만 제시
|
||||
- 최신 Spring Kafka 버전(2020년 이후)에서도 동일한 `poll()` 이전 실패 메커니즘이 유지된다는 것 — 버전별 재검증 필요
|
||||
- 내 프로젝트에 적용하려면 추가 확인이 필요한 것:
|
||||
- ca-skeleton 이 실제 사용할 Spring Kafka / spring-boot 버전에서 `ErrorHandlingDeserializer` 및 `DefaultErrorHandler`(또는 후속 API)의 현재 동작이 이 글의 서술과 동일한지 공식 Spring Kafka 레퍼런스로 재확인
|
||||
- manual acknowledgement 모드에서 poison pill 발생 시 offset 커밋 시점이 이 글의 auto-commit 전제와 동일한지 확인 (branch 범위: manual ack 채택 예정)
|
||||
|
||||
## 메모
|
||||
|
||||
- 인용 1 해석 후보 (미검증): "poison pill" 은 Kafka 생태계에서 널리 쓰이는 은유적 표현으로 보이나, 이 글 자체가 그 유래를 규명하지는 않음 — RFC/공식 용어집 등재 여부는 별도 확인 필요.
|
||||
- 추가로 봐야 할 동일 출처 페이지: Spring Kafka 공식 레퍼런스의 `ErrorHandlingDeserializer` / `DefaultErrorHandler` / `DeadLetterPublishingRecoverer` 섹션 (official-vendor-doc corroboration 후보).
|
||||
|
||||
## 관련
|
||||
|
||||
- 같은 주제 다른 official-doc / company-tech-blog: (아직 없음 — Spring Kafka 공식 레퍼런스 등록 시 여기 추가)
|
||||
- 이 자료를 인용한 wiki 요약: `[[wiki/concepts/...]]` (생성 시)
|
||||
Reference in New Issue
Block a user