Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Performance

This page gives measured throughput and latency for the runtime, explains what bounds them, and shows how to reproduce the numbers on your own hardware. The figures are from two benchmark programs in the repository. They measure the runtime itself: the task handlers do no work, so a real deployment is usually limited by its workers instead.

What was measured

Each execution runs a workflow with three sequential task steps (each step’s input is the previous step’s output), so one execution is three dispatches and three results.

ProgramMeasures
bench_engine (crates/orbit-runtime/examples)The decider and the store only: one thread starts every execution, then plays the worker (poll, complete) until all have completed. No network.
bench_http (crates/orbit-worker/examples)The whole path: executions started over the HTTP API by 4 client threads, and tasks served by 4 Rust SDK workers with 8 handler threads each, over the HTTP worker protocol.

Machine: 4 vCPU (Intel Xeon, 2.1 GHz), 16 GB RAM, Linux, a container’s local disk. PostgreSQL 16 ran on the same machine with its default settings (synchronous_commit = on). ORBIT 0.4.0, release build.

Results

End to end (bench_http, 2000 executions)

StoreExecutions/sSteps/sLatency p50p90p99
In memory (no durability)1210362911 ms15 ms18 ms
SQLite file24473152 ms71 ms101 ms
PostgreSQL111332113 ms151 ms206 ms

Latency is from the execution’s creation to its completion, as recorded in its history (created_at_ms to finished_at_ms), with all 2000 executions started as fast as the clients could.

Engine only (bench_engine)

StoreExecutionsExecutions/sSteps/s
In memory5000446413392
In memory, 10 steps each5000134613464
SQLite file50004251276
PostgreSQL2000138413

Several servers (shared mode)

Throughput of shared mode with one, two and three servers, on the same machine, is in Running several servers: throughput.

What bounds the numbers

  • Durable commits. Every change to an execution is committed to the store before it takes effect: one transaction when a step is scheduled and dispatched, one when its result arrives. With SQLite that is a WAL write with synchronous = FULL (an fsync); with PostgreSQL it is one statement and a WAL flush. On a durable store, throughput is therefore set by how many commits per second the disk can sustain, not by the CPU. Faster disks, or a PostgreSQL server with battery-backed or fast NVMe storage, raise it directly.
  • One engine per server. Commits for all executions on one server are serialized. With PostgreSQL, the round-trip time to the database is part of every commit, so run the database close to the server.
  • Steps, not executions. Throughput in steps per second stays about the same as steps per execution grow (13392 against 13464 above); plan capacity in steps.
  • The number of running executions does not matter. Workers are handed the oldest scheduled step from an index, so the cost of a poll does not grow with the number of executions in flight: the engine benchmark runs at the same rate with 1000 or 5000 executions running at once.

Grouping the commits of several executions into one transaction would raise durable throughput further, and is planned work.

Reproducing

cargo run --release -p orbit-runtime --example bench_engine -- --executions 5000
cargo run --release -p orbit-runtime --example bench_engine -- --store /tmp/bench.db
cargo run --release -p orbit-runtime --example bench_engine -- --store postgres://orbit:orbit@127.0.0.1/orbit_bench
cargo run --release -p orbit-worker --example bench_http -- --executions 2000
cargo run --release -p orbit-worker --example bench_http -- --store /tmp/bench.db

bench_http can also drive a runtime you started yourself, for example a server with your production settings:

orbit server --token admin --worker-token worker --db /var/lib/orbit/bench.db
cargo run --release -p orbit-worker --example bench_http -- --server http://127.0.0.1:7233
OptionDefaultMeaning
--executions2000Executions to run.
--steps3Sequential task steps per execution.
--storememorymemory, a SQLite file path (deleted first), or a PostgreSQL URL (used as is; give it an empty database).
--servernonebench_http only: use this runtime instead of starting one in-process.
--token, --worker-tokenadmin, workerbench_http only: credentials for --server.
--workers, --concurrency4, 8bench_http only: SDK workers, and handler threads per worker.
--clients4bench_http only: threads starting executions.

Each run prints its parameters, throughput and latency. Against an orbit server on a SQLite file, on the machine above:

runtime      http://127.0.0.1:7391
executions   1000 (3 task steps each)
workers      4 x 8 handler threads, 4 client threads
starts       238/s
total        4.25s
throughput   236 executions/s, 707 steps/s
latency      p50 55ms, p90 72ms, p99 101ms (start to completion)

See also