fix(cost): widen reservation sweep to hourly so Neon can auto-suspend - #123
Merged
Merged
Conversation
The ReservationSweeper queried Postgres every SweepIntervalMinutes=5. Neon's compute auto-suspends after ~5 minutes idle, so a query every 5 minutes reset the idle timer just before it could sleep — holding the database awake ~24/7. At Neon's 0.25 CU floor that is ~180 CU-hrs/month, nearly double the 100 CU-hr free allowance (the project hit 80% mid-month). The sweep only reclaims stock from orders left in AwaitingPayment past ExpireAfterMinutes (15) — pure housekeeping, not latency-sensitive. Widening it to hourly lets Neon sleep between passes (~5 min awake per sweep, ~15 CU-hrs/mo, a ~12x cut) while still returning abandoned-checkout stock within ~75 minutes. No code change — config only. keep-warm still pings /health (no DB query), so the API stays warm without holding the database open. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…riant The first pass changed only appsettings, which broke two guard tests: ShippedConfigurationTests requires the file to match the code defaults, and another test requires ExpireAfterMinutes > SweepIntervalMinutes (stock must be reclaimed faster than the window or expired orders sit unreleased). So the code defaults move with the file: SweepIntervalMinutes 5 -> 60 and ExpireAfterMinutes 15 -> 90 (window stays above the interval). Comments updated to explain both — the interval is set by Neon's ~5-min auto-suspend (a shorter one pins the DB awake ~24/7), and the longer hold is harmless on this demo where no real inventory is at stake. Verified locally: ShippedConfigurationTests and ReservationSweeperTests pass (7/7). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Raising the default ExpireAfterMinutes to 90 (for Neon cost) broke handler tests that park orders ~30 min ago and expect a release — correct against the old 15-min default, stale-but-inside-window against 90. These are behaviour tests of "past the threshold -> released", so they should fix their own window rather than ride on the production default. Each now sets ExpireAfterMinutes = 15 explicitly. Verified: WidgetWorks.UnitTests 369/369. (The reservation/config guard tests in ApiTests already pass, 7/7.) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.
Stops WidgetWorks from burning through the Neon free compute allowance (project hit 80% of 100 CU-hrs mid-month).
Root cause
ReservationSweeperqueried Postgres everySweepIntervalMinutes = 5. Neon compute auto-suspends after ~5 min idle, so a query every 5 minutes reset the idle timer right before it could sleep — holding the database awake ~24/7 (~180 CU-hrs/month, nearly double the free tier). keep-warm is not the cause — it pings/health, which runs no DB query.Fix (Option 1: slow the sweep)
Sweep hourly so Neon sleeps between passes. The repo enforces
ExpireAfterMinutes > SweepIntervalMinutes(stock must be reclaimed faster than the window, or expired orders sit unreleased), andShippedConfigurationTestsrequires appsettings to match the code defaults — so both the code defaults and appsettings move together, and the window is lengthened to keep the invariant:SweepIntervalMinutesExpireAfterMinutesShippedConfigurationTests+ReservationSweeperTestspass (7/7).taskboard (ToDoApp)
Checked — no background poller, keep-warm hits a no-DB endpoint,
dapperdoesn't deploy. Not at risk; no change needed.🤖 Generated with Claude Code