102 lines
14 KiB
Markdown
102 lines
14 KiB
Markdown
---
|
||
title: official-doc / Spring for Apache Kafka — DefaultErrorHandler Fatal Exceptions, BackOff Thread Suspension, and Dead Letter Topic Defaults
|
||
source_type: official-doc
|
||
url: https://docs.spring.io/spring-kafka/reference/kafka/annotation-error-handling.html
|
||
archive_url:
|
||
related_branches: [feature-kafka-consumer-inbox-contract]
|
||
related_projects: [ca-skeleton]
|
||
tags: [official-doc, ca-skeleton, messaging, kafka, dead-letter-queue, exponential-backoff]
|
||
created: 2026-07-28
|
||
---
|
||
|
||
# Spring for Apache Kafka — DefaultErrorHandler Fatal Exceptions, BackOff Thread Suspension, and Dead Letter Topic Defaults
|
||
|
||
> Layer: `raw/` — 외부 자료(공식 문서)의 **원문 발췌·출처 기록**.
|
||
> 문서 버전: Spring for Apache Kafka **4.1.0** reference ("Stable" 채널) — "Using Spring for Apache Kafka" §"Handling Exceptions".
|
||
|
||
## source_type
|
||
|
||
`official-doc` — Spring for Apache Kafka 공식 reference 문서 (docs.spring.io, Broadcom/VMware Tanzu 가 관리하는 Spring 프로젝트).
|
||
|
||
## Parent / 활용 branch
|
||
|
||
| Branch | 이 자료가 정당화하는 결정 |
|
||
|---|---|
|
||
| [[raw/branch-notes/feature-kafka-consumer-inbox-contract]] | (1) poison/역직렬화 예외 분류 시 `DefaultErrorHandler` 기본 fatal 예외 6종을 non-retryable 기준으로 삼을 근거, (2) blocking retry 의 backoff 대기가 consumer 스레드를 정지시키므로 `max.poll.interval.ms` 를 넘는 지연에는 `ContainerPausingBackOffHandler` 가 필요하다는 메커니즘 근거, (3) DLT 기본 명명(`<originalTopic>-dlt`)·파티션 요건과 recoverer 가 producer(`KafkaTemplate`/`KafkaOperations`)를 요구한다는 근거, (4) recoverer 를 명시 구성하지 않으면 재시도 소진 후 기본 동작이 "로그만"이라는 근거 |
|
||
|
||
## 출처
|
||
|
||
- 원본 URL: https://docs.spring.io/spring-kafka/reference/kafka/annotation-error-handling.html
|
||
- 아카이브 URL: (미제공)
|
||
- 저자 / 조직: Spring team (Broadcom / VMware Tanzu — Spring for Apache Kafka 프로젝트)
|
||
- 발행일: 명시 없음 (버전 4.1.0 reference, "Stable" 채널 — 문서 자체에 발행 연월일 필드 없음)
|
||
- 마지막 확인일: 2026-07-28
|
||
|
||
## 왜 저장했는지
|
||
|
||
`feature-kafka-consumer-inbox-contract` 브랜치가 poison exception 분류·blocking retry backoff·DLT 명명·기본 recoverer 동작을 결정할 때 필요한 **공식 기본값 사실**을 확보하기 위함. 어떤 예외를 "재시도 무의미"로 볼지, backoff 를 얼마로 잡을지, DLT 토픽명을 어떻게 override 할지는 이 문서가 아니라 branch 의 별도 결정이며, 이 문서는 그 결정이 마주치는 **프레임워크 기본 동작의 경계**만 증명한다.
|
||
|
||
## 핵심 인용
|
||
|
||
> [§DefaultErrorHandler — Skipping Retries for Specific Exceptions] "The DefaultErrorHandler considers certain exceptions to be fatal, and retries are skipped for such exceptions; the recoverer is invoked on the first failure. The exceptions that are considered fatal, by default, are:
|
||
>
|
||
> DeserializationException
|
||
> MessageConversionException
|
||
> ConversionException
|
||
> MethodArgumentResolutionException
|
||
> NoSuchMethodException
|
||
> ClassCastException
|
||
>
|
||
> since these exceptions are unlikely to be resolved on a retried delivery." (원문 lines 435–450, 열거형 리스트이므로 elide 미적용 — 6개 예외명 자체가 claim 의 핵심 내용이라 축약하지 않고 원문 순서대로 보존)
|
||
|
||
> [§Back Off Handlers] "Error handlers such as the DefaultErrorHandler use a BackOff to determine how long to wait before retrying a delivery. Starting with version 2.9, you can configure a custom BackOffHandler. The default handler simply suspends the thread until the back off time passes (or the container is stopped). The framework also provides the ContainerPausingBackOffHandler which pauses the listener container until the back off time passes and then resumes the container. This is useful when the delays are longer than the max.poll.interval.ms consumer property." (원문 lines 344–348)
|
||
|
||
> [§Publishing Dead-letter Records] "The recoverer requires a KafkaTemplate<Object, Object>, which is used to send the record." (원문 line 1043)
|
||
|
||
> [§Publishing Dead-letter Records] "By default, the dead-letter record is sent to a topic named <originalTopic>-dlt (the original topic name suffixed with -dlt) and to the same partition as the original record. Therefore, when you use the default resolver, the dead-letter topic must have at least as many partitions as the original topic." (원문 lines 1046–1047)
|
||
|
||
> [§DefaultErrorHandler] "The default recoverer logs the failed record after retries are exhausted. You can use a custom recoverer, or one provided by the framework such as the DeadLetterPublishingRecoverer." (원문 line 411; 같은 subsection line 362 "By default, after ten failures, the failed record is logged (at the ERROR level)." 도 동일 사실을 뒷받침)
|
||
|
||
## Claims Extracted
|
||
|
||
| Claim ID | Claim | Evidence quote | Strength | Applies to | Does not prove |
|
||
|---|---|---|---|---|---|
|
||
| SPRK-ERRH-C1 | `DefaultErrorHandler` 는 6종 예외(`DeserializationException`, `MessageConversionException`, `ConversionException`, `MethodArgumentResolutionException`, `NoSuchMethodException`, `ClassCastException`)를 기본으로 fatal 로 분류해 재시도를 건너뛰고 첫 실패에서 바로 recoverer 를 호출한다 | "The DefaultErrorHandler considers certain exceptions to be fatal, and retries are skipped for such exceptions; the recoverer is invoked on the first failure." + 6종 나열 + "since these exceptions are unlikely to be resolved on a retried delivery." (lines 435–450) | `official-vendor-doc` | Spring for Apache Kafka 4.1.0 `DefaultErrorHandler`(및 `DefaultAfterRollbackProcessor`, 동일 목록이 line 908–923 에 재등장)의 기본 classification 맵 | 이 6종이 ca-skeleton 이 실제로 채택해야 할 poison-exception 전체 목록이라는 것은 증명하지 않는다 — 프레임워크는 `addNotRetryableExceptions()`/`setClassifications()` 로 추가·교체를 명시적으로 지원하므로, 프로젝트별 poison 기준은 별도 branch 결정 |
|
||
| SPRK-ERRH-C2 | `DefaultErrorHandler` 의 기본 `BackOffHandler` 는 backoff 시간이 지날 때까지 **consumer 스레드를 정지(suspend)**시키며, 이 지연이 `max.poll.interval.ms` 보다 길 때를 위해 프레임워크가 별도로 `ContainerPausingBackOffHandler`(container 자체를 pause 후 resume)를 제공한다 | "The default handler simply suspends the thread until the back off time passes (or the container is stopped)." + "The framework also provides the ContainerPausingBackOffHandler ... This is useful when the delays are longer than the max.poll.interval.ms consumer property." (lines 346–348) | `official-vendor-doc` | Spring for Apache Kafka 4.1.0 `DefaultErrorHandler`/`BackOffHandler` 기본 동작과 `max.poll.interval.ms` 초과 우려가 있는 backoff 설정 | 이 인용은 "지연이 `max.poll.interval.ms` 를 넘으면 반드시 rebalance 가 발생한다"는 인과 관계를 **명시적으로 진술하지 않는다** — 그 인과는 Kafka consumer group 프로토콜(broker 측 rebalance 로직) 일반 지식이며 본 페이지의 claim 범위 밖. 본 페이지가 직접 증명하는 것은 "기본 handler 는 스레드를 정지시킨다"와 "그래서 `ContainerPausingBackOffHandler` 가 존재한다"까지다 |
|
||
| SPRK-ERRH-C3 | 기본 리졸버 사용 시 dead-letter 레코드는 `<originalTopic>-dlt`(원본 토픽명 + `-dlt` suffix) 토픽의 **원본과 동일한 partition** 으로 전송되며, 따라서 DLT 토픽은 원본 토픽 이상의 partition 수를 가져야 한다 | "By default, the dead-letter record is sent to a topic named <originalTopic>-dlt (the original topic name suffixed with -dlt) and to the same partition as the original record." + "the dead-letter topic must have at least as many partitions as the original topic." (lines 1046–1047) | `official-vendor-doc` | `DeadLetterPublishingRecoverer` 의 기본(커스텀 리졸버 미지정) destination 계산 | 커스텀 `BiFunction<ConsumerRecord, Exception, TopicPartition>` 리졸버를 지정하면 이 명명 규칙이 적용되지 않는다는 것도 같은 문서가 별도로 보여준다(예: line 1055–1063, line 1159 `"topic-dlt"` 커스텀 예시) — 즉 이 claim 은 **기본값**에 한정되며 ca-skeleton 이 커스텀 리졸버를 쓸지는 별도 결정 |
|
||
| SPRK-ERRH-C4 | `DeadLetterPublishingRecoverer` 는 레코드를 보내기 위해 producer 참조(`KafkaTemplate<Object, Object>`, 또는 `KafkaOperations<K, V>` 타입 파라미터를 받는 오버로드)를 요구한다 — producer 없이는 구성할 수 없다 | "The recoverer requires a KafkaTemplate<Object, Object>, which is used to send the record." (line 1043) + 코드 예시 "public ErrorHandler eh(KafkaOperations<String, String> template) {" (line 1147) + "Map<Class<?>, KafkaOperations<?, ?>> templates = new LinkedHashMap<>();" (line 1113, 멀티 템플릿 오버로드) | `official-vendor-doc` | `DeadLetterPublishingRecoverer` 의 생성자 요구사항 (단일 템플릿 및 멀티 템플릿 오버로드 모두) | 본문 서술(line 1043)은 정확히 `KafkaTemplate<Object, Object>` 라는 단어를 쓰고, "KafkaOperations" 라는 단어 자체는 코드 예시(line 1113, 1147)에서만 등장한다 — "recoverer 가 정확히 `KafkaOperations` 인터페이스 타입으로 문서화되어 있다"는 문장은 본문 prose 인용만으로는 과잉 해석이며, 코드 예시 조합으로 뒷받침한 것임을 구분할 것 (자세한 것은 `## 메모` 참조) |
|
||
| SPRK-ERRH-C5 | recoverer 를 명시적으로 구성하지 않으면, `DefaultErrorHandler` 는 재시도가 소진된 뒤 기본적으로 **실패 레코드를 로그만 남기고**(기본값 10회 실패 후 ERROR 레벨) DLT 등으로 보내지 않는다 | "The default recoverer logs the failed record after retries are exhausted. You can use a custom recoverer, or one provided by the framework such as the DeadLetterPublishingRecoverer." (line 411) + "By default, after ten failures, the failed record is logged (at the ERROR level)." (line 362) | `official-vendor-doc` | `DefaultErrorHandler` 에 커스텀 recoverer/`BackOff` 를 지정하지 않은 기본 구성 | "10회 실패" 라는 수치는 기본 `FixedBackOff` 설정에서 파생되는 값이며, `BackOff`(재시도 횟수·지연)를 다른 값으로 구성하면 실패까지 걸리는 시도 횟수도 달라진다 — "항상 10회" 라는 뜻은 아니다 |
|
||
|
||
### Strength 참고
|
||
|
||
모두 `official-vendor-doc` — Spring for Apache Kafka 는 Spring 프로젝트 공식 reference 문서이며 RFC/표준(`official-standard`)은 아니다.
|
||
|
||
## Usage Boundaries
|
||
|
||
- 이 자료가 직접 증명하는 것:
|
||
- `SPRK-ERRH-C1`: `DefaultErrorHandler` 기본 fatal 예외 6종과 "재시도 skip + 첫 실패에 recoverer 호출" 동작
|
||
- `SPRK-ERRH-C2`: 기본 `BackOffHandler` 가 consumer 스레드를 정지시킨다는 것과, 그로 인해 `max.poll.interval.ms` 초과 우려가 있을 때 `ContainerPausingBackOffHandler` 가 제공된다는 것
|
||
- `SPRK-ERRH-C3`: 기본 리졸버의 DLT 명명(`<originalTopic>-dlt`) + 동일 partition 전송 + partition 수 요건
|
||
- `SPRK-ERRH-C4`: `DeadLetterPublishingRecoverer` 가 producer(`KafkaTemplate`/`KafkaOperations` 계열) 없이는 구성 불가하다는 것
|
||
- `SPRK-ERRH-C5`: recoverer 미구성 시 기본 동작이 "로그만"이라는 것
|
||
- 이 자료가 증명하지 **않는** 것:
|
||
- **backoff 지연이 `max.poll.interval.ms` 를 넘으면 반드시 rebalance 가 발생한다는 인과 자체.** 본 페이지는 "스레드 정지" 사실과 "그래서 `ContainerPausingBackOffHandler` 가 유용하다"는 권고만 진술한다 — rebalance 트리거 메커니즘은 Kafka consumer group 프로토콜의 별도 공식 문서로 보강 필요(`SPRK-ERRH-C2` Does not prove 참조).
|
||
- ca-skeleton 이 실제로 채택해야 할 poison-exception 전체 목록 — 6종은 Spring 기본값일 뿐, 확장/교체가 명시적으로 지원된다.
|
||
- "recoverer 가 `KafkaOperations` 인터페이스로 문서화되어 있다"는 prose 수준 주장 — 본문 prose 는 `KafkaTemplate<Object, Object>` 로 서술하고, `KafkaOperations` 는 코드 예시에서만 파라미터 타입으로 등장한다.
|
||
- DLT 파티션 수·명명 override 시의 동작 — 커스텀 리졸버를 쓰면 `<originalTopic>-dlt` 규칙이 적용되지 않는다는 것도 같은 문서가 별도 예시로 보여준다.
|
||
- 내 프로젝트에 적용하려면 추가 확인이 필요한 것:
|
||
- ca-skeleton 이 실제로 설정할 `max.poll.interval.ms` 값과 backoff 지연 상한이 서로 충돌하지 않는지 — Kafka consumer 공식 설정 문서(`max.poll.interval.ms`, `max.poll.records`)로 별도 확인
|
||
- poison exception 목록에 ca-skeleton 도메인 역직렬화/스키마 예외를 추가할지 여부 — branch 자체 결정
|
||
- DLT 파티션 수를 원본과 동일하게 프로비저닝할지, 토픽 생성 자동화(Kafka Streams/Admin API)로 강제할지 — 별도 인프라 결정
|
||
|
||
## 메모
|
||
|
||
- `KafkaOperations` vs `KafkaTemplate`: 본문 prose 는 `KafkaTemplate<Object, Object>` 를 요구한다고 명시하고, `KafkaOperations` 라는 단어는 이 페이지의 코드 예시(멀티 템플릿 맵, 커스텀 destination resolver 예시)에서만 파라미터 타입으로 등장한다. `KafkaTemplate` 이 `KafkaOperations` 인터페이스의 구현체라는 것은 Spring Kafka 일반 지식이지만, **이 페이지 자체가 그 상속 관계를 설명하는 문장은 없다** — 다음 문서 작업자는 이 구분을 유지할 것 (branch 의 D-row 에서 "producer 타입은 `KafkaTemplate` 구현체(또는 `KafkaOperations` 파라미터로 받는 오버로드)" 로 정확히 표현 권장).
|
||
- 추가로 봐야 할 동일 출처 페이지: Kafka consumer 공식 문서의 `max.poll.interval.ms`/`max.poll.records` 설정 — `SPRK-ERRH-C2` 의 "얼마나 길어야 rebalance 위험인지" 를 별도로 보강해야 함. Non-Blocking Retries(`@RetryableTopic`) 페이지도 blocking retry 대안 비교 시 참고.
|
||
|
||
## 관련
|
||
|
||
- 같은 주제 다른 official-doc: [[raw/official-docs/kafka-message-delivery-semantics-design]], [[raw/official-docs/spring-kafka-sending-messages-kafkatemplate]] — 같은 Spring for Apache Kafka 4.1.0 reference 의 인접 주제(전달 시맨틱, producer 전송 결과)
|
||
- 추가 확보 필요 (아직 raw 에 없음): Kafka consumer 공식 문서의 `max.poll.interval.ms`/`max.poll.records` 설정 페이지 — `SPRK-ERRH-C2` 보강용
|
||
- 이 자료를 인용한 wiki 요약: (생성 시 링크)
|