Nobody has started this yet — be first.
Business impact
This bug only manifests once you actually run the Redis backend -- i.e. the real, multi-instance production deployment shape, not local single-instance testing against the in-memory implementation. That's exactly the deployment most likely to matter most. With this live, a rule like free-tier (sold as "10 requests/minute") silently permits roughly double that at every window boundary, undermining the pricing/quota differentiation between tiers and removing the load-shedding protection any downstream system was sized around. It's also the kind of regression that passes every existing test (none of which exercise the Redis backend's edge timing) and would first be noticed in production, possibly during exactly the traffic spike the limiter exists to contain.
Problem
SlidingWindowMemory (Go) and SlidingWindowRedis (Lua, EVAL'd in Redis) are supposed to implement the identical algorithm -- that's the entire premise of "swapping backends never touches a call site" from the README. They no longer agree: right at a window boundary, the Redis-backed implementation computes the opposite of the intended weight for the previous window's contribution, which lets a full burst through that the in-memory implementation would correctly deny.
Current behavior
The Go implementation's weight calculation correctly starts near 1 right as a new window begins and decays toward 0 as the current window fills up. The Lua script computes weight using the opposite relationship -- it grows from 0 toward 1 over the window instead of decaying from 1 toward 0.
Expected behavior
At any given instant, SlidingWindowRedis.AllowN and SlidingWindowMemory.AllowN, given the same limit/window/request pattern, must make the same allow/deny decision (up to unavoidable clock skew).
Steps to reproduce
Create a SlidingWindowRedis (via a redis.Scripter, e.g. miniredis) with limit=5, window=2s. Fully consume the limit (5 requests) right as a window starts. Wait for the next window boundary (early into the following window -- elapsed_in_current is small). Make one more request for the same key. // got: allowed -- the buggy weight formula discards the previous window's count entirely at the boundary // want: denied -- the previous window (just filled to 5) is still almost entirely "in view" this early // into the new window, so 5+1 should exceed the limit of 5
Why this matters
This is exactly the burst-at-the-boundary problem that the two-window blending technique exists to prevent (a naive fixed-window counter has this exact flaw, which is the whole reason this smarter algorithm exists). With this bug, the Redis-backed free-tier rule effectively degrades into "reset fully every window boundary," i.e. a client can get roughly 2x their configured limit for free by timing requests around window edges, and the in-memory and Redis backends of the same rule would enforce visibly different effective limits.
Suggested approach
Compare the weight expression line-by-line against sliding_window.go's Go implementation of the same formula -- they should be structurally identical modulo Lua vs. Go syntax. Everything else in the Lua script (window rollover, HMSET/EXPIRE, the remaining calculation) is correct and should not need to change.
Acceptance criteria
Verification
go test ./test/ -run TestTask09 -v
Hints (0/2)
Try it without hints first — the reading is the exercise.
Working on this ticket
Work on a branch named for the ticket — that's what you'll submit.
Branch off your fork
Sign in to create it automatically, or do it yourself:
$git checkout -b fix/sliding-window-lua-weightFix it and commit
Meet every acceptance criterion, and add a test that would have caught this.
Push the branch
$git push -u origin fix/sliding-window-lua-weightSubmit it below
Paste your fork URL and the branch name, with a short write-up of the root cause.
Questions
Ask about anything unclear in the ticket — the maintainer and anyone who has solved it can answer. Please don't post full solutions.
Sign in to ask a question or reply.
Sign inhttp://localhost:8080localhost:6380http://localhost:8081curl localhost:8080/healthz
curl localhost:8080/api/v1/rules
curl -s -X POST localhost:8080/api/v1/check -d '{"rule":"free-tier","key":"user-123"}' | jq
No Docker? STORE_BACKEND=memory is the default, so go run ./cmd/server alone (no Redis needed) exercises everything except the Redis-backed code paths.
Work the tickets in tasks/ (ordered easy → hard); each names one Go test in test/:
make test-race # go test -race -count=1 ./...
go test ./test/ -run TestTask01 -v # a single ticket
./test/run_all.sh # all 15, pass/fail summary
Level 1
Fix a bug
Read existing behaviour, correct it.