Skip to content

Plan: Implement retry policies with exponential backoff and dead-letter handling (5.2.3) - #457

Draft
leynos wants to merge 3 commits into
mainfrom
5-2-3-retry-policies-with-exponential-backoff-and-dead-letter-handling
Draft

Plan: Implement retry policies with exponential backoff and dead-letter handling (5.2.3)#457
leynos wants to merge 3 commits into
mainfrom
5-2-3-retry-policies-with-exponential-backoff-and-dead-letter-handling

Conversation

@leynos

@leynos leynos commented Aug 16, 2026

Copy link
Copy Markdown
Owner

Summary

This draft pull request delivers the ExecPlan for backend roadmap item 5.2.3:
retry policies with exponential backoff and dead-letter handling for the
Apalis (PostgreSQL) queue adapter. It is a planning-only change; no
implementation begins until the plan is approved.

Plan document (branch-wise link):
docs/execplans/5-2-3-retry-policies-with-exponential-backoff-and-dead-letter-handling.md

The plan proposes:

  • A pure domain retry policy (JobRetryPolicy) computing bounded, jittered
    exponential backoff under a hard total-backoff budget, with a
    retryable/fatal error classification, executed in production via a
    delegating tower::retry::Policy so the verified function is the running
    function.
  • A DeadLetterQueue domain port with a PostgreSQL adapter over the
    apalis.jobs table: ordered listing, guarded compare-and-swap requeue
    with provenance, and guarded discard-as-delete.
  • Storage-level dead-lettering of fatal failures, compensating for the
    incomplete abort-to-Killed path in apalis-postgres 1.0.0-rc.6.
  • Verification via rstest unit tests, proptest invariants, googletest
    and pretty_assertions assertions, insta snapshot stability, and
    rstest-bdd behavioural scenarios against embedded PostgreSQL, plus
    rc-coupling canary tests to catch upstream drift on the eventual 1.0-final
    pin bump.
  • Two ADRs (retry mechanism; dead-letter representation), an operator
    runbook, and documentation updates, with worker consumption and
    deployment explicitly deferred to roadmap 5.3.1.

The draft was revised after a six-lens expert design review (structure,
contracts, scaling, operations, alternatives, and long-term viability);
the revision note at the end of the plan summarizes the changes.

References

🤖 Generated with Claude Code

Summary by Sourcery

Documentation:

  • Introduce a detailed ExecPlan outlining retry policy design, dead-letter queue semantics, and associated operational and testing guidance for backend roadmap item 5.2.3.

leynos and others added 3 commits August 16, 2026 05:30
Add the execution plan for backend roadmap item 5.2.3: bounded
exponential-backoff retry policies and dead-letter handling for the
Apalis (PostgreSQL) queue adapter.

The plan records the Apalis 1.0-rc capability research (tower-based
in-process retry, no built-in dead-letter API, incomplete abort
persistence at rc.6), proposes a pure domain retry policy plus a
`DeadLetterQueue` port with a PostgreSQL adapter, and defines the
verification obligations (rstest, proptest, rstest-bdd against
embedded PostgreSQL). Status is DRAFT pending expert-panel revision
and user approval.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The typos gate rejects "requeueing"; use the accepted spelling.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fold the design-review panel's findings into the plan:

- Make fatal dead-lettering a storage-level action: an early fatal
  failure acked as `Failed` with spare attempts would otherwise be
  re-delivered by `apalis.get_jobs` and invisible to the dead
  predicate (D4 rewritten; V5 asserts non-re-delivery).
- Reconcile the two retry loops (tower in-process vs storage
  refetch) via the named axiom AXIOM-ATTEMPT, an EP-M0 compile
  probe, and a total-executions assertion in V4.
- Make the domain policy the executed policy by delegating a custom
  `tower::retry::Policy` to `JobRetryPolicy` (D1 rewritten).
- Add a TOTAL_BACKOFF_BUDGET constructor constraint (V1b) and record
  the orphan-recovery inequality 5.3.1 must respect.
- Document per-lease attempt semantics, crash-loop risk, per-attempt
  logging, and the fact that orphan recovery is unconfigured today.
- Harden the dead-letter port: guarded compare-and-swap requeue and
  discard with `NotDead` errors, requeue provenance, discard as
  delete (D5), `DeadJobId` newtype, `dead_at`, `#[non_exhaustive]`,
  ordering, payload privacy, and a forbid on automated requeue.
