Skip to content

Performance

Ameya Borkar edited this page May 26, 2026 · 4 revisions

Performance

All numbers are reproducible on your own hardware via npm run bench (and npm run bench:compare for the head-to-head). They are measured on the dev machine, not vendor claims, and vary run-to-run (~±10%). The full table — algorithms labelled, methodology, and every place ThrottleKit loses — lives in SCOREBOARD.md.

In-process, single hot key

Node 24, reproducible via npm run bench:

  • checkSync (GCRA): ~3.1M ops/s, ~320 ns/op, allocation-free.
  • check (async, GCRA): ~1.7M ops/s (~600 ns/op) — 2.7× faster after the sync-store fast path.
  • Token bucket checkSync: ~2.5M ops/s; fixed window checkSync: ~2.1M ops/s.
  • Redis: exactly one EVALSHA round trip per check.

Head-to-head, the honest version

npm run bench:compare — same machine, process, warmup, and iteration count; all on the allow path. The algorithm each library actually implements is labelled (a fixed-window counter and a GCRA cell are not the same guarantee even at equal ops/s).

  • Sync: ThrottleKit is one of the few JS limiters with a synchronous API at all, and it's allocation-free.
  • Redis (loopback): roughly tied with rate-limiter-flexible on throughput and p50 (both one atomic Lua round trip), with a tighter tail (p999 ~1.6× lower — cached EVALSHA + a leaner script).
  • Async in-memory: the counter-based libraries are faster (~2–5M vs ~1.3–1.7M ops/s). That is the trade: their async edge is a plain fixed-window counter, while ThrottleKit's headline path is GCRA over a timing-wheel + CLOCK store — more real work per check. All contenders are far past real-world per-process need.
  • Postgres: a single bare check trails rate-limiter-flexible's one-statement upsert (~3×, by design — PostgresStore runs one generic transaction per strategy so the same proven transform drives every backend). Under load, twoTier(leased) over Postgres amortizes one transaction per batch requests into a ~34× throughput win (12.6k vs 366 ops/s).

Why "where it loses" is in the docs

A rate limiter's value is its correctness guarantee, not its microbenchmark. ThrottleKit publishes the cases where a leaner counter beats it precisely because the trade is deliberate: a bounded-memory GCRA cell with a smooth pacing guarantee and a proven distributed bound is worth a few hundred nanoseconds against a plain counter that offers neither. The benchmark harness is in the repo so you can confirm all of this on your own hardware.

Clone this wiki locally