perf: replace timestamp-array badge rate limiter with sliding window counter#1842
Open
Ridanshi wants to merge 1 commit into
Open
perf: replace timestamp-array badge rate limiter with sliding window counter#1842Ridanshi wants to merge 1 commit into
Ridanshi wants to merge 1 commit into
Conversation
Priyanshu-byte-coder#1818) The badge rate limiter stored a full array of request timestamps per IP (Map<string, number[]>). Under heavy traffic this allocates up to BADGE_LIMIT (20) Date objects per client and filters the array on every request — O(N) work and O(N) memory per client. Replace with a sliding window counter that stores exactly three numbers per client — prevCount, currCount, and windowStart — giving O(1) memory and O(1) per-request work regardless of traffic volume. Algorithm (weighted dual-bucket): windowStart = floor(now / WINDOW_MS) * WINDOW_MS estimate = floor(prevCount * (1 - elapsed/WINDOW_MS)) + currCount When the clock crosses a window boundary the current counter is promoted to prevCount and the new window starts at zero. Entries older than one full window are removed from the store during the periodic prune that runs when store.size >= 500. Behaviour is preserved for callers: - same 20 req/60 s limit enforced - allowed / remaining / reset fields still returned - 429 response with Retry-After still issued when limit is exceeded - same endpoint interface; no new dependencies The reset field now points to the fixed window boundary rather than the oldest-timestamp expiry; the difference is sub-second in practice and makes the value more predictable for clients. Also fix getBadgeClientIp to fall back to req.ip (available in Next.js edge runtime) before returning unknown. Tests updated and extended to cover single-window enforcement, the sliding-window cross-boundary reduction of capacity, two-window expiry, reset field progression, and the req.ip fallback path (15 tests, all passing).
|
@Ridanshi is attempting to deploy a commit to the PRIYANSHU DOSHI's projects Team on Vercel. A member of the Team first needs to authorize it. |
GSSoC Label Checklist 🏷️@Priyanshu-byte-coder — please apply the appropriate labels before merging: Difficulty (pick one):
Quality (optional):
Validation (required to score):
|
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.
Closes #1818
Investigation findings
The badge rate limiter in
src/lib/badge-rate-limit.tsstored aMap<string, number[]>(timestamp arrays). On every request it filtered the array to remove expired entries and appended the current timestamp — O(N) allocations and O(N) memory per client, where N is the request limit (20). The prune step scanned all 500+ entries only when the map grew large.Issue confirmed.
Implementation
Replaced the timestamp-array store with a sliding window counter. Each client entry now holds exactly three numbers:
Rate estimate for any instant within the current window:
When the clock crosses a window boundary,
currCountis promoted toprevCountand a freshcurrCountstarts from zero. Entries whose window has fully elapsed are removed during the periodic prune (threshold still 500 entries, cutoff condition simplified toentry.windowStart < now - WINDOW_MS).Memory and performance improvement
Behaviour preservation
allowed,remaining, andresetfields still returned on every call.Retry-Afterheader still issued when the limit is exceeded.resetnow points to the fixed window boundary (e.g. epoch second 60 for a request at t=1 s) rather than the oldest-timestamp expiry. The difference is at most one window period and makes the value more predictable for clients.Additional fix
getBadgeClientIpnow falls back toreq.ip(available in Next.js edge runtime) before returning"unknown", which was the behaviour a pre-existing test expected but the old code never implemented.Files modified
src/lib/badge-rate-limit.ts— sliding window counter implementationtest/badge-rate-limit.test.ts— updated reset expectation; added 7 new testsTest plan
npx vitest run test/badge-rate-limit.test.ts— all 15 tests passreq.ipfallback returns the correct IP address