- Promote retry/dead-letter metrics to named deliverables; add
  rc-coupling canary tests (V8), attempt-mapping tests (V3b), an
  optional partial index (D6), per-instance policy (D7), an operator
  runbook, a harness promotion path for 5.3.1, and split the ADR
  into ADR-002 (retry mechanism) and ADR-003 (dead-letter
  representation).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 2d050341-e3b8-43dd-926c-6c99a89ad059

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Comment @coderabbitai help to get the list of available commands.

@sourcery-ai

sourcery-ai Bot commented Aug 16, 2026

Copy link
Copy Markdown

Reviewer's Guide

Planning-only ExecPlan document for backend roadmap item 5.2.3, introducing a domain-level retry policy with bounded exponential backoff and jitter, a DeadLetterQueue port with a PostgreSQL adapter over apalis.jobs, storage-level fatal dead-lettering, verification strategy (unit/property/BDD/integration tests), ADRs, and documentation/runbook updates—no runtime behaviour changes yet.

Sequence diagram for planned retry and fatal dead-letter handling

sequenceDiagram
    participant Worker
    participant JobHandler
    participant JobRetryPolicy
    participant RetryLayer
    participant DeadLetterQueueAdapter
    participant ApalisJobs

    Worker->>RetryLayer: process_job()
    RetryLayer->>JobHandler: handle()
    JobHandler-->>RetryLayer: Err(JobErrorKind_Retryable)
    RetryLayer->>JobRetryPolicy: decide(attempt, Retryable, jitter)
    JobRetryPolicy-->>RetryLayer: RetryDecision_Retry(delay)
    RetryLayer->>RetryLayer: wait_backoff(delay)
    RetryLayer->>JobHandler: handle() (retry)

    alt [fatal error]
      JobHandler-->>RetryLayer: Err(JobErrorKind_Fatal)
      RetryLayer->>JobRetryPolicy: decide(attempt, Fatal, jitter)
      JobRetryPolicy-->>RetryLayer: RetryDecision_DeadLetter
      RetryLayer->>DeadLetterQueueAdapter: mark_fatal_dead(job_id)
      DeadLetterQueueAdapter->>ApalisJobs: UPDATE status = Killed
      DeadLetterQueueAdapter-->>RetryLayer: Ok
    end
Loading

File-Level Changes

Change Details Files
Add detailed ExecPlan document for implementing retry policies with bounded exponential backoff, fatal/retryable classification, and dead-letter handling on the Apalis PostgreSQL queue adapter, including interfaces, invariants, milestones, verification obligations, and ADR/runbook work.
  • Define domain-level JobRetryPolicy concept with bounded, jittered exponential backoff, max_attempts, and total-backoff budget requirements, plus JobErrorKind and RetryDecision classification to be executed via a delegating tower::retry::Policy in adapters.
  • Specify DeadLetterQueue domain port and PostgreSQL adapter design over apalis.jobs for listing, guarded compare-and-swap requeue with provenance, and guarded discard-as-delete, with dead predicate based on Killed or exhausted Failed rows.
  • Capture constraints, tolerances, risks, external Apalis axioms, and rc-coupling canaries, including AXIOM-FACADE, AXIOM-ATTEMPT, AXIOM-ACK, and explicit fatal dead-lettering requirements compensating for missing abort-to-Killed semantics in apalis-postgres rc.6.
  • Lay out verification plan with property tests for backoff invariants, BDD scenarios on embedded PostgreSQL for retry/dead-letter behaviour, and rc-canary tests to catch upstream drift, plus Makefile gate usage.
  • Define plan-of-work milestones (EP-M0–EP-M4) covering approval/compile probe, domain policy implementation, Apalis retry wiring and bounded attempts, dead-letter port and adapter implementation, and ADR/documentation/runbook updates.
  • Record architectural decisions (ADR-002, ADR-003) and a decision log around using Apalis's retry facade with a delegating Policy, in-process backoff bounded by TOTAL_BACKOFF_BUDGET, dead letters as apalis.jobs rows with guarded transitions, storage-level fatal kill, optional partial index, and per-queue retry policy configuration.
docs/execplans/5-2-3-retry-policies-with-exponential-backoff-and-dead-letter-handling.md

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

codescene-access[bot]

This comment was marked as outdated.

@codescene-access codescene-access Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No quality gates enabled for this code.

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