mirror of
https://github.com/lukaszraczylo/graphql-monitoring-proxy.git
synced 2026-11-05 08:14:19 +00:00
The sustained gate stored one entry for every attempt, denied ones too, and summed all entries on each request. A flood grew memory with rate times interval and made each request slower. Both gates now keep only the newest limit plus one attempt times. A Lean proof shows this decides the same as the full count. One check dropped from 29,502 ns and 11 allocations to 43 ns and none. The go-ratecounter dependency is gone.
65 lines
1.6 KiB
Go
65 lines
1.6 KiB
Go
package main
|
|
|
|
import (
|
|
"math/rand"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// naiveExceeds is the exact reference: count every attempt in the trailing window.
|
|
func naiveExceeds(attempts []time.Time, now time.Time, window time.Duration, limit int) bool {
|
|
cutoff := now.Add(-window)
|
|
n := 0
|
|
for _, t := range attempts {
|
|
if t.After(cutoff) {
|
|
n++
|
|
}
|
|
}
|
|
return n > limit
|
|
}
|
|
|
|
// Pins theorems/Theorems/SlidingWindow.lean: storing the newest limit+1 attempts decides like the full count.
|
|
func TestAttemptWindow_MatchesExactCount(t *testing.T) {
|
|
for _, limit := range []int{0, 1, 3, 10} {
|
|
rng := rand.New(rand.NewSource(int64(limit) + 1))
|
|
window := 10 * time.Second
|
|
fc := newFakeClock(time.Unix(1_000_000, 0))
|
|
w := newAttemptWindow(window, limit)
|
|
w.now = fc.Now
|
|
w.base = fc.Now()
|
|
|
|
var attempts []time.Time
|
|
for i := 0; i < 5000; i++ {
|
|
fc.Advance(time.Duration(rng.Intn(3000)) * time.Millisecond)
|
|
attempts = append(attempts, fc.Now())
|
|
want := naiveExceeds(attempts, fc.Now(), window, limit)
|
|
if got := w.recordAndExceeds(); got != want {
|
|
t.Fatalf("limit=%d step=%d: got %v, want %v", limit, i, got, want)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestAttemptWindow_StorageIsBounded(t *testing.T) {
|
|
const limit = 10
|
|
fc := newFakeClock(time.Unix(1_000_000, 0))
|
|
w := newAttemptWindow(time.Hour, limit)
|
|
w.now = fc.Now
|
|
w.base = fc.Now()
|
|
for i := 0; i < 100_000; i++ {
|
|
w.recordAndExceeds()
|
|
fc.Advance(time.Millisecond)
|
|
}
|
|
if got := len(w.ring); got > limit+1 {
|
|
t.Fatalf("stored %d timestamps, want <= %d", got, limit+1)
|
|
}
|
|
}
|
|
|
|
func BenchmarkAttemptWindow(b *testing.B) {
|
|
w := newAttemptWindow(time.Hour, 1000)
|
|
b.ReportAllocs()
|
|
for i := 0; i < b.N; i++ {
|
|
w.recordAndExceeds()
|
|
}
|
|
}
|