fix(api/submissions): make POST idempotent via Idempotency-Key header#2426
Open
AybH26 wants to merge 1 commit into
Open
fix(api/submissions): make POST idempotent via Idempotency-Key header#2426AybH26 wants to merge 1 commit into
AybH26 wants to merge 1 commit into
Conversation
fa09f4e to
5a48517
Compare
Collaborator
|
Seems good to me, but a little detail about the model file : better use: see: https://docs.djangoproject.com/en/5.2/ref/models/options/ |
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.
@ mention of reviewers
@ @
A brief description of the purpose of the changes contained in this PR
POST /api/submissions/was not idempotent. Any client retry — network timeout, double-click on the Submit button, proxy / load-balancer retransmission after a 504 — created a brand-newSubmissionrow, enqueued a brand-newcompute_worker_runtask, and produced a duplicate leaderboard entry. There was no server-side dedup and no unicity constraint protecting against retries.This PR introduces Stripe-style idempotency on submission creation:
IdempotencyRecordmodel keyed by(owner, endpoint, key)(unique_together).IdempotentCreateMixinthat wraps theSubmissionViewSet.create()flow: it reads theIdempotency-Keyrequest header, atomically reserves the key, replays the original response on retry, and rejects mismatched payloads with409 Conflict(Stripe semantics).GET /api/submissions/receipt/<key>/endpoint so a client that lost its connection mid-POST can pull the original response back without replaying.Issues this PR resolves
Closes #2425
Symptoms reported / reproduced:
POST /api/submissions/→ 2Submissionrows, 2 worker runs, 2 leaderboard entries.Root cause: no idempotency layer on submission creation. Each retry was treated as a brand-new request.
Fix: header-driven Stripe-style idempotency.
IdempotencyRecord(owner, endpoint, key)withunique_togetherand arequest_fingerprintso the same key with a different payload is rejected (409 Conflict) instead of silently aliased.IdempotentCreateMixinplugged intoSubmissionViewSet:400 Bad Request,get_or_createreserves aPENDINGrecord, executes the real create, storesresponse_status+response_body+ FK to theSubmission,409 Conflict.GET /api/submissions/receipt/<key>/:PENDING(creation in flight),A checklist for hand testing
Idempotency-Key: <uuid>→ 201. Replay the exact same POST → 200/201 with the same body, only one row inSubmission, only one worker run.curl --retry 3simulating a 504 between gateway and Django → single submission in DB.phase/datapayload →409 Conflict.Idempotency-Key→400 Bad Request(or current behaviour if the header is opt-in — verify with reviewer).GET /api/submissions/receipt/never-seen-key/→404.GET /receipt/<key>/while still pending →202.GET /receipt/<key>/→200with the same body the first POST returned.IdempotencyRecordrow per logical submission attempt; FKsubmission_idset after the create.Any relevant files for testing
New: src/apps/api/idempotency.py —
IdempotentCreateMixin.Modified: src/apps/api/views/submissions.py — mixin plugged into
SubmissionViewSet, newreceiptaction.New model: src/apps/competitions/models.py (
IdempotencyRecord).Migration: src/apps/competitions/migrations/0062_idempotencyrecord.py — additive, no backfill.
Sample requests for hand-testing (drag-and-drop or copy from this PR description):
Checklist