Skip to content

Benchmarks

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