Skip to content

feat: OutboxPurger v2 — configurability, reliability, batched delete (KOJAK-56)#11

Open
endrju19 wants to merge 14 commits intomainfrom
feat102
Open

feat: OutboxPurger v2 — configurability, reliability, batched delete (KOJAK-56)#11
endrju19 wants to merge 14 commits intomainfrom
feat102

Conversation

@endrju19
Copy link
Collaborator

Summary

Makes OutboxPurger a production-ready, configurable component — establishing a pattern to replicate for OutboxScheduler/ProcessorScheduler.

  • Batched delete with FOR UPDATE SKIP LOCKED (Postgres subquery, MySQL derived table wrapper) — prevents full table scans and lock contention on large tables
  • Error handlingtry/catch in tick() prevents ScheduledExecutorService from silently dying on exceptions
  • Configurability@ConfigurationProperties binding from application.yml (okapi.purger.retention-days, interval-minutes, batch-size, enabled)
  • SmartLifecycle — replaces SmartInitializingSingleton + DisposableBean for phase-ordered startup/shutdown
  • Database index(status, last_attempt) composite index for efficient purge queries
  • SLF4J logging — INFO on start/stop, DEBUG per tick, ERROR on failures

Breaking change

OutboxStore.removeDeliveredBefore(Instant)removeDeliveredBefore(Instant, Int): Int — accepts batch limit, returns count deleted. Pre-1.0, no external consumers.

Key design decisions

Decision Rationale
FOR UPDATE SKIP LOCKED in purge Multi-instance safety without distributed locks
Manual spring-configuration-metadata.json KAPT maintenance mode, KSP unsupported by Spring (2026)
implementation for SLF4J Types don't leak to public API, consistent with Exposed
MAX_BATCHES_PER_TICK = 10 Safety guard: max 1000 entries per tick, prevents DB monopolization
check(!scheduler.isShutdown) Prevents cryptic RejectedExecutionException on restart after stop

JIRA

Test plan

  • 11 unit tests in OutboxPurgerTest (batch loop, error recovery, double-start, restart guard, validation)
  • 8 Spring integration tests in OutboxPurgerAutoConfigurationTest (property binding, conditional beans, lifecycle, stop callback)
  • MySQL removeDeliveredBefore with limit test (Testcontainers)
  • E2E tests pass (Postgres + WireMock, MySQL + WireMock)
  • ktlint clean

endrju19 added 14 commits March 26, 2026 14:28
…and return count

Breaking change: removeDeliveredBefore(Instant) -> removeDeliveredBefore(Instant, Int): Int
Store implementations temporarily ignore limit parameter.
…nding and conditional beans

Also fix nested PostgresStoreConfiguration to use proxyBeanMethods=false
(Kotlin classes are final; CGLIB proxying requires non-final classes)
and add assertj-core to version catalog (required by spring-boot-test's
ApplicationContextRunner).
The enabled flag is handled by @ConditionalOnProperty on the bean factory.
Having it also in the properties class was redundant — no code read it.
…, KDoc, test coverage

- OutboxPurger.start(): check(!scheduler.isShutdown) prevents restart after stop
- OutboxPurgerScheduler.stop(callback): try-finally guarantees callback invocation
- OutboxStore.removeDeliveredBefore: verbose KDoc with @param/@return contract
- OutboxAutoConfiguration.outboxStore(): restore concrete PostgresOutboxStore return type
- OutboxPurgerTest: assert batchSize is forwarded as limit, test start-after-stop
…ies, improve test assertions

- Rename OkapiPurgerProperties -> OutboxPurgerProperties (consistent with Outbox* naming convention)
- Add latch.await() shouldBe true assertions for better timeout diagnostics
- Add stop callback invocation test for SmartLifecycle contract
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant