Files
document-haness/docs/clean-architecture-backend-template/analysis/messaging/messaging-rabbit.md
T
DongHyeonkaandClaude Opus 5 b2963105a8 docs(keycloak-session-store): import the session-storage lab as a new project
The keycloak project ended with four open questions that design could not
settle. A two-VM lab was built to answer them by measurement, and this is
that material: 26 experiments, 125 raw command outputs, 22 browser captures.

Follows the import procedure in README.md.

  source/     the originating repository verbatim — 78 documents, 28 SVGs,
              8 manifests, plus .source-revision recording the commit
  final/      the SSOT
    document.md   729 lines written from the 29 experiment documents, not
                  concatenated: what was predicted, what was measured, and
                  where the measurement itself was wrong
    evidence/raw    125 outputs, flattened to <experiment>__<file> because
                    the originals collided (01-baseline.txt appeared three
                    times) and the audit only globs the top level
    evidence/meta   one per raw file; command and exitCode are null and the
                    README says why rather than inventing them
    evidence/browser  22 captures
    assets/       three diagrams through techviz
    .techviz/     their VizSpecs

A separate project rather than an addition to keycloak: the B-layer answers
that project's four questions, but the A, C and D layers are about cluster
failure, SSO and operations, and one document.md should hold one subject.
The four question records there can point here through 관계.

Recorded rather than papered over: only three of the 28 diagrams were
remade. The repository forbids hand-drawn SVG and forbids titles inside the
canvas; all 28 originals carry both, so converting them is redrawing, not
reformatting. They stay in source/ and the gap is written into the document.

verify-pipeline.py passes. audit-records.py reports no issues.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 22:51:59 +09:00

25 KiB

messaging-rabbit 완전 해부

상태: COMPLETE 재오픈 게이트: cycle 2 재통독(2026-09-01) — src/main production 20파일 2,443줄 + src/test 10파일 1,727줄 축자 통독 완료. STRUCTURAL_ONLYgradle.lockfile 하나. 기준 revision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916 분석 범위: src/messaging/messaging-rabbit SSOT owner: messaging-rabbit integration/family document: analysis/19-messaging-platform.md (secondary, INTEGRATION_ONLY)


0. SSOT identity / 커버리지

  • runtime_memberships: ["app-bootstrap"] — 클래스패스에 올라간다
  • 도달성: 없다. 제공자 선택이 rabbit 을 이름으로 거부한다(§12.1)
파일 LOC 참조
RabbitConfirmCoordinator 279 전송 + 테스트
RabbitMessagingTransport 246 테스트만
RabbitConsumerRegistrar 238 테스트만
RabbitDeliveryMapper 195 소비자 + 테스트
RabbitHeaderMapper 180 매퍼 둘 + 테스트
RabbitSecurityConfigurer 179 스타터 빈만 — 호출처 없음
RabbitBatchConsumerRegistrar 165 테스트만
RabbitTopologyProfile 137 네이티브 DLQ 능력 + 테스트
RabbitDeadLetterPublisher 130 자기 파일 밖 참조 0
RabbitPublishFailureClassifier 117 전송 + 테스트
RabbitSettlementController 84 소비자 + 테스트
RabbitProfileValidator 83 스타터 시작 검증
RabbitPublishMapper 72 전송
RabbitNativeDeadLetterCapability 70 DLQ 발행자 + 테스트
RabbitRetryQueueTopology 57 테스트만
RabbitSettlementOperations 50 인터페이스 — 구현은 테스트 셋뿐
RabbitBrokerProfile 50 설정 컴파일
RabbitChannelPublisher 42 인터페이스 — production 구현 0
RabbitPublishReference 37 좌표
RabbitRequestReply 32 자기 파일 밖 참조 0

main 총 20파일 / 2,443줄.

Coverage ledger

