Repository navigation
Queue syncs in RabbitMQ with retries and a dead-letter queue - #21
Merged
Merged
Conversation
POST /sync now puts a ProjectSyncRequest on the durable queue resourceapi.project-sync (202 once the broker confirmed it, 503 if RabbitMQ is unreachable) instead of running it on the in-memory @async executor. Requests survive restarts and wait for a free consumer instead of being refused. ProjectSyncConsumer (4 consumers per instance, virtual threads, prefetch 1, trace continued from the request) runs the sync with Spring's RetryTemplate: SDLC failures that may pass (unreachable, 5xx, 429, our own rate limit) are retried after 1s and 2s; a sync that still fails, or fails otherwise, is dead-lettered to <queue>.dead and logged in one line, without the upstream body. The per-project lock is taken by the request and released by the consumer via the owner id carried in the message. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012MLyPXGHeCc1fvTeB8JnT2
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Third of three event/messaging PRs, stacked on #20 (outbox notifications), which is stacked on #19 (Spring events).
Before:
POST /api/v2/projects/{id}/syncran the sync on the in-memory@Asyncexecutor (at most 8 at once). A crash lost queued work, and a busy executor answered 503.Now the request goes on a durable RabbitMQ queue:
ProjectSyncService):ProjectSyncRequesttoresourceapi.project-syncand waits for the broker's publisher confirm;Retry-After: 5, and the lock is released.ProjectSyncConsumer,@RabbitListener):prefetch=1so waiting requests go to whichever instance is free;traceparentheader).RetryTemplate: up to 2 retries after 1 s and 2 s. They apply only to failures that may pass:5xxor429;resourceapi.project-sync.dead, where it can be inspected and shovelled back.default-requeue-rejected=false, so a failing message never loops. A project deleted in the meantime is skipped.ProjectSyncLock.lease(projectId, ownerId)rebuilds the lease. A request waiting longer than the lease (60 s) loses its lock (documented).ProjectSyncWorkeris now a plain synchronoussync(id)that throws; the consumer decides what to retry and logs.Things that came up
ProjectSyncIntegrationTestassertion caught it. Fixed: the consumer logs the status only and rejects without the cause, and aContainerCustomizermakes deliberate dead-lettering log one line instead of a stack trace. Other listener failures still log in full.ProjectSyncBackpressureTest(executor full → 503): that behavior no longer exists. A burst now waits in the queue, covered by the new test below.spring.context.exit=onRefresh) exits before listener containers start, sodocker buildstill needs no broker. No training profile was needed.Tests
ProjectSyncQueueTest(one consumer):503then200: synced after one retry (2 calls);500): 3 calls, then dead-lettered, and the lock is free again;403: no retry (1 call), dead-lettered.RabbitUnavailableTest:POST …/sync→ 503 +Retry-Afterwithin the connection timeout, the lock is released, and reads still work.ProjectSyncIntegrationTest(trace through the queue, status-only logging, re-key guard) and the lock tests (409, release when the sync ends, lease expiry).Full suite: 128 tests pass.
AOT and native images of this head, run against Redis 8, RabbitMQ 4 and a WireMock stand-in for Jira:
🤖 Generated with Claude Code
https://claude.ai/code/session_012MLyPXGHeCc1fvTeB8JnT2