Benchmarks

Measured sender throughput and send() latency, the harness that produced them, and how to reproduce them.

View as Markdown

The sender spec's Worker throughput target and Send latency budget requirements are benchmarked numbers, not aspirational ones — they're re-measured by the harness below and the spec is amended whenever reality moves.

Reference setup

  • 1 Postgres nodepostgres:16-alpine, started via testcontainers, no tuning beyond the image default.
  • 4 in-process workersInProcess({ concurrency: 4 }), the TS reference concurrency mechanism.
  • A local mock HTTP receiver — returns 200 immediately, so the numbers isolate the sender path rather than a receiver's processing time.

Machine assumptions

Numbers below were measured with Postgres running inside a Docker Desktop VM constrained to 2 vCPUs / 2.8 GB RAM (docker info), driven by a Node 20 process on the host. This is a modest, easily-reproduced machine, not dedicated benchmark hardware — the point of publishing is an honest floor, not a marketing ceiling. Re-run mise run bench on your own hardware; expect different absolute numbers and roughly the same shape.

Results

Delivery throughput

Sustained deliveries/sec, 4 in-process workers draining an outbox of 20,000 enqueued events against the mock receiver:

MetricMeasuredSpec floor
Sustained deliveries/sec~360–400 (author's machine); ~140–150 (GitHub Actions shared runner)≥ 100

The spec floor (100/sec) is set with headroom below the slowest CI runner observed so far — it is a contractual minimum, not the expected number.

send() latency

Added latency of send() itself, firing 10,000 concurrent calls against a single Postgres node through a bounded 20-connection pool:

PercentileMeasured
p50~1.2 s
p95~1.4–1.5 s
p99~1.4–1.5 s
MetricSpec ceiling
p99 added latency≤ 2000 ms

A literal burst of 10,000 simultaneous calls queues through whatever connection pool it's given — at 20 connections, most of that queue is wait time, not insert time. This is expected: the number describes queuing behavior under a full-burst load, not the cost of a single unblocked insert.

Reproduce it

mise run bench

Requires Docker (for the Postgres testcontainer). Runs both the throughput and latency scenarios and prints a report; set BENCH_JSON_OUT=<path> to also write it as JSON, or BENCH_LATENCY_SENDS / BENCH_THROUGHPUT_SENDS to change the sample sizes.

The source lives at typescript/scripts/bench.mjs. The throughput floor is additionally gated in CI via a POSTEL_PG_TESTCONTAINERS-only test at typescript/packages/storage/pg/test/throughput-benchmark.test.ts.

On this page