Skip to content

DOC-6999: port alternative rate limiter algorithms to the other eight client pages - #3861

Merged
andy-stark-redis merged 2 commits into
mainfrom
DOC-6999-rate-limiter-alt-algorithms
Aug 26, 2026
Merged

DOC-6999: port alternative rate limiter algorithms to the other eight client pages#3861
andy-stark-redis merged 2 commits into
mainfrom
DOC-6999-rate-limiter-alt-algorithms

Conversation

@andy-stark-redis

@andy-stark-redis andy-stark-redis commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Follow-up to #3788, which added four extra rate limiting algorithms to the redis-py page only. This ports them to the remaining eight client pages. DOC-6999

What this adds

The Alternative rate limiting algorithms section — comparison table plus fixed window counter, sliding window log, sliding window counter, and leaky bucket (policing) — on dotnet, go, java-jedis, java-lettuce, nodejs, php, ruby, and rust.

Eight pages, not the six named in #3788: java is two separate pages (Jedis and Lettuce), and ruby was omitted from that list.

Approach

The four Lua scripts are the algorithm, so they are reused byte-for-byte — only the wrapper around each script is per-language. The sections were generated from a script that extracts the Lua and the language-agnostic prose once from the redis-py page, rather than being written out eight times by hand.

Verified after the fact by extracting all 36 script instances (9 pages x 4 scripts) back out of the written files and diffing them against the originals: byte-identical once the host string literal's indentation is stripped.

Two judgment calls

  • Lettuce is sync only. That page also carries async and reactive token bucket variants; the new algorithms follow the sync form only, matching the majority of its existing examples.
  • Snippets, not runnable source. The token bucket on every page is backed by committed demo files (token_bucket.py, TokenBucket.cs, and so on). These four algorithms are snippets, exactly as redis-py left them. Adding 32 runnable demos would be a much larger job and belongs in its own ticket.

Placement

The section sits immediately before ## Learn more, i.e. after ## Customization. On redis-py those are the same position (it has no Customization section). Mirroring redis-py's literal offset — straight after ## Response headers — would have split the token bucket material on the other eight, because each page's Customization section refers back to the token bucket limiter.

One correction to the merged redis-py page

The section intro claimed all four algorithms derive the timestamp from the server clock via redis.call('TIME'). Three do; the fixed window counter reads no clock at all and leans on the key's TTL. Reworded on all nine pages rather than copied eight more times.

Verification

  • Compiled against real libraries: jedis 7.5.3, lettuce-core 7.7.0, StackExchange.Redis 2.9.32, and redis 0.24.1 + uuid (0.24 is the version the Rust page pins).
  • Parsed: gofmt -e (clean, no formatting diff), node --check, ruby -c, php -l.
  • Run against a live server: the four Lua scripts directly, and the full Ruby wrappers end to end. Limit 3 gave allow/allow/allow/deny with sensible retry-after in every case; the sliding window log's sorted set held exactly 3; the leaky bucket returned ~1s retry at leak rate 1/s. The Ruby run also produced key {rswc}:178767316, confirming the hash-tag marshalling yields the Cluster-safe key the prose describes.
  • Hugo build clean, with all six comparison-table anchors resolving in the rendered HTML on all nine pages.

Not verified: the seven non-Ruby wrappers were compiled or parsed, not executed. The Lua they call is proven identical and run-tested, so algorithm behavior is covered; what is untested per language is argument marshalling and return unpacking.

Also brings redis-py into line with its siblings

The redis-py page was missing two sections that all eight other client pages have: ## Installation and ### Script caching with EVALSHA. Both are now added in the sibling position, since this PR is effectively a general pass over the rate limiter use case.

The EVALSHA section's claims were traced with MONITOR rather than read off the source. TokenBucket sends SCRIPT LOAD on the first allow() and EVALSHA thereafter. The non-obvious part: the four alternative-algorithm snippets call register_script() on every request, which looks wasteful but is not — redis-py derives the digest from the script text client-side, so a freshly built Script object has the same digest and EVALSHA still hits the cached script. Three calls through a per-request register_script() produced three EVALSHA commands and no extra round trip.

Installation is deliberately shorter than the ruby and rust equivalents: naming a minimum redis-py version would have meant inventing one.

🤖 Generated with Claude Code


Note

Low Risk
Documentation-only changes to Hugo markdown; no runtime or security-sensitive code paths.

Overview
Ports the Alternative rate limiting algorithms content from the redis-py guide to dotnet, go, java-jedis, java-lettuce, nodejs, php, ruby, and rust. Each page gets the same comparison table, shared prose, and four byte-identical Lua scripts with per-language EVAL wrappers (fixed window, sliding window log, sliding window counter with {key} hash tags, leaky bucket policing).

Sections are inserted immediately before ## Learn more so they stay after customization/error-handling material. Lettuce examples use sync RedisCommands only, matching how those pages present token bucket.

