# 주제: 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 단위 얘기다.
