fix(audit): grow writeAuditEntry 412 backoff to 6 retries / ~3050 ms#282
Merged
Conversation
The previous mitigation (PR #277) widened the backoff from linear (~500 ms) to exponential jitter (~750 ms worst-case) but kept the 4-retry cap. Six days after that deploy the writeAuditEntry PreconditionFailed rate is still sustained at ~5614/24h on a hot audit ledger key, indicating that the contention window is wider than the per-write latency: every retry inside a short window observes the same losing-etag generation, so timing knobs alone have not converged. This change adds two more retry hops to the same exponential jitter formula, so the worst-case sleep ladder per attempt is now: 50, 100, 200, 400, 800, 1600 ms (~3050 ms total) The intent is reversible diagnostic data: if the rate does not drop below the <250/24h target after this, the next escalation is an append-only ledger layout (one S3 object per audit entry under the existing audit dir prefix) which eliminates the read-modify-write race entirely. Tests updated to assert the new contract (7 total attempts, 6 retries, new exponential ceiling) and an additional lockdown that delay[5] reaches the new ~800 ms floor on the final hop. Co-Authored-By: Paperclip <noreply@paperclip.ing>
…udget The audit retry bump (4 -> 6 with exponential jitter, PR #282) raised the worst-case write-audit budget from ~750 ms to ~3050 ms. "PUT with If-Match: returns 412 when ETag does not match and does not retry" mocks every PutObjectCommand to 412 so the audit writer exhausts its full retry budget. Two consequences after the bump: - Total PutObjectCommand calls: 6 -> 8 (1 main PUT + 7 audit attempts = 1 initial + 6 retries). - Wall time exceeds mocha's 2000 ms default, causing a timeout failure. Updated the count assertion and added `this.timeout(8000)` so the test covers the new 6-retry path without flaking. Co-Authored-By: Paperclip <noreply@paperclip.ing>
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
bosschaert
approved these changes
May 21, 2026
Collaborator
|
🎉 This PR is included in version 1.9.1 🎉 The release is available on:
Your semantic-release bot 📦🚀 |
This was referenced May 26, 2026
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
PR #277 widened the writeAuditEntry 412 backoff from linear (~500 ms) to exponential jitter (~750 ms worst-case) but kept the 4-retry cap. Six days after that deploy the PreconditionFailed rate is still sustained at ~5614/24h — the contention window is wider than the per-write latency, so every retry inside a short window observes the same losing-etag generation.
This change adds two more retry hops to the same jitter formula:
Reversible diagnostic step. If the rate does not converge to <250/24h after deploy, the next escalation is an append-only ledger layout (one S3 object per audit entry under the existing audit-dir prefix) which removes the read-modify-write race entirely. That follow-up is tracked separately.
What changed
src/storage/version/audit.js—attempt < 4→attempt < 6, docstring + comment refreshed to call out the contention model and the planned next escalation.test/storage/version/audit.test.js— bump the persists-all-attempts test from 5 → 7 attempts; expand the jitter-backoff lockdown to assert the new exponential ladder (upperBounds = [50, 100, 200, 400, 800, 1600]), a new floor check thatdelay[5] >= 800ms proves the 6th retry reached the new ceiling, and adjust total-sleep bounds to ~3050 ms.No production behaviour changes for the success path — only the failure tail (412 burst) now retries longer before logging.
Test plan
mocha test/storage/version/audit.test.js— 25/25 pass.mocha test/storage/version/put.test.js— 64/64 pass (call-site untouched).eslint src/storage/version/audit.js test/storage/version/audit.test.js— clean.Tracking