Repository navigation
feat(server): keep hour and day rate limits across restarts - #132
Merged
Merged
Conversation
Every limiter lived in a process-local map, so each deploy handed every caller a fresh register, invite, waitlist and code-redeem budget, and a second instance would multiply them. Limiters with hour or day windows now also count hits in a rate_limit_hits table. The in-memory check still runs first, so a flood is rejected without touching the database; an admitted request then takes a per-key transaction advisory lock, prunes that key's expired hits, counts and inserts. If the database is unreachable the in-memory count decides. The daily retention sweep removes hits older than a day, which is why .durable() refuses longer windows. Auth, sync, search and the per-minute limiters stay in memory: they are hot, and their windows are about as short as a restart. Refs VoltiusApp/voltius#800
This was referenced Nov 11, 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.
Part of #133. Fixes a gap that exists on a single instance today, and removes most of the rate-limit row from the multi-instance blockers.
Problem
Every
RateLimiterwas a process-local map, so each deploy reset every budget. Register (20/day per IP), invite, waitlist, knock, session-code mint/redeem, grant mint/redeem, enrolment redeem/register and resend-verification all started over after a restart. A second instance would also multiply each limit.Change
RateLimiter::durable(pool, bucket)also counts hits in a newrate_limit_hitstable (migration 071).pg_advisory_xact_lock(hashtext(bucket), hashtext(key)), prunes that key's expired hits, counts them and inserts a new one. The two-key lock form has its own key space, separate from the single-key instance lock.DURABLE_WINDOW_MAX(1 day)..durable()asserts no window is longer than that.Tests
-D warningsclean.