Performance & benchmarks
chronos-go ships a benchmark suite that runs the same workload against
chronos-go and asynq side by side, on
four scenarios: plain enqueue (client-side only), end-to-end
(produce + consume) at worker concurrency C ∈ {1, 4, 16, 64}, a chain
of sequential links, and a group fan-out/fan-in. Full methodology,
tables and caveats live in the library's
docs/BENCHMARKS.md.
Methodology, in brief
Both libraries run their default configs; only the shared knobs are set identically — concurrency, payload (100-byte JSON), task count (20,000), producers (4) — on the same machine, same local Redis, runs interleaved. Every number is the median-throughput run of 3 (Redis is flushed between runs). Producers enqueue as fast as they can while the server consumes — a saturation benchmark, so throughput is the meaningful headline number; latency percentiles under saturation include backlog wait, and approach true per-task latency only once consumption keeps up with production (high concurrency).
Measured 2026-07-12 on: Apple M4 Pro (12 cores), Redis 8.0.3, Go 1.26.4, chronos-go @ that commit, asynq v0.26.0.
The headline numbers
Quoted from docs/BENCHMARKS.md:
Enqueue throughput (pure client-side, 4 producers, no server):
| library | tasks/s |
|---|---|
| chronos-go | 25,872 |
| asynq | 27,133 |
End-to-end throughput (20k tasks, 4 producers, worker concurrency C):
| C | chronos-go | asynq | ratio |
|---|---|---|---|
| 1 | 2,605 | 3,939 | 0.66x |
| 4 | 5,370 | 5,321 | 1.01x |
| 16 | 15,257 | 6,096 | 2.50x |
| 64 | 19,859 | 4,908 | 4.05x |
chronos-go scales with concurrency (batched XREADGROUP COUNT=k plus
pipelined body loads amortize Redis round trips across free workers); asynq
peaks around C=16 and declines. At C=64, chronos-go's consumers keep up with
the producers, so tasks see p50 ≈ 1ms with no backlog; at lower
concurrency the percentiles are backlog-dominated:
| C | chronos p50 / p95 | asynq p50 / p95 |
|---|---|---|
| 1 | 3,910ms / 6,552ms | 2,874ms / 4,146ms |
| 4 | 1,675ms / 2,737ms | 2,191ms / 2,877ms |
| 16 | 268ms / 354ms | 1,286ms / 2,128ms |
| 64 | 1ms / 1ms | 2,481ms / 3,234ms |
Honest note from the source: at C=1 asynq is ~1.5x faster — with a batch of one, chronos-go still pays 3 round trips per task (read, load body, mark active) where asynq uses a single script. Low-concurrency single-worker deployments are not chronos-go's sweet spot; C≥4 is.
Workflows (no asynq equivalent), C=16, 20k tasks total:
| scenario | tasks/s | notes |
|---|---|---|
| chain (2,000 × 10 links) | 13,489 | whole-chain p50 1.1s under saturation (all 2,000 chains progress in waves) |
| group (2,000 × 10 members) | 6,664 | callback fires p50 3ms after the last member |
For the newest numbers and the full write-up — including what the benchmark
suite found and fixed in earlier runs — see
docs/BENCHMARKS.md
on GitHub; results here are a snapshot and can drift as both libraries
evolve.
Recipe: reproducing the numbers
make bench (from the library repo) runs the full matrix — enqueue, then
end-to-end at each concurrency, then chain and group — against a local
Redis:
make bench
It flushes local Redis DB 15 between runs, so point it at a Redis you don't mind clearing, and don't run it against a database holding real data.
Gotcha: relative comparisons, not absolute guarantees
These numbers come from one machine with local (non-Docker) Redis and
no-op handlers that isolate library overhead — real handlers doing real
work will dominate these costs long before the queue does. Treat the
figures as relative comparisons against asynq under identical
conditions, not as a throughput guarantee for your deployment; re-run
make bench on your own hardware and Redis topology before sizing anything
on these numbers.