scope count disposition reason
main/java/** 20 FULL_READ 2,443줄. 위 표가 전부
test/java/** 10 FULL_READ 1,727줄
build.gradle 1 FULL_READ 전문
gradle.lockfile 1 STRUCTURAL_ONLY 잠금 파일 — 생성물

UNCLASSIFIED 0.

"참조" 열은 2026-09-01 재통독에서 저장소 전체 grep 으로 채웠다. 이전 판의 Source anchors 는 절반을 괄호 하나로 묶어 두었고, 그 괄호 안에 §17.2·§17.3·§17.4 가 있었다.


1. 이 어댑터의 중심 — 확인과 반환은 다른 질문에 답한다

"AMQP delivers a return before the confirm for an unroutable message, so a naive adapter that completes on the confirm reports success for a message the broker threw away. The coordinator therefore keeps each publish pending until the confirm arrives, and remembers whether a return was seen first."

네 결과가 나온다 — 확인+미반환은 CONFIRMED, 확인+반환은 UNROUTABLEREJECTED, 부정 확인은 REJECTED, 확인 미도착은 AMBIGUOUS.

이 리프에서 가장 중요한 판단이고, 실브로커 시험(RabbitBrokerIT.anUnroutablePublishIsRejectedEvenThoughTheExchangeConfirmedIt)이 그것을 붙든다. 그리고 그 시험이 production 에 없는 조각을 스스로 채워 넣는다(§17.2).

2. 자료구조 선택이 결함 수정이다

private final ConcurrentSkipListMap<Long, PendingPublish> pending = new ConcurrentSkipListMap<>();

"Ordered because a Rabbit confirm carries a multiple flag meaning 'everything up to and including this tag'. Resolving one sequence per confirm — which is what a hash map forces — leaves every earlier publish pending forever: the caller's stage never completes and the entry is never removed, so the map grows for the life of the connection."

confirmed(sequence, multiple=true, …)headMap(sequence, true) 로 범위를 해소한다. 전용 시험이 있다.

3. 부정 확인의 증거를 전송됨으로 기록한다

// A NACK is the broker's answer to a frame it received. Recording it as never sent contradicts the
// very evidence that produced it, and a caller reading the evidence would conclude the message can
// be re-sent freely.

REJECTED + TRANSMITTED + ConfirmationLevel.NONE. 완결 상태와 전송 증거를 분리해서 다루는 곳이 이 가족에서 여기와 Kafka 뿐이다.

4. 소비·정착·죽은 편지의 세 규율

좁은 catch. RabbitConsumerRegistrar.onMessage 가 디코딩만 감싸는 안쪽 try 를 따로 둔다.

"One catch around decode, the handler and the settlement meant a business failure or an ACK that could not be written was recorded as an undecodable payload and discarded — a message that should have been retried, deleted instead."

정착하지 않은 핸들러. 완료했는데 정착하지 않으면 대신 ack 하지 않고 requeue 한다 — "acknowledging on its behalf would silently drop it".

네이티브 죽은 편지. RabbitNativeDeadLetterCapability 가 두 조건을 모두 요구한다.

"If the dead-letter exchange is unroutable — nobody bound a queue to it, or the binding was removed — the broker discards the message silently and the reject still succeeds."

5. 자격증명은 연결 시도마다 해석된다

"RabbitMQ client connections are long-lived and reconnect on their own, so a factory holding a credential from startup will happily reconnect with a revoked one for as long as the process runs — the reconnect is exactly the moment a rotated credential should take effect."

AmqpCredentials 가 record 가 아니라 class 인 이유도 적혀 있다 — 비밀을 지우려면 가변이어야 하고, record 가 char[] 를 동등성에 쓰면 같은 자재를 가진 둘이 서로 다르다고 판정된다.

6. 시작 검증

RabbitProfileValidator 가 여덟을 요구한다 — Stable 에 확인·반환·mandatory, 소비자 auto-ack 금지, prefetch ≥ 1, 확인 마감 양수, 운영에 TLS·인증. 그리고 목적지 검증이 둘 더 — 작업 큐에 쿼럼 큐, 교환기나 큐 중 하나.

Kafka 쪽과 달리 이 검증기는 스타터에서 StartupProfileValidation 으로 감싸여 있다. 다만 그 자동 설정 자체가 도달하지 않는다(§12.1).

10. 테스트 레인

10파일 1,727줄.

파일 무엇을 붙드나
RabbitRuntimeTest 309 전송 4경로 + 소비자 7경로(일시정지·배수·미디코딩·핸들러 실패·미정착·close)
RabbitContractHarness 304 공유 어댑터 계약을 production 조정자·정착 제어기 위에서
RabbitBrokerIT 232 실브로커 rabbitmq:4.3-management — 반환-먼저-확인
RabbitTopologyAndBatchTest 218 쿼럼 요구·DLX 논리·실패 분류·배치 누적
RabbitProfileValidatorTest 184 검증기 여덟 규칙
RabbitConfirmCoordinatorTest 167 상태 기계 12경로(다중 확인·채널 종료 포함)
RabbitEnvelopeRoundTripTest 152 헤더 왕복·위조 거부
RabbitSettlementControllerTest 105 일회 종결·지연 재시도 큐 인자
RabbitAdapterContractTest · RabbitFixtureProfiles 24 · 32 계약 실행·픽스처

RabbitAdapterContractTest 의 javadoc 이 이 레인의 요점을 적는다.

"Two brokers with completely different machinery — offsets and commits versus delivery tags and confirms — answering the same seven questions the same way is what makes the logical destination abstraction real rather than aspirational."

12. negative-space probes

12.1 도달성 — 리프 전체가 production 호출자를 갖지 않는다.

전송을 만들려면 RabbitChannelPublisher 구현이 필요하다. 저장소 전체에서 그 인터페이스의 구현은 테스트의 익명 클래스 둘(RabbitRuntimeTest:49, RabbitBrokerIT:709)뿐이다. 따라서 new RabbitMessagingTransport(...) 도 테스트에만 있고, RabbitConsumerRegistrar·RabbitBatchConsumerRegistrar 도 마찬가지다.

그리고 그 사실이 플랫폼 쪽에 이름으로 기록되어 있다.

// MessagingProviderSelection
static final Map<String, String> BROKERS_WITHOUT_A_TRANSPORT = Map.of(
    "rabbit",
    "the Rabbit adapter ships its validators and security configuration but no MessagingTransport: "
        + "its native channel publisher is not implemented, so a publish has nothing to travel on");

app.messaging.broker=rabbit 은 시작 오류이고, 전용 시험이 그 메시지를 단언한다. 그래서 RabbitMessagingAutoConfiguration 98줄도 도달하지 않는다.

이 리프의 품질과 도달성이 정반대다. 코드는 이 가족에서 가장 정교한 축이고 — 반환-먼저-확인 상태 기계, multiple 범위 해소, 정착 일회성, 네이티브 DLQ 의 조건부 신뢰 — 실행 경로는 없다.

12.2 리프 자체 기준으로도 죽은 둘.

파일 LOC 상태
RabbitDeadLetterPublisher 130 자기 파일 밖 참조 0 — production 도 테스트도 부르지 않는다
RabbitRequestReply 32 인터페이스. 구현 0, 테스트 0, 호출 0

RabbitDeadLetterPublisher 가 담고 있는 것이 §4 의 세 번째 규율 — 네이티브 경로와 플랫폼 발행 중 어느 쪽을 쓸지 한 곳에서 결정한다는 판단 — 인데, 그 결정을 내리는 코드를 아무도 부르지 않는다. 그 판단의 근거가 되는 RabbitNativeDeadLetterCapability 는 테스트가 있다. 즉 판단의 재료는 시험되고 판단 자체는 시험되지 않는다.

RabbitRequestReply 는 M2 능력의 인터페이스 선언이다. javadoc 이 왜 제한적으로 제공하는지를 적는데("a synchronous call wearing an asynchronous costume"), 제공되는 것이 없다.

12.3 대조군 — 재시도 헤더 오염의 처리가 두 어댑터에서 갈린다. 두 어댑터의 attemptOf 는 같은 fail-closed 결정을 같은 문구로 적는다.

// "the message is quarantined rather than restarting its retry budget"
throw new MessagingConfigurationException("RETRY_ATTEMPT_MALFORMED", );

그런데 소비자가 그 던짐을 받는 위치가 다르다.

어댑터 attemptOf 호출 위치 결과
Rabbit toMetadata 안 → 디코딩 실패 catch 안쪽 operations.discard(tag, …) — DLX 가 있으면 죽은 편지로
Kafka 디코딩 catch 바깥의 두 번째 블록 requeueAfterFailure() → 무한 pause-and-seek(messaging-kafka §17.4)

같은 판단, 반대 결과다. Rabbit 쪽이 javadoc 이 약속한 것에 가깝다.

12.4 대조군 — pause 의 뜻이 SPI 하나 뒤에서 두 가지다.

어댑터 반환 시점 의미
Kafka 다음 폴 주기 consumer.pause() — 브로커에서 더 가져오지 않는다
Rabbit 즉시 완료 onMessagefalse 를 답한다 — 리스너 컨테이너가 계속 밀고, 미확인으로 재배달된다

Kafka 쪽 javadoc 은 왜 즉시 완료하지 않는지를 명시한다("'paused' cannot be true until the loop says so"). Rabbit 쪽에는 그 대비 서술이 없다. §17.4.

12.5 드리프트. 검증기가 강제하는 항목과 어댑터가 실제로 보내는 플래그(mandatory)가 일치한다.

16. 확인하지 못한 것

  • 실제 브로커로 반환-먼저-확인 순서를 재현하지 않았다. RabbitBrokerIT 가 그 레인이고 컨테이너가 필요하다.
  • 지연 재시도 큐 토폴로지를 실제로 선언해 보지 않았다.
  • §17.2 를 실행으로 재현하지 않았다. RabbitHeaderMapper.toProperties 전문에 순번 헤더가 없다는 것과, RabbitBrokerIT 가 자기 publish 람다에서 x-seq 를 붙인다는 것으로 판정했다.
  • RABBIT-CR-DEMO(§17.3)를 실제 브로커에 붙여 보지 않았다. 이름과 RabbitMQ 의 기본 활성 상태로 판정했다.
  • gradle.lockfile 은 읽지 않았다(STRUCTURAL_ONLY).

17. 손볼 것

17.1 P3 — 확인 등급이 요구에서 파생되고, 그 요구를 뒷받침하는 강제는 목적지 종류 하나에만 걸린다

ConfirmationLevel level =
    requirement == ConfirmationRequirement.REPLICATION_OR_PERSISTENCE_ACK
        ? ConfirmationLevel.REPLICATION_OR_PERSISTENCE_ACK
        : ConfirmationLevel.BROKER_ACK;

증거의 등급이 브로커가 무엇을 했는지가 아니라 프로파일이 무엇을 요구했는지 에서 나온다.

대부분의 경우 이 파생은 성립한다. 두 강제가 그것을 받쳐 준다.

  • RabbitHeaderMapper.toProperties 가 배달 모드를 무조건 PERSISTENT 로 둔다. RabbitMQ 는 지속 메시지를 디스크에 쓴 뒤에 확인한다.
  • RabbitProfileValidator.validateDestination 이 내구 작업 큐에 쿼럼 큐를 요구한다. 쿼럼 큐의 확인은 다수 복제 뒤에 온다.

빈틈은 둘째 강제의 범위다.

if (destination.kind() == DestinationKind.WORK_QUEUE && !broker.quorumQueues()) { throw ; }

작업 큐가 아닌 목적지에는 쿼럼 요구가 없다. 교환기로 발행하는 목적지가 REPLICATION_OR_PERSISTENCE_ACK 를 요구하면, 그 교환기에 바인딩된 큐가 고전 큐여도 어댑터는 그 등급을 보고한다. 지속 모드 덕분에 디스크 기록은 보장되지만 복제는 보장되지 않는다.

이 저장소의 규율은 증거가 관측에서 나와야 한다는 것이다 — MessagingCapabilities 의 javadoc 이 "a silently weakened guarantee is indistinguishable from a working one until the incident" 라고 적는다.

수정은 쿼럼 요구를 목적지 종류가 아니라 요구된 확인 등급 에 걸거나, 작업 큐가 아닌 목적지에서는 등급을 BROKER_ACK 로 낮추는 것이다.

17.2 P2 — 반환을 순번에 맞추는 조각이 production 에 없고, 시험이 그 자리를 스스로 메운다

이 어댑터의 핵심 보장(§1)은 반환과 확인을 같은 발행 에 묶는 데 달려 있다. 묶는 열쇠는 순번이다.

public void returned(long sequence) {  }        // RabbitConfirmCoordinator
public void onReturn(long sequence) {  }        // RabbitMessagingTransport

그런데 AMQP 의 basic.return 콜백은 순번을 주지 않는다. 교환기·라우팅 키·속성·본문만 온다. 그래서 발행자가 순번을 메시지에 실어 보내고 반환에서 되읽어야 한다.

RabbitHeaderMapper.toProperties 전문에 그런 헤더가 없다. 쓰는 것은 msg.* 예약 헤더들과 AMQP 의 messageId·correlationId·timestamp·deliveryMode 뿐이다.

그 조각이 존재하는 곳은 시험 하나다.

// RabbitBrokerIT
channel.addReturnListener(returned ->
    transport.onReturn(Long.parseLong(returned.getProperties().getHeaders().get("x-seq").toString())));

private static Map<String, Object> withSequence(MessageProperties source, long sequence) {
  headers.put("x-seq", Long.toString(sequence));   // ← 시험이 직접 붙인다
}

그 메서드의 javadoc 이 문제를 정확히 서술한다.

"A returned message arrives without its publish sequence number, so the adapter has to carry one itself to correlate the return with the pending publish."

"the adapter has to" 인데 어댑터는 하지 않는다. RabbitChannelPublisher 의 javadoc 은 등록 경합(확인이 basicPublish 반환보다 먼저 올 수 있다)만 설명하고 이 상관 문제는 언급하지 않는다.

결과는 이렇다. 언젠가 RabbitChannelPublisher 를 구현하는 사람은 이 헤더 규약을 다시 발명해야 하고, 발명하지 않으면 onReturn 이 호출되지 않아 unroutable 발행이 CONFIRMED 로 보고된다 — 이 어댑터가 존재하는 이유로 든 바로 그 실패다.

수정은 순번 헤더를 RabbitHeaderMapperRabbitPublishMapper 로 올려 production 계약으로 만들고, 그 이름을 RabbitChannelPublisher javadoc 에 적는 것이다. 지금은 그 규약이 시험 파일 20줄에만 있다.

17.3 P3 — SCRAM 자격을 RabbitMQ 의 데모 기구로 조용히 매핑한다

case BrokerCredentialProfile.SaslScram scram -> {
  CredentialRuntime resolved = credentials.resolve(scram.credentialId(), now);
  yield new AmqpCredentials("RABBIT-CR-DEMO", scram.credentialId(), resolved.material(), profile.tlsEnabled());
}

RABBIT-CR-DEMO 는 RabbitMQ 의 시연용 challenge-response 인증 기구(rabbit_auth_mechanism_cr_demo)의 이름이고 기본 활성이 아니다. RabbitMQ 는 SCRAM-SHA 를 구현하지 않으므로 SaslScram 에 대응하는 AMQP 기구가 없다는 것 자체는 사실이다.

문제는 그 사실을 다루는 방식이 같은 파일 안에서 일관되지 않다는 것이다.

case BrokerCredentialProfile.Nkey ignored ->
    throw new IllegalArgumentException("NKey credentials are a NATS concept, not an AMQP one");   // ← 거부
case BrokerCredentialProfile.OAuth2 oauth -> {
  // RabbitMQ's OAuth 2 plugin takes the token in the password field of a PLAIN exchange.
  yield new AmqpCredentials("PLAIN", );                                                          // ← 주석으로 근거
}
case BrokerCredentialProfile.SaslScram scram -> yield new AmqpCredentials("RABBIT-CR-DEMO", );   // ← 둘 다 없다

그리고 이웃 어댑터의 같은 클래스가 정확히 이 상황에 대한 규범을 적어 두었다.

"Refused rather than half-configured. Setting the mechanism name without a callback handler produces a client that authenticates with nothing and fails at connect time, which is later and harder to attribute than failing here." — KafkaSecurityConfigurer

SaslScram 에도 그 규범이 적용되어야 한다. 플러그인이 없는 브로커에서는 handshake 가 알아보기 어려운 오류로 실패하고, 있는 브로커에서는 시연용 기구로 인증한다.

수정은 Nkey 와 같이 거부하거나, PLAIN 으로 매핑하고 그 이유를 주석으로 남기는 것이다. 어느 쪽이든 지금처럼 말없이 데모 기구를 고르는 것보다 낫다.

17.4 P3 — 능력 상수의 delayedDelivery 가 무조건 참이고, 그 지연을 제공할 토폴로지는 조립되지 않는다

private static final MessagingCapabilities CAPABILITIES =
    new MessagingCapabilities(true, true, true, false, false, false, false, true, false, false, true, true);
//                                                                        ^^^^ delayedDelivery

이 플래그는 읽힌다.

// DefaultRetryDecisionEngine:64
if (policy.mode() == RetryMode.BROKER_DELAYED && context.capabilities().delayedDelivery()) {  }

그런데 지연을 실제로 만드는 것은 RabbitRetryQueueTopology 이고, 그 클래스는 자기 파일과 시험 하나 밖에서 참조되지 않는다. 어떤 production 코드도 그 큐를 선언하지 않는다.

그리고 그 클래스의 javadoc 이 이 지연의 성질을 정확히 적는다.

"TTL expiry is evaluated at the head of the queue, so mixed delays in one retry queue do not expire independently."

즉 제공되는 것은 "메시지별 지연" 이 아니라 "재시도 큐 하나당 TTL 하나" 다. 능력 모델에는 그 구분을 표현하는 자리가 없고, 상수는 프로파일과 무관하게 참을 답한다.

Kafka 는 같은 칸을 false 로 둔다. 그래서 이 플래그의 두 값이 "지연 있음/없음" 이 아니라 "지연을 흉내낼 토폴로지를 선언할 수 있음/없음" 을 뜻하게 된다.

수정은 능력을 전송 상수가 아니라 목적지의 재시도 큐 선언에서 파생시키는 것이다. 이 리프가 조립되지 않는 동안에는 P3 이고, RabbitChannelPublisher 구현이 생기는 날 함께 봐야 한다.

17.5 P3 — pause 의 의미가 SPI 하나 뒤에서 두 브로커에 다르게 구현된다

@Override public CompletionStage<Void> pause(String scope) {
  pausedScopes.add(scope == null ? "" : scope);
  return CompletableFuture.completedFuture(null);      // ← 즉시 완료
}

호출자가 이 단계를 기다리고 나면 "일시정지되었다" 고 읽는다. 실제로 일어난 것은 onMessage 가 이후 배달에 false 를 답하기 시작한 것뿐이고, 리스너 컨테이너는 계속 배달을 밀며 그 배달들은 미확인 상태로 재배달된다. 즉 정지가 아니라 거부-재배달 루프다.

Kafka 쪽은 같은 SPI 를 정반대로 구현하고 그 이유를 적는다.

"The returned stage completes after the poll loop has actually applied the change, so a caller that awaits it knows the consumer is paused rather than merely asked to pause… there is no safe way to touch the consumer from another thread, so 'paused' cannot be true until the loop says so."

AMQP 에는 대응하는 수단이 있다 — basicCancel 로 소비자를 취소하거나 컨테이너를 멈추는 것. 지금 구현이 그것을 하지 않는 이유는 어디에도 없다.

전용 시험(aPausedQueueRefusesDeliveriesSoTheBrokerRedeliversThem)의 이름이 이미 실제 동작을 정확히 말한다. 그러므로 수정은 둘 중 하나다 — 컨테이너를 실제로 멈추거나, SPI 의 javadoc 에 "브로커에 따라 정지가 거부-재배달일 수 있다" 를 명시하는 것.

확인된 설계(문제 아님)

  • 확인과 반환을 두 질문으로 나누고, 반환-먼저 순서를 상태 기계로 다룬 것.
  • 정렬된 맵을 골라 multiple 확인의 범위 해소를 가능하게 한 것과, 해시 맵이 만들었을 누수를 javadoc 에 남긴 것.
  • 부정 확인의 증거를 전송됨으로 기록한 것과 그 근거.
  • 채널 종료를 모호로 완결시킨 것 — 보류로 남기면 호출자가 매달린다.
  • 순번 예약을 발행과 분리한 것 — 확인이 basicPublish 반환을 앞지를 수 있다.
  • 동기 발행 실패를 던지지 않고 분류기를 거쳐 스테이지로 돌려주는 것.
  • 적재물 크기와 종료 상태를 채널 앞에서 검사해 미전송 증거로 실패시키는 것.
  • 정착의 일회성과 재사용된 배달 태그의 위험을 명시한 것.
  • 디코딩만 감싸는 좁은 catch — 넓은 catch 가 재시도 가능한 실패를 삭제로 바꾸던 형태를 고쳤다.
  • 정착하지 않은 핸들러를 대신 ack 하지 않고 requeue 하는 것.
  • 요구 재큐 대신 지연 재시도 큐를 쓴 것과, TTL 이 큐 머리에서 평가된다는 한계를 javadoc 에 남긴 것.
  • 배수 중 진행 배달을 끝내게 한 것.
  • 네이티브 죽은 편지를 검증된 곳에서만 쓰고 나머지는 공유 조율자에 위임한 것, 그리고 네이티브 경로의 증거를 BROKER_ACK 로만 주장한 것.
  • 자격증명을 연결 시도마다 해석하고 짧은 수명 객체로 넘긴 것, AmqpCredentials 를 record 가 아니라 class 로 둔 것과 그 근거.
  • 내구 작업 큐에 쿼럼 큐를 요구한 것과 그 근거.
  • 배치 누적에 나이 경계를 필수로 만든 것 — 조용한 큐가 마지막 메시지를 미확인으로 붙들지 않게.
  • prefetch 가 배치 크기보다 작으면 교착이라는 것을 거부로 표현한 것.
  • 배치를 settlableAsBatch=false 로 보고한 것 — AMQP multiple-ack 은 진행 중인 작업까지 정착시킨다.

Source anchors

src/messaging/messaging-rabbit/build.gradle
main/java/…/rabbit/RabbitConfirmCoordinator.java:1-279  (§17.2 returned:74-79)
main/java/…/rabbit/RabbitMessagingTransport.java:1-246  (능력 상수 41-43 · §17.4)
main/java/…/rabbit/RabbitConsumerRegistrar.java:1-238   (§17.5 pause:694-697)
main/java/…/rabbit/RabbitDeliveryMapper.java:1-195      (§12.3 attemptOf:133-153)
main/java/…/rabbit/RabbitHeaderMapper.java:1-180        (§17.1 배달 모드 235 · §17.2 순번 헤더 부재)
main/java/…/rabbit/RabbitSecurityConfigurer.java:1-179  (§17.3 switch 438-463)
main/java/…/rabbit/RabbitBatchConsumerRegistrar.java:1-165
main/java/…/rabbit/RabbitTopologyProfile.java:1-137
main/java/…/rabbit/RabbitDeadLetterPublisher.java:1-130 (§12.2 참조 0)
main/java/…/rabbit/RabbitPublishFailureClassifier.java:1-117
main/java/…/rabbit/RabbitSettlementController.java:1-84
main/java/…/rabbit/RabbitProfileValidator.java:1-83     (§17.1 validateDestination:543-555)
main/java/…/rabbit/RabbitPublishMapper.java:1-72
main/java/…/rabbit/RabbitNativeDeadLetterCapability.java:1-70
main/java/…/rabbit/RabbitRetryQueueTopology.java:1-57   (§17.4)
main/java/…/rabbit/{RabbitSettlementOperations:1-50, RabbitBrokerProfile:1-50, RabbitChannelPublisher:1-42,
                    RabbitPublishReference:1-37, RabbitRequestReply:1-32}
test/java/…/rabbit/ 10파일 1,727줄 (RabbitRuntimeTest:309 · RabbitContractHarness:304 · RabbitBrokerIT:232 …)
test/java/…/rabbit/RabbitBrokerIT.java:726-733, 786-813 (§17.2 시험이 메우는 x-seq 규약)
messaging-spring-boot-starter/…/MessagingProviderSelection.java:64-69 (§12.1 rabbit 거부)
messaging-policy/…/DefaultRetryDecisionEngine.java:64 (§17.4 delayedDelivery 소비처)