# 주제: EVD-294(bounded purge 미호출)의 구현 측 확인 — # 프로덕션은 무제한 DELETE 한 방을 쏘고, 그것을 가리는 것은 스크립트 대역이다 # revision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916 # severity: P1 (EVD-294 와 동일 항목의 구현 측 근거) # ---- 구현: 두 오버로드 ---- # JdbcOutboxRepository.java:486-518 bounded # public int purgePublishedBefore(Instant publishedBefore, int limit) { # if (limit < 1) throw new IllegalArgumentException("a bounded purge deletes at least one row per call"); # // The CTE picks a bounded set of ids with SKIP LOCKED and deletes exactly those. An unbounded # // DELETE holds locks and writes WAL in proportion to the whole backlog, which stalls the relay # // and the business writes behind retention. # WITH expired AS ( # SELECT message_id FROM messaging_outbox # WHERE status = 'PUBLISHED' AND published_at < ? # ORDER BY published_at LIMIT ? FOR UPDATE SKIP LOCKED) # DELETE FROM messaging_outbox o USING expired e WHERE o.message_id = e.message_id # # JdbcOutboxRepository.java:519-533 unbounded # public int purgePublishedBefore(Instant publishedBefore) { # "DELETE FROM messaging_outbox WHERE status = 'PUBLISHED' AND published_at < ?" # } # ---- 호출자는 무제한 쪽을 부른다 ---- # OutboxCleanupJob.java:44-57 # int removed = 0; # for (int batch = 0; batch < maxBatches; batch++) { # int deleted = outbox.purgePublishedBefore(cutoff); <-- 무제한 오버로드 # removed += deleted; # if (deleted == 0) break; # } # => 1회차가 전체를 지우고, 2회차가 0을 반환해 break. maxBatches 는 실질적으로 죽은 값이다. # ---- 배선 ---- # MessagingReliabilityAutoConfiguration.java:140-141 # public OutboxCleanupJob outboxCleanupJob(OutboxRepository outbox, OutboxProperties properties) { # return new OutboxCleanupJob(outbox, properties, 20); # } # MessagingReliabilityAutoConfiguration.java:169-170 # return new InboxCleanupJob(inbox, policy, 20); # => 둘 다 빈이며 maxBatches=20 하드코딩. 둘 다 무제한 오버로드를 부른다. # ---- 왜 테스트가 잡지 못하는가 ---- # OutboxOperationsTest.java:120-134 RecordingRepository # @Override # public int purgePublishedBefore(Instant publishedBefore, int limit) { # return Math.min(purgePublishedBefore(publishedBefore), limit); <-- 전부 지우고 숫자만 깎는다 # } # @Override # public int purgePublishedBefore(Instant publishedBefore) { # cutoffs.add(publishedBefore); # return pass < deletions.size() ? deletions.get(pass++) : 0; <-- 스크립트 # } # # OutboxOperationsTest.java:157-165 # void cleanupDeletesInBoundedBatchesRatherThanOneLongStatement() { # RecordingRepository repository = new RecordingRepository(List.of(1000, 1000, 250)); # int removed = new OutboxCleanupJob(repository, OutboxProperties.defaults(), 10).runOnce(NOW); # assertThat(removed).isEqualTo(2250); # assertThat(repository.cutoffs).hasSize(4); # } # => "여러 번 나눠 지운다" 는 관측은 전적으로 대역의 스크립트(1000,1000,250,0)가 만든 것이다. # 실제 JdbcOutboxRepository 를 넣으면 1회차에 전체가 지워지고 cutoffs 는 2가 된다. # # 실 DB 테스트도 무제한 쪽만 부른다: # OutboxPostgresIT.java:202 int removed = repository.purgePublishedBefore(NOW.plusSeconds(1)); # => bounded 오버로드는 전 저장소에서 호출부 0건이라는 EVD-294 의 판정이 구현 측에서도 확인된다. # (이 리프의 대역 2개 + inbox 대역 3개 + 포트 선언 2개 + 구현 2개 = 9개 등장, 호출 0)