An imperfect science, benchmarks can be useful as a rough gauge for a job queue's throughput, and River comes with a simple benchmarking utility for this purpose.
On a commodity laptop (8-core 2022 M2 MacBook Air) with 2,000 worker goroutines, River works about 46k jobs/sec.
Using River bench
River's CLI ships with a basic benchmarking command to help produce a rudimentary measure of its job throughput. It inserts synthetic no-op jobs and sends them through the queue to completion.
Benchmarking is a highly imperfect science, and throughput will depend on database size and IO, the utility's distance to the database, and the hardware it's running on. We don't recommend interpreting these measurements as gospel, or using them for direct comparisons to other systems.
Install the CLI:
go install github.com/riverqueue/river/cmd/river@latest
Update the CLI frequently
The benchmark program uses the version of River internal to the installed River CLI. Make sure you have a recent version and update frequently to pick up the latest bug fixes and optimizations.
Migrate a database to use as a benchmark target:
river migrate-up --database-url $DATABASE_URL
Benchmark only empty databases
The benchmark program will truncate and VACUUM FULL
jobs table in the target database. Only target databases where total data loss is okay.
Fixed job burn down
Burn down mode inserts a fixed number of jobs prior to starting, then works them until finishing. This isn't very realistic, but produces more consistent results because there are no concurrent job insertions for workers to compete with. It's also the way that many similar systems benchmark themselves, and may be most useful in comparisons.
Use -n
/--num-total-jobs
with the total number of jobs to work:
river bench --database-url $DATABASE_URL --num-total-jobs 1_000_000
On an 8-core 2022 M2 MacBook Air, River works about 46k jobs/sec:
bench: jobs worked [ 0 ], inserted [ 1000000 ], job/sec [ 0.0 ] [0s]
bench: jobs worked [ 82657 ], inserted [ 0 ], job/sec [ 41328.5 ] [2s]
bench: jobs worked [ 96057 ], inserted [ 0 ], job/sec [ 48028.5 ] [2s]
bench: jobs worked [ 89829 ], inserted [ 0 ], job/sec [ 44914.5 ] [2s]
bench: jobs worked [ 96847 ], inserted [ 0 ], job/sec [ 48423.5 ] [2s]
bench: jobs worked [ 96042 ], inserted [ 0 ], job/sec [ 48021.0 ] [2s]
bench: jobs worked [ 87198 ], inserted [ 0 ], job/sec [ 43599.0 ] [2s]
bench: jobs worked [ 96474 ], inserted [ 0 ], job/sec [ 48237.0 ] [2s]
bench: jobs worked [ 94126 ], inserted [ 0 ], job/sec [ 47063.0 ] [2s]
bench: jobs worked [ 85323 ], inserted [ 0 ], job/sec [ 42661.5 ] [2s]
bench: jobs worked [ 94043 ], inserted [ 0 ], job/sec [ 47021.5 ] [2s]
bench: jobs worked [ 81387 ], inserted [ 0 ], job/sec [ 40693.5 ] [2s]
bench: total jobs worked [ 1000000 ], total jobs inserted [ 1000000 ], overall job/sec [ 45753.1 ], running 21.856442959s
Continuous operation
Without any other arguments River will run in continuously:
# do benchmarking
river bench --database-url $DATABASE_URL
An initial set of jobs are inserted, and the program works jobs as quickly as it can while a background goroutine inserts enough new jobs that the benchmark never runs out. It continues indefinitely until receiving SIGTERM
(i.e. Ctrl+C
in a terminal).
Timed duration
Use the --duration
parameter to run the benchmark for a fixed amount of time before stopping and printing results. It takes Go-style durations like 1m
or 5m30s
.
river bench --database-url $DATABASE_URL --duration 1m