Saga Retention

Completed sagas are kept as tombstones so a late or duplicate event is skipped idempotently rather than resurrecting a finished saga. Retention removes those tombstones once they are old enough to no longer serve that purpose.

Retention is off by default. When enabled, a single-active sweeper periodically deletes completed sagas whose completion is older than a configurable age.

Enabling retention

spring:
  ddd:
    saga:
      retention:
        enabled: true
        max-age: P30D        # delete completed sagas older than 30 days

With only enabled: true, the sweep runs every hour. Completion is recorded in the completed_at column of the saga instance table; the sweeper deletes rows whose completed_at is older than max-age.

A short max-age (for example PT30S) approximates delete-on-completion while still keeping the idempotency tombstone for the grace window.

Scheduling

Choose either a fixed delay or a cron expression — setting both while enabled is rejected at startup.

Property Behaviour

polling.interval

Fixed delay between sweeps (for example 1h). Used when polling.cron is disabled.

polling.cron

Spring 6-field cron expression (for example 0 0 3 * * * for 03:00 daily). - (the default) or an empty string disables cron. When set, it drives the sweep and polling.interval must be left unset.

Cron is the better fit for a maintenance sweep — it lets you target an off-peak window instead of an arbitrary wall-clock time.

spring:
  ddd:
    saga:
      retention:
        enabled: true
        max-age: P30D
        polling:
          cron: "0 0 3 * * *"   # 03:00 every day

Per-saga overrides

Override the window or opt a saga type out entirely, keyed by the saga’s @Saga(type) id. Each field falls back to the global value. Overrides refine the global policy while it is active — retention runs only when the top-level enabled is true, so a per-type enabled: true cannot switch retention on by itself.

spring:
  ddd:
    saga:
      retention:
        enabled: true
        max-age: P30D
        overrides:
          order-saga:
            enabled: false     # never delete order sagas
          payment-saga:
            max-age: P7D        # keep payment sagas for 7 days

How the sweep runs

The sweeper is a single active instance across a deployment: each run acquires a lease before deleting, so a saga is swept once even with many application instances. The lease reuses the deadline sweep-lock table (SAGA_DEADLINE_LOCK) under a distinct lock name, so retention needs no additional table. Deletion runs in batches of polling.batch-size (default 100, maximum 1000) and drains until a batch is not full.

The SAGA_DEADLINE_LOCK table is created by the deadline schema initializer, which runs whenever spring.ddd.saga.deadline.schema.auto is true (the default) — even if the deadline poller itself is disabled. If you manage the schema externally (spring.ddd.saga.deadline.schema.auto=false) and enable retention, provision SAGA_DEADLINE_LOCK yourself, otherwise every sweep fails to acquire the lease.

For the full spring.ddd.saga.retention property table, see Configuration properties.