Files
document-haness/docs/clean-architecture-backend-template/final/evidence/raw/312-outbox-assembly-and-jitter.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

72 lines
4.8 KiB
Plaintext

# 주제: messaging-outbox-jdbc-postgresql 조립 탐침과, 백오프 지터가 목적을 달성하지 못하는 지점
# revision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
# ---- 조립 탐침 (new <Type>( 정규식, 완전수식 포함) ----
JdbcOutboxRepository src/main=0 src/test=2 <-- 스타터가 만들지 않는다
OutboxRelay src/main=1 src/test=3
OutboxRelayWorker src/main=1 src/test=1
OutboxRetryScheduler src/main=2 src/test=6
OutboxCleanupJob src/main=1 src/test=3
OutboxEnvelopeFactory src/main=0 src/test=5 <-- 스타터가 만들지 않는다
JdbcAdminOperationJournal src/main=0 src/test=2 <-- 스타터는 InMemory 를 쓴다
DebeziumOutboxEventRouter src/main=1 src/test=2 (DebeziumOutboxRecordMapper 내부 필드)
DebeziumOutboxRecordMapper src/main=0 src/test=1 <-- 아무도 만들지 않는다
DebeziumOutboxProfile src/main=1 src/test=3 (자기 polling() 팩토리)
# src/main 생성 상세:
DebeziumOutboxProfile.java:44 new DebeziumOutboxProfile(RelayMode.POLLING, "", false) (자기 팩토리)
DebeziumOutboxRecordMapper.java:32 new DebeziumOutboxEventRouter() (자기 필드)
DebeziumOutboxRecordMapper.java:63 new DebeziumMappedRecord(...) (자기 반환)
OutboxRetryScheduler.java:84 new OutboxRetryScheduler(OutboxProperties.defaults(), 5분) (자기 standard())
MessagingReliabilityAutoConfiguration:63 new OutboxRetryScheduler(properties, Duration.ofMinutes(1))
MessagingReliabilityAutoConfiguration:89 new OutboxRelay(...)
MessagingReliabilityAutoConfiguration:109 new OutboxRelayWorker(relay, scheduler)
MessagingReliabilityAutoConfiguration:141 new OutboxCleanupJob(outbox, properties, 20)
# => 스타터 밖 생성은 전부 이 리프 내부의 자기 참조다.
# JdbcOutboxRepository / OutboxEnvelopeFactory / JdbcAdminOperationJournal 은
# 애플리케이션이 직접 빈으로 등록해야 한다.
# ---- 릴레이는 실제로 기동된다 (대조군) ----
# command: git grep -n "\.start()" -- src/messaging
# MessagingOutboxRelayLifecycle.java:42 worker.start();
# => OutboxRelayWorker javadoc:18-21 이 기록한 과거 결함
# ("The relay, its retry scheduler and the attempt budget all existed and nothing ever called
# runOnce")이 실제로 수정되어 배선까지 완료되어 있다.
# 이 문서에서 확인한 "선언되고 배선되지 않은" 항목들과 대비되는 사례다.
# ---- CDC 경로 전체가 소비되지 않는다 ----
# command: git grep -n "requireExactlyOneRelay|DebeziumOutboxProfile.polling|RelayMode" -- src
# 전부 DebeziumOutboxProfile.java 자기 자신 + DebeziumOutboxRecordMapperTest
# => requireExactlyOneRelay 프로덕션 호출부 0건.
# DebeziumOutboxProfile 클래스 javadoc:9-13 은
# "the incompatibility is therefore enforced at startup instead of documented"
# 라고 쓰지만, 기동 시 이것을 부르는 코드가 없다.
# 두 릴레이가 동시에 켜지는 구성을 막는 주체가 없다.
# ---- 백오프 지터가 복제본을 분산시키지 못한다 ----
# OutboxRetryScheduler.java:18-20 클래스 javadoc:
# "Jitter is applied deterministically from the attempt count rather than randomly. Several relay
# instances that all started at deployment time would otherwise synchronise their retries into a
# thundering herd, and a random source would make the schedule impossible to test."
#
# OutboxRetryScheduler.java:98-109
# public Duration backoff(int consecutiveEmptyOrFailedPasses) {
# if (consecutiveEmptyOrFailedPasses <= 0) return baseInterval;
# int exponent = Math.min(consecutiveEmptyOrFailedPasses, 20);
# long scaled = baseInterval.toMillis() << exponent;
# long capped = Math.min(scaled, maxInterval.toMillis());
# long jittered = capped - (capped / 8) * (exponent % 3);
# return Duration.ofMillis(Math.max(baseInterval.toMillis(), jittered));
# }
#
# jittered 는 exponent 만의 함수다. exponent 는 워커의 unproductivePasses 카운터에서 온다
# (OutboxRelayWorker.java:183, 192).
# 동시에 배포되어 같은 브로커 장애를 겪는 복제본들은 같은 카운터 값을 갖게 되므로
# 같은 backoff 를 계산한다. 즉 지터가 인스턴스 간 위상차를 만들지 않는다.
# 지터는 시도 횟수에 따라 값을 바꿀 뿐(0, 1/8, 2/8 감산), 인스턴스에 따라 바꾸지 않는다.
#
# 주: 행(row) 단위 백오프(nextAttemptAt)는 DB 컬럼 next_attempt_at 에 기록되므로
# 이 문제와 무관하다. 문제는 pass 단위 백오프에만 해당한다.
# javadoc 이 말하는 "several relay instances … synchronise their retries" 는 pass 단위 얘기다.