Files
document-haness/docs/clean-architecture-backend-template/final/evidence/raw/310-debezium-properties-vs-java-drift.txt
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

78 lines
4.6 KiB
Plaintext

# 주제: Debezium 커넥터 설정이 두 곳에 있고, 운영자가 실제로 배포하는 쪽이 수정 이전 버전이다
# revision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
# severity: P2
# ---- 표현 1: Java ----
# DebeziumOutboxEventRouter.java:35-85
# public static final String KEY_COLUMN = "routing_key";
# public Map<String, String> connectorConfiguration(String topicPrefix) {
# configuration.put("transforms.outbox.table.field.event.key", KEY_COLUMN); // routing_key
# configuration.put("transforms.outbox.route.by.field", ROUTE_BY_COLUMN); // destination
# configuration.put("transforms.outbox.route.topic.replacement", topicPrefix + "${routedByValue}");
# configuration.put("transforms.outbox.table.fields.additional.placement", <15개>);
# }
#
# 클래스 javadoc:21-26 이 고친 결함을 명시한다:
# "The message key and the topic are two different questions and used to be answered by one
# column. `destination` routed the topic *and* became the key, so every message on a topic
# carried the same key, landed on one partition, and made keyed ordering a claim with no
# mechanism behind it. The key now comes from `routing_key` …"
# ---- 표현 2: 배포되는 리소스 ----
# src/main/resources/debezium/outbox-event-router.properties:28
# transforms.outbox.table.field.event.key=destination <-- 수정 이전 값
# :33
# transforms.outbox.route.topic.replacement=${routedByValue} <-- 프리픽스 없음
# :30
# transforms.outbox.table.field.event.timestamp=created_at <-- Java 에는 없는 키
# :37
# transforms.outbox.table.fields.additional.placement=
# message_type:header:msg.type,
# schema_version:header:msg.schema-version,
# content_type:header:msg.content-type,
# message_id:header:msg.id <-- 4개
# ---- 측정 ----
# command: (Java) sed -n "66,83p" DebeziumOutboxEventRouter.java | grep -c "header:"
# 15
# command: (properties) grep "additional.placement" …properties | tr "," "\n" | grep -c "header:"
# 4
# command: grep "event.key" …properties
# transforms.outbox.table.field.event.key=destination
# properties 에 없는 11개 헤더: created_at(PRODUCED_AT), destination(ORIGIN_DESTINATION),
# producer, occurred_at, correlation_id, causation_id, tenant, partition_key, ordering_key,
# traceparent, tracestate, baggage
# (= V4__messaging_outbox_canonical_metadata.sql 이 추가한 정경 메타데이터 전부)
# V4 주석:6-9 "Everything else the envelope carries … had nowhere to go, so it was either lost
# when the relay rebuilt the envelope or smuggled through the header map"
# -> 그 손실을 막으려고 컬럼을 만들었는데, CDC 배포 파일은 그 컬럼들을 헤더로 옮기지 않는다.
# ---- 두 표현을 잇는 것이 없다 ----
# command: git grep -rn "outbox-event-router" -- src
# exit: 1 (출력 없음)
# => properties 파일을 읽는 코드도, 그 내용을 Java 설정과 대조하는 테스트도 없다.
# ---- 테스트는 Java 쪽만 검증하며, 그 테스트는 의도적으로 견고하게 작성되어 있다 ----
# DebeziumOutboxRecordMapperTest.java:154-162
# @Test
# void theRoutedKeyIsNotTheTopicName() {
# // Literals, not the class's own constants: comparing a configuration value against the
# // constant that produced it asserts that the router agrees with itself, which it always will.
# assertThat(new DebeziumOutboxEventRouter().connectorConfiguration("prod."))
# .as("keying by destination puts every message on a topic onto one partition")
# .containsEntry("transforms.outbox.table.field.event.key", "routing_key")
# .containsEntry("transforms.outbox.route.by.field", "destination");
# }
# => 리터럴 대조까지 하는 테스트가 Java 를 지키고 있고, 운영자가 배포하는 파일은 아무도 지키지 않는다.
# ---- 부수 발견: aggregateIdAsPartitionKey 는 커넥터에 도달할 수 없다 ----
# DebeziumOutboxProfile 은 aggregateIdAsPartitionKey 필드를 갖는다(:20).
# DebeziumOutboxRecordMapper 는 그것으로 분기한다(:70-78):
# if (!aggregateIdAsPartitionKey) { return Optional.empty(); }
# 그러나 connectorConfiguration(String topicPrefix) 는 프로필을 받지 않으며(:55),
# event.key 를 항상 routing_key 로 고정한다(:60).
# => 플래그가 false 여도 실제 Debezium 은 키를 붙인다. 모델(mapper)은 "키 없음" 을 예측한다.
# mapper 의 존재 이유가 "Produces what Debezium's Event Router will emit"(:11) 인데,
# 기본값(polling() -> false)에서 그 예측이 커넥터 설정과 불일치한다.