On redis-py, adds missing ## Installation and ### Script caching with EVALSHA (including how register_script() still hits EVALSHA), and corrects the section intro on all nine pages: only three algorithms use redis.call('TIME'); the fixed window counter relies on key TTL, not the server clock.

Reviewed by Cursor Bugbot for commit ec63c7c. Bugbot is set up for automated code reviews on this repo. Configure here.

Adds fixed window counter, sliding window log, sliding window counter and
leaky bucket to the dotnet, go, java-jedis, java-lettuce, nodejs, php, ruby
and rust rate limiter pages, following the community contribution in #3788
that added them to redis-py only. Lettuce gets sync only, matching its
existing token bucket examples, and everything is snippets — no new runnable
source files, so these four algorithms stay unbacked by demo code the way
redis-py left them.

The four Lua scripts are the algorithm; only the wrapper is per-language. So
the sections were generated from a script that extracts the Lua and the prose
once from redis-py and reuses both, rather than being written eight times by
hand. That is the only reason nine copies can be trusted to stay in step, and
it is why the identity check below is worth re-running after any edit here.

Two things that cost time and will cost it again. First, the obvious way to
extract the Lua back out for comparison — take lines from the first "local"
to the last "return {" — silently over-captures, because the wrapper code
also contains a return of a brace-delimited value. It reported four false
mismatches before the bound was changed to the host string-literal
terminator. Second, compiling the dotnet snippets reported Guid as
unresolved, which looks like a defect in the snippet but is the harness
missing ImplicitUsings; these pages already assume it, since the existing
examples call Console.WriteLine with no System import. A check project has to
match what "dotnet new console" gives the reader or it manufactures errors.

Also corrects a claim on the redis-py page before copying it eight times over:
it said all four algorithms read the server clock via TIME, but the fixed
window counter reads no clock at all and leans on the key's TTL.

Verified by compiling against jedis 7.5.3, lettuce-core 7.7.0,
StackExchange.Redis 2.9.32 and redis 0.24.1, parsing the rest, running the
four scripts and the full Ruby wrappers against a live server, and a clean
Hugo build with every comparison-table anchor resolving.

Learned: an extractor bounded by a code pattern the wrapper also contains over-captures, and a compile harness that misses the reader's project defaults invents failures
Constraint: the four Lua scripts must stay byte-identical across all nine rate-limiter pages — that is what makes the reuse safe
Directive: never hand-edit the Lua on one page; change all nine together and re-run the identity check bounded by the string-literal terminator, not by "return {"
Rejected: uuid-free sorted-set members for rust from pid plus a counter | pid collisions across hosts silently overwrite log entries and undercount, so the uuid crate is worth the dependency
Rejected: inserting the section after Response headers to mirror redis-py's offset | splits the token bucket material, because each page's Customization section refers back to the token bucket limiter
Gaps: only the Ruby wrappers were run end to end; the other seven were compiled or parsed, so per-language argument marshalling and return unpacking are unproven
Ticket: DOC-6999
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

DOC-6999

@github-actions

github-actions Bot commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

🧠 Redis Memory

Found 8 related items from repository history (3 new this commit):

Memory updated at ec63c7c

…-py page

Every other rate limiter client page explains that the limiter caches its
script and sends EVALSHA, and every one tells you how to install the client.
The redis-py page did neither, even though its own token_bucket.py has done
the SHA1-plus-script-load-plus-fallback dance since it was written. This adds
both sections in the position the eight sibling pages use.

The wire behaviour in the new section was traced with MONITOR rather than read
off the source. TokenBucket sends SCRIPT LOAD on the first allow and EVALSHA
after that, as expected. The part worth writing down is the alternative
algorithm snippets, which call register_script on every request and look
wasteful: they are not, because redis-py derives the digest from the script
text client-side, so a freshly built Script object has the same digest and
EVALSHA still hits the cached script. Three calls through a per-request
register_script produced three EVALSHA commands and no extra round trip.

Installation stays shorter than the ruby and rust equivalents on purpose,
since naming a minimum redis-py version would have meant inventing one.

Learned: redis-py computes the EVALSHA digest client-side from the script text, so re-registering a Script per call costs nothing on the wire — MONITOR-verified, not inferred
Rejected: pinning a version floor in the Installation section the way the ruby and rust pages pin theirs | the floor was never verified and a wrong minimum misleads more than an absent one
Ticket: DOC-6999
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@dwdougherty dwdougherty left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Language LGTM. I did not scrutinize the code.

@andy-stark-redis

Copy link
Copy Markdown
Contributor Author

Thanks @dwdougherty !

@andy-stark-redis
andy-stark-redis merged commit 0fa3408 into main Aug 26, 2026
93 checks passed
@andy-stark-redis
andy-stark-redis deleted the DOC-6999-rate-limiter-alt-algorithms branch August 26, 2026 13:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

clients Client library docs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants