# Performance Baselines

Recorded 2026-03-20 on Apple M4 Max / 128 GB / macOS 26.3.1. All benchmarks run with `cargo bench` (release profile, criterion 0.5).

These baselines inform the plan 11.5 decision gate and provide regression detection.

## Codec (`benches/codec_bench.rs`)

| Benchmark | Median |
|---|---|
| encode/ping | 18 ns |
| encode/connect | 68 ns |
| encode/subscribe | 64 ns |
| encode/publish_64b | 45 ns |
| encode/publish_1kb | 61 ns |
| encode/publish_64kb | 1.18 µs |
| encode/publish_1mb | 13.2 µs |
| encode/deliver_1kb | 64 ns |
| decode/pong | 36 ns |
| decode/publish_64b | 66 ns |
| decode/publish_1kb | 81 ns |
| decode/publish_64kb | 1.16 µs |
| decode/publish_1mb | 13.2 µs |
| decode/deliver_1kb | 85 ns |
| decode/partial_buffer_none | 15 ns |
| roundtrip/connect | 139 ns |
| roundtrip/subscribe | 143 ns |
| roundtrip/publish_1kb | 121 ns |
| roundtrip/publish_1mb | 13.6 µs |
| roundtrip/deliver_1kb | 127 ns |

### Notes
- Codec is memcpy-dominated: encode/decode scale linearly with payload size (~13 ns/KB)
- 1MB payloads require a 2MB `max_message_size` codec due to framing overhead (topic string + length prefix + type byte)

## Queue Engine (`benches/queue_bench.rs`)

| Benchmark | Median |
|---|---|
| publish single topic (64B) | 323 ns |
| publish single topic (1KB) | 436 ns |
| publish single topic (64KB) | 5.2 µs |
| consume single topic (256B) | 146 ns |
| concurrent 10 producers, same topic (100 msgs each) | 568 µs total |
| concurrent 100 producers, same topic (100 msgs each) | 5.6 ms total |
| cross-topic steady 10 topics (100 msgs each) | 536 µs total |
| cross-topic steady 100 topics (100 msgs each) | 5.4 ms total |
| namespace contention steady 100 ns (50 msgs each) | 2.8 ms total |
| namespace contention steady 1000 ns (50 msgs each) | 35 ms total |
| compact 1000 offsets | 98 ns |

### Notes
- **Cross-topic contention scales linearly**: 10 topics ~536 µs, 100 topics ~5.4 ms (10x). No superlinear degradation.
- **Same-topic vs cross-topic at 100 concurrent**: ~5.6 ms vs ~5.4 ms — nearly identical, confirming the `RwLock` read path (topic lookup) is not a bottleneck.
- **Namespace contention at 1000 namespaces**: ~35 ms for 50K total publishes = ~700 ns/msg overhead. Acceptable.
- **Steady-state benchmarks** pre-create all topics so only the read lock path (not write lock for auto-creation) is measured.
- **Decision for plan 11.5**: DashMap sharding is not justified at current scale. Contention is linear, not superlinear. Revisit if we need >10K concurrent topics.

## Storage (`benches/storage_bench.rs`)

| Benchmark | Median |
|---|---|
| append single topic (64B) | 9.0 µs |
| append single topic (1KB) | 10.4 µs |
| append single topic (64KB) | 42 µs |
| append cross-topic 10 topics (100 msgs each) | 4.5 ms total |
| append cross-topic 100 topics (100 msgs each) | 55 ms total |
| replay 1K messages (256B each) | 785 µs |
| replay 10K messages (256B each) | 11 ms |
| replay 100K messages (256B each) | 61 ms |
| flush after 10 writes | 4.4 ms |

### Notes
- **Append is ~9-10 µs for small payloads**: dominated by the OS write syscall, not serialization
- **64KB append is ~42 µs**: ~4x slower than 1KB, showing some non-linear overhead at large sizes
- **Replay 100K messages in ~61 ms**: well within the 5-second target for 1M messages (extrapolating ~610 ms)
- **Flush is expensive**: 4.4 ms for a single fsync. This confirms the batch flush approach is correct — per-write fsync would be devastating for throughput.
- All storage benchmarks use `tempfile::tempdir()` for isolation.

## Fan-out / Delivery (`benches/fanout_bench.rs`)

| Benchmark | Median |
|---|---|
| individual 1 subscriber (publish+deliver) | 9.7 µs |
| individual 10 subscribers (publish+deliver) | 20 µs |
| individual 100 subscribers (publish+deliver) | 113 µs |
| consumer group 1 member (publish+deliver) | 10 µs |
| consumer group 10 members (publish+deliver 10 msgs) | 138 µs |
| publish no subscribers (manager overhead) | 444 ns |

### Notes
- **Fan-out scales sub-linearly**: 1→10 subscribers is 2x, 10→100 is 5.6x (not 10x). Delivery task parallelism helps.
- **Manager publish overhead**: 444 ns with no subscribers vs ~323 ns for the raw engine. Manager adds ~120 ns for interceptor pipeline and notify.
- **Consumer group delivery is slower per-message** than individual because of the round-robin lock and ack tracking overhead.
- Uses `MockStorage` backend — real `FsStorage` adds ~10 µs per publish (see storage baselines).

## Performance Targets vs Actuals

| Metric | Target | Actual | Status |
|---|---|---|---|
| Publish throughput (single topic) | > 100K msgs/sec | ~3.1M msgs/sec (323 ns/msg, engine only) | **Met** |
| End-to-end latency (publish → deliver) | p99 < 10ms | ~10 µs (1 subscriber) | **Met** |
| Fan-out to 1,000 subscribers | < 50ms total | ~1.1 ms (extrapolated from 113 µs / 100 subs) | **Met** |
| Concurrent namespaces | 10,000 with no degradation | Linear at 1,000 ns — extrapolates to ~350 ms for 10K | **Likely met** |
| Startup replay | 1M messages in < 5 seconds | ~610 ms (extrapolated from 61 ms / 100K) | **Met** |
| Memory per idle connection | < 16KB | Not measured (needs runtime profiling) | **Unknown** |
