Skip to content

A million ordered partitions

1,000,000 FIFO lanes in one PostgreSQL, created during the run under load.

Updated View as Markdown

One broker against one PostgreSQL held 1,000,000 ordered partitions in a single queue, none preallocated, created during the run at a thousand a second while it served 200,000 messages a second with leased pops and explicit acknowledgement. 722,265,600 messages, zero push, pop or ack errors, and a database that stopped growing. Queen 1.0.0, 32 vCPU / 62 GiB, artifacts in benchmark-queen/2026-08-11-1m-partitions/.

A partition costs a row and an index range, not a file or a process. The serve path does not care how many there are.

Configuration

The space is a million, the working set a thousand: the loader rotates which 1,000 partitions take pushes, so each sees 0.2 messages a second and each active one 200.

goload -mode openloop -> queue=partbench partitions=1000000 consumers=300 manualAck=true ackAsync=true
  offered: rate=200000 msg/s | push-batch=100 -> 2000.0 req/s across 41 pacer workers | payload=256B
  partitions: space=1000000 | active=1000/s (rotate) | 200 msg/s per active partition | space covered in 1000 s

Pop batch 1,000, wildcard pops, retention on with RETENTION_PARALLELISM=16, deduplication off, one hour of load of which 43 minutes on the completed space.

Result

From the final line of raw-final-dedupoff/g.out:

Metric Value
Messages offered 722,270,400
Messages accepted (pushed) 722,265,600
Shed by the loader’s in-flight cap 0
Messages popped 722,263,300
Messages acked 722,261,300
Push / pop / ack errors 0 / 0 / 0
Partitions created 1,000,000, at 1,000 per second
Average ack round trip 25.54 ms
Restarts, incidents 0, 0

Latency in two phases

Creating a lane costs a row and a lock, so a thousand a second is expensive.

Phase 30 s reports p50 p99 Worst p99 p99.9
While creating the million 34 40.5 ms 214.1 ms 346.1 ms 339.8 ms
On the completed space 86 27.5 ms 115.3 ms 209.9 ms 189.9 ms

The database stopped growing

Over the last nine minutes:

11:56 11:59 12:01 12:04
log_segments 3,085 MB 3,085 MB 3,085 MB 3,085 MB
log_txns 5,301 MB 5,302 MB 5,302 MB 5,302 MB
Database 9.37 GB 9.40 GB 9.43 GB 9.47 GB

The cause is in the retention log: 4,002 rows deleted per second over the last eight sweeps, against the 4,000 the offered rate creates.

What it weighs

Object Size
log_partitions, 1,000,000 rows 315 MB
log_consumers, 1,000,000 cursors 326 MB
The lanes and their cursors 641 MB
log_segments, messages inside the retention window 3.01 GB
log_txns, the transaction sidecar 5.18 GB
Database total 9.47 GB
Broker resident memory 5.03 GB

Only the first two rows scale with cardinality. The sizing rule is the last: about 5 KB of resident memory per partition.

Resources, 1 Hz host sampler

Resource Value
PostgreSQL CPU 10.15 cores of 32
Broker CPU 3.84 cores
Broker resident memory 5.03 GB, no drift
Active backends 28
PostgreSQL commits about 16,500 per second
WAL fsync 247 µs
Connection pool never above 208 of 300

The hot list holds only partitions with data, so its cost follows the work: a few thousand entries all run.

Three costs that follow the space

Invisible at two hundred partitions, decisive at a million.

Deduplication holds every hash, not the ones in the window. The cache grew 235 MB a minute, 19.6 bytes per message against a model asking 192 MB, and filled 6 GB then 16 GB: a block seals only when it fills, nearly six hours at 0.2 messages a second per partition. Saturated, pushes fall back to a full-window SQL probe, still authoritative, no duplicate admitted, but the cost lands on PostgreSQL.

Retention walks one row per partition per cycle. At RETENTION_PARALLELISM=4 the sweep grew from 108 s at 400,000 partitions to 402 s at a million; at 16 it settles near 195 s, which is what this run used.

The statistics refresh took 6.8 seconds at 825,706 partitions, harmlessly.

What it does not establish

  • Not a 24-hour run. One hour, 43 minutes of it on the completed space.
  • Deduplication was off, which is what made the run possible: this run and Throughput 24h 1M do not add up.
  • One shape. A contiguous rotating window is the friendly case for index locality.
  • A compressible payload.
  • Nothing about high availability, failure or the proxy.
Navigation

Type to search…

↑↓ navigate↵ selectEsc close