# 주제: Debezium 커넥터 설정이 두 곳에 있고, 운영자가 실제로 배포하는 쪽이 수정 이전 버전이다 # revision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916 # severity: P2 # ---- 표현 1: Java ---- # DebeziumOutboxEventRouter.java:35-85 # public static final String KEY_COLUMN = "routing_key"; # public Map 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)에서 그 예측이 커넥터 설정과 불일치한다.