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 sPop 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.