Customers cant scan the QR getting too many requests error — rate-limit BUCKETS Map memory leak (PR #651 VII F1)
Owen (37) runs Cig & Cawl 40-cover bistro in Cardiff city centre serving traditional Welsh dishes. Mid-May customers started reporting "I scanned the QR but the order screen says Too Many Requests — I haven t ordered anything!" Friday evening 12 tables raised the same complaint. Support 3 wrong theories busted: threshold too tight (100/5min was fine), shared WiFi (40 covers wouldn t hit 100), retry pattern (loose). Workers memory metrics: Apr 15 12-18MB, May 1 28-35, May 10 45-58, May 15 72-95, May 18 (Friday) 112-127 — approaching 128MB limit. Monotonic memory leak signature. Forensic: apps/web-menu/src/lib/rate-limit-ip-hash.ts module-scope BUCKETS Map for per-IP-hash sliding-window counters. Entry count Apr 15 ~3,200 → May 18 ~84,500. Code does BUCKETS.set() on every request, BUCKETS.delete() NEVER called. Entries are overwritten on expiry but never removed — every unique IP that ever hit the endpoint stays as a permanent resident. Memory cost: 84,500 × ~150 bytes = ~12.6MB just for BUCKETS; plus other state + Workers runtime crept to 100+MB. As isolate approached 128MB, GC churn caused request latency + transient 429s during isolate cycling. **PR #651 batch VII F1** fix: opportunistic per-N-call cleanup pass. Every 500 calls, walk BUCKETS + delete expired entries. Cost ~20µs/call amortised. Alternatives: KV/D1-backed (latency 1-2ms→20-50ms, heavy traffic feels it); setInterval (Cloudflare Workers has no setInterval). Opportunistic chosen — minimal change, negligible overhead. Memory dropped Sat 28-35, Sun 18-22, +5d steady 16-20MB. False-positive 429s fell to near zero. Pattern: any in-process Map/Set/Object accumulating entries must have a cleanup mechanism. Sliding-window replace-on-expiry doesn t suffice — entries must be explicitly deleted. Audit checklist: module-scope state? entries deleted? cleanup pass? Workers memory monitored? steady-state profiled?