Benchmark Guide
Surgewave ships a multi-tier benchmark suite that covers everything from low-level buffer
primitives to full cross-platform competitor comparisons. All benchmark projects live
under benchmarks/ in the repository root.
Table of Contents
- Overview
- Quick Start
- Competitor Benchmarks
- Latency Benchmarks
- Storage Engine Benchmarks
- Transport Benchmarks
- Throughput Benchmarks
- BenchmarkDotNet Integration
- Regression Detection
- Real-World Scenarios
Overview
Benchmark Projects
| Project | Purpose |
|---|---|
Kuestenlogik.Surgewave.Benchmarks |
Main entry point — routes all commands to sub-projects |
Kuestenlogik.Surgewave.Benchmarks.Comparison |
Multi-platform competitor comparison (Kafka, Redpanda, Pulsar, NATS) |
Kuestenlogik.Surgewave.Benchmarks.Integration |
Embedded broker throughput (end-to-end, no mock) |
Kuestenlogik.Surgewave.Benchmarks.Latency |
P50/P90/P99/P99.9/P99.99 latency percentile measurement |
Kuestenlogik.Surgewave.Benchmarks.RealWorld |
Scenario-based tests: multi-broker, replication, failover |
Kuestenlogik.Surgewave.Benchmarks.Regression |
Regression detection against a stored baseline |
Kuestenlogik.Surgewave.Benchmarks.Storage |
All 7 storage engines (Memory, File, Arrow, NVMe, DuckDB, LMDB, Parquet) |
Kuestenlogik.Surgewave.Benchmarks.Streams |
Streams state stores and topology latency |
Kuestenlogik.Surgewave.Benchmarks.Transport |
SHM ring buffer, protocol serialization, transport comparison |
Kuestenlogik.Surgewave.Benchmarks.Unit |
Micro-benchmarks: serialization, SIMD, compression, buffer pools |
Kuestenlogik.Surgewave.Benchmarks.Streams |
Streams state stores and Streams latency percentiles |
What Gets Measured
- Throughput — messages per second and MB/s for produce and consume paths
- Latency percentiles — P50, P90, P99, P99.9, P99.99 for produce, consume, and end-to-end
- Storage engines — append and read performance across all 7 backends
- Protocol overhead — MQTT, WebSocket JSON, Surgewave Native binary, and Kafka wire format
- Transport primitives — lock-free SPSC ring buffer vs TCP MemoryStream
- Streams operations — state store Put/Get/Delete/Range, topology, window, join, serde
- Competitor comparison — Surgewave vs Apache Kafka, Redpanda, Apache Pulsar (KoP), NATS JetStream
Quick Start
Prerequisites
- .NET 10 SDK
- Docker Desktop (for competitor benchmarks that start real brokers)
Build
dotnet build benchmarks/Kuestenlogik.Surgewave.Benchmarks -c Release
Or build the whole solution:
dotnet build Kuestenlogik.Surgewave.slnx -c Release
Run Your First Benchmark
The fastest way to get a number — embedded broker throughput using memory storage:
dotnet run --project benchmarks/Kuestenlogik.Surgewave.Benchmarks -c Release -- embedded 100000 100 1000 memory
This produces a table showing producer and consumer msg/s for a 100-byte message workload with batch size 1000 against an in-process broker.
To see all available commands:
dotnet run --project benchmarks/Kuestenlogik.Surgewave.Benchmarks -c Release
Competitor Benchmarks
These commands benchmark one system at a time and save the result as a JSON file to
artifacts/benchmarks/results/. After running several systems, the compare command
loads all saved results and prints a unified side-by-side table.
All competitor benchmarks are run from the main benchmark entry point:
dotnet run --project benchmarks/Kuestenlogik.Surgewave.Benchmarks -c Release -- <command> [options]
benchmark-surgewave — Surgewave Baseline
Benchmarks the embedded Surgewave broker using both the native and Kafka wire protocols. No Docker required; the broker runs in-process.
# Default: 100,000 messages, 100 bytes
dotnet run --project benchmarks/Kuestenlogik.Surgewave.Benchmarks -c Release -- benchmark-surgewave
# Custom message count and size
dotnet run --project benchmarks/Kuestenlogik.Surgewave.Benchmarks -c Release -- benchmark-surgewave 500000 1024
Result saved to: artifacts/benchmarks/results/surgewave.json
benchmark-kafka — Apache Kafka
Benchmarks a running Apache Kafka broker. Start Kafka with Docker first:
docker run -d --name kafka \
-p 29092:29092 \
-e KAFKA_NODE_ID=1 \
-e KAFKA_PROCESS_ROLES=broker,controller \
-e KAFKA_LISTENERS=PLAINTEXT://:9092,CONTROLLER://:9093,EXTERNAL://:29092 \
-e KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://localhost:9092,EXTERNAL://localhost:29092 \
-e KAFKA_LISTENER_SECURITY_PROTOCOL_MAP=PLAINTEXT:PLAINTEXT,CONTROLLER:PLAINTEXT,EXTERNAL:PLAINTEXT \
-e KAFKA_CONTROLLER_QUORUM_VOTERS=1@localhost:9093 \
-e KAFKA_CONTROLLER_LISTENER_NAMES=CONTROLLER \
-e KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR=1 \
confluentinc/cp-kafka:7.6.0
Then run the benchmark:
# Against the default host localhost:29092
dotnet run --project benchmarks/Kuestenlogik.Surgewave.Benchmarks -c Release -- benchmark-kafka
# Custom count, size, and bootstrap server
dotnet run --project benchmarks/Kuestenlogik.Surgewave.Benchmarks -c Release -- benchmark-kafka 100000 100 localhost:29092
# Also benchmark Surgewave in the same run for instant comparison
dotnet run --project benchmarks/Kuestenlogik.Surgewave.Benchmarks -c Release -- benchmark-kafka 100000 100 localhost:29092 --include-surgewave
Result saved to: artifacts/benchmarks/results/kafka.json
benchmark-redpanda — Redpanda
docker run -d --name redpanda \
-p 19092:19092 \
redpandadata/redpanda:latest \
redpanda start \
--advertise-kafka-addr localhost:19092 \
--kafka-addr 0.0.0.0:19092 \
--overprovisioned \
--smp 1 \
--memory 512M \
--reserve-memory 0M \
--node-id 0 \
--check=false
# Default: localhost:19092
dotnet run --project benchmarks/Kuestenlogik.Surgewave.Benchmarks -c Release -- benchmark-redpanda
# Custom bootstrap address
dotnet run --project benchmarks/Kuestenlogik.Surgewave.Benchmarks -c Release -- benchmark-redpanda 100000 100 localhost:19092
# With Surgewave comparison
dotnet run --project benchmarks/Kuestenlogik.Surgewave.Benchmarks -c Release -- benchmark-redpanda 100000 100 localhost:19092 --include-surgewave
Result saved to: artifacts/benchmarks/results/redpanda.json
benchmark-pulsar — Apache Pulsar (via KoP)
Apache Pulsar with the Kafka-on-Pulsar (KoP) protocol handler exposes a Kafka-compatible endpoint:
docker run -d --name pulsar \
-p 6650:6650 \
-p 8080:8080 \
-p 9092:9092 \
apachepulsar/pulsar:latest \
bin/pulsar standalone
# Default: localhost:9092 (KoP endpoint)
dotnet run --project benchmarks/Kuestenlogik.Surgewave.Benchmarks -c Release -- benchmark-pulsar
# Custom address
dotnet run --project benchmarks/Kuestenlogik.Surgewave.Benchmarks -c Release -- benchmark-pulsar 100000 100 localhost:9092
# With Surgewave comparison
dotnet run --project benchmarks/Kuestenlogik.Surgewave.Benchmarks -c Release -- benchmark-pulsar 100000 100 localhost:9092 --include-surgewave
Result saved to: artifacts/benchmarks/results/pulsar.json
benchmark-nats — NATS JetStream
docker run -d --name nats \
-p 4222:4222 \
nats:latest \
-js
# Default: nats://localhost:4222
dotnet run --project benchmarks/Kuestenlogik.Surgewave.Benchmarks -c Release -- benchmark-nats
# Custom NATS URL
dotnet run --project benchmarks/Kuestenlogik.Surgewave.Benchmarks -c Release -- benchmark-nats 100000 100 nats://localhost:4222
# With Surgewave comparison
dotnet run --project benchmarks/Kuestenlogik.Surgewave.Benchmarks -c Release -- benchmark-nats 100000 100 nats://localhost:4222 --include-surgewave
Result saved to: artifacts/benchmarks/results/nats.json
--include-surgewave Flag
Any competitor command accepts --include-surgewave as the fourth positional argument.
When present, Surgewave is benchmarked in the same run so results are immediately comparable
without running compare separately.
dotnet run --project benchmarks/Kuestenlogik.Surgewave.Benchmarks -c Release -- benchmark-kafka 100000 100 localhost:29092 --include-surgewave
compare — Cross-Platform Comparison Table
After running two or more competitor commands, generate a unified comparison table from the saved JSON files:
dotnet run --project benchmarks/Kuestenlogik.Surgewave.Benchmarks -c Release -- compare
The command reads every *.json file from artifacts/benchmarks/results/ and prints a
side-by-side table of throughput, latency, and relative performance ratios.
Results Directory
All JSON result files are written to:
artifacts/benchmarks/results/
surgewave.json
kafka.json
redpanda.json
pulsar.json
nats.json
Latency Benchmarks
latency — Surgewave Protocol Comparison (P50/P90/P99/P99.9/P99.99)
Measures produce, consume, and end-to-end latency percentiles for Surgewave Native protocol and the Confluent Kafka client, both against the same embedded Surgewave broker.
# Signature: latency [msgCount] [msgSize] [storage]
# Defaults: 10000 100 memory
# Quick run with defaults
dotnet run --project benchmarks/Kuestenlogik.Surgewave.Benchmarks -c Release -- latency
# 50,000 messages, 1 KB payload, file storage
dotnet run --project benchmarks/Kuestenlogik.Surgewave.Benchmarks -c Release -- latency 50000 1024 file
# 10,000 messages, 100 bytes, Arrow columnar storage
dotnet run --project benchmarks/Kuestenlogik.Surgewave.Benchmarks -c Release -- latency 10000 100 arrow
Storage options: memory (default), file, arrow
Output includes a comparison table:
| Operation | P50 | P99 | P99.9 | P99.99 | Native vs Kafka |
|---|---|---|---|---|---|
| Produce Native | 45 | 180 | 420 | 850 | — |
| Produce Kafka | 15600 | 45000 | 58000 | 72000 | 345.6x slower |
latency-compare — Surgewave vs Real Kafka Broker
Runs the same latency measurement but compares against a live Apache Kafka broker rather than against Surgewave's Kafka protocol support. Requires a running Kafka instance.
# Signature: latency-compare [msgCount] [msgSize] [kafkaBootstrap]
# Defaults: 5000 100 localhost:29092
dotnet run --project benchmarks/Kuestenlogik.Surgewave.Benchmarks -c Release -- latency-compare
# Custom
dotnet run --project benchmarks/Kuestenlogik.Surgewave.Benchmarks -c Release -- latency-compare 10000 512 localhost:29092
streams-latency — Streams Operations Latency
Measures P50/P90/P99/P99.9/P99.99 for Streams state store operations, topology processing, join operations, window operations, and serde.
# Signature: streams-latency [operation] [recordCount] [storeType]
# Defaults: all 10000 all
# All operations, all store types
dotnet run --project benchmarks/Kuestenlogik.Surgewave.Benchmarks -c Release -- streams-latency
# State store only, RocksDB
dotnet run --project benchmarks/Kuestenlogik.Surgewave.Benchmarks -c Release -- streams-latency statestore 10000 rocksdb
# Topology processing, 50K records
dotnet run --project benchmarks/Kuestenlogik.Surgewave.Benchmarks -c Release -- streams-latency topology 50000
# Window operations, SQLite
dotnet run --project benchmarks/Kuestenlogik.Surgewave.Benchmarks -c Release -- streams-latency window 10000 sqlite
Operations: all, statestore, topology, join, window, serde
Store types: all, inmemory, rocksdb, sqlite, mappedfile, caching
Storage Engine Benchmarks
storage — All 7 Storage Engines
Runs BenchmarkDotNet microbenchmarks for append (small/medium/large batch) and read operations across all available segment storage backends.
dotnet run --project benchmarks/Kuestenlogik.Surgewave.Benchmarks -c Release -- storage
Or run the storage project standalone for a quick non-BDN test:
dotnet run --project benchmarks/Kuestenlogik.Surgewave.Benchmarks.Storage -c Release -- quick
Engines benchmarked:
| Engine | Class | Notes |
|---|---|---|
Memory |
MemoryLogSegment |
Fastest; no persistence |
File |
FileStorageEngine |
Default persistence backend |
Arrow |
ArrowLogSegment |
Columnar, analytics-friendly |
NvmeDirect |
NvmeDirectStorageEngine |
Direct I/O, bypasses page cache |
DuckDB |
DuckDbStorageEngine |
SQL-queryable storage |
LMDB |
LmdbStorageEngine |
Memory-mapped B-tree |
Parquet |
ParquetStorageEngine |
Columnar, offline analytics |
Benchmark operations:
Append_SmallBatch— 100 bytes, 1 record (baseline)Append_MediumBatch— 1 KB, 10 recordsAppend_LargeBatch— 10 KB, 100 recordsRead_Single— read up to 2 KBRead_Multiple— read up to 64 KBRead_Large— read up to 1 MB
Filter to a specific engine:
dotnet run --project benchmarks/Kuestenlogik.Surgewave.Benchmarks -c Release -- --filter "*StorageBenchmarks*Arrow*"
statestore — Streams State Stores
BenchmarkDotNet microbenchmarks for Streams state store backends: Put (single and batch-100), Get (existing and missing), Delete, and full enumeration.
dotnet run --project benchmarks/Kuestenlogik.Surgewave.Benchmarks -c Release -- statestore
Backends: InMemory, RocksDb, Sqlite, MappedFile, Caching
Preload sizes: 1,000 and 10,000 entries (both run for each backend).
Filter to one backend:
dotnet run --project benchmarks/Kuestenlogik.Surgewave.Benchmarks -c Release -- --filter "*StateStoreBenchmarks*RocksDb*"
Transport Benchmarks
sharedmemory / ringbuffer — SHM Ring Buffer
BenchmarkDotNet microbenchmarks for the lock-free SPSC ring buffer that backs the shared-memory transport. Runs entirely in-process; no broker required.
dotnet run --project benchmarks/Kuestenlogik.Surgewave.Benchmarks -c Release -- sharedmemory
# Equivalent aliases
dotnet run --project benchmarks/Kuestenlogik.Surgewave.Benchmarks -c Release -- ringbuffer
dotnet run --project benchmarks/Kuestenlogik.Surgewave.Benchmarks -c Release -- shared-memory
Operations measured:
| Benchmark | Description |
|---|---|
SpscRingBuffer_Write |
Single claim+commit write (baseline) |
SpscRingBuffer_ReadWrite |
Write then immediately read (round-trip) |
SpscRingBuffer_BatchWrite |
100 writes in a tight loop |
Message sizes: 64 B, 256 B, 1 KB, 4 KB (parameterized)
protocols — Protocol Serialization Comparison
Compares MQTT topic matching, WebSocket JSON serialization, Surgewave Native binary encoding, and Kafka wire protocol parsing — all without a running broker.
dotnet run --project benchmarks/Kuestenlogik.Surgewave.Benchmarks -c Release -- protocols
Benchmark categories:
| Category | What Is Measured |
|---|---|
TopicMapping |
MQTT topic filter matching (wildcard and exact), MQTT→Surgewave topic translation |
Serialization |
Encode an outbound message for WebSocket JSON and Surgewave Native |
Deserialization |
Decode WebSocket JSON, Surgewave Native binary, and Kafka wire requests |
MessageSize |
Total frame size (framing overhead) for each protocol at each payload size |
Payload sizes: 64 B, 256 B, 1 KB, 4 KB (parameterized)
transport-compare — SHM Ring Buffer vs TCP
Compares the raw buffering cost of the shared-memory ring buffer against a MemoryStream
write (TCP send-buffer proxy) for Write, ReadWrite, and BatchWrite patterns.
dotnet run --project benchmarks/Kuestenlogik.Surgewave.Benchmarks -c Release -- transport-compare
Message sizes: 64 B, 256 B, 1 KB, 4 KB (parameterized)
Throughput Benchmarks
embedded — Embedded Broker Throughput
Measures producer and consumer throughput using an in-process Surgewave broker. No Docker or external processes required.
# Signature: embedded [msgCount] [msgSize] [batchSize] [storageMode]
# Defaults: 100000 100 1000 both
# Defaults: 100K messages, 100 bytes, batch 1000, File + Memory comparison
dotnet run --project benchmarks/Kuestenlogik.Surgewave.Benchmarks -c Release -- embedded
# 1 million messages, 1 KB payload, batch 5000, memory-only
dotnet run --project benchmarks/Kuestenlogik.Surgewave.Benchmarks -c Release -- embedded 1000000 1024 5000 memory
# All storage modes
dotnet run --project benchmarks/Kuestenlogik.Surgewave.Benchmarks -c Release -- embedded 500000 100 1000 all
Storage modes: file, memory, arrow, arrownocompress, both (file + memory), all
Output includes a summary table comparing msg/s, MB/s, and startup time for each mode.
Multi-Platform Scenario Comparison (Comparison Project)
The comparison project (Kuestenlogik.Surgewave.Benchmarks.Comparison) provides richer scenario-based
commands. Run it directly for access to platform presets and scenario selection:
cd benchmarks/Kuestenlogik.Surgewave.Benchmarks.Comparison
dotnet run -c Release -- [scenario] [options]
Scenarios:
| Scenario | Description |
|---|---|
throughput |
Max throughput (msg/sec, MB/sec) |
latency |
Latency comparison (P50/P90/P99) |
batch-size |
Batch size impact on throughput |
message-size |
Message size impact on throughput |
multi-producer |
Producer scaling (1, 3, 5, 10 concurrent) |
all |
All scenarios (default) |
Platform presets (--platforms):
| Preset | Platforms Included |
|---|---|
all |
All 8 platform configurations |
containers |
Container variants (Surgewave Container + Kafka Container + Redpanda Container) |
fair |
Fair comparison — all container variants using Kafka wire protocol |
embedded |
Surgewave Embedded Native + Surgewave Embedded Kafka |
standalone |
Surgewave Standalone Native + Surgewave Standalone Kafka |
surgewave |
All 6 Surgewave variants |
Named platforms:
| Name | Description |
|---|---|
embedded-native |
Surgewave Embedded + Native Client |
embedded-kafka |
Surgewave Embedded + Kafka Client |
standalone-native |
Surgewave Standalone + Native Client |
standalone-kafka |
Surgewave Standalone + Kafka Client |
container-native |
Surgewave Container + Native Client |
container-kafka |
Surgewave Container + Kafka Client |
kafka |
Apache Kafka Container |
redpanda |
Redpanda Container |
Examples:
# Throughput: 1M messages
dotnet run -c Release -- throughput --messages 1000000
# Latency, embedded Native vs Kafka vs Redpanda
dotnet run -c Release -- latency --platforms embedded-native,kafka,redpanda
# Full comparison, all platforms, save results
dotnet run -c Release -- all --platforms all --output results.json --report report.md
# Fair comparison across all containers
dotnet run -c Release -- throughput --platforms fair
# Surgewave-only, skip all non-Surgewave platforms
dotnet run -c Release -- multi-producer --surgewave-only
# Custom Kafka image
dotnet run -c Release -- all --platforms containers --kafka-image confluentinc/cp-kafka:7.7.0
All options:
| Option | Default | Description |
|---|---|---|
--messages N |
100000 | Messages to produce |
--message-size N |
100 | Message size in bytes |
--batch-size N |
1000 | Producer batch size |
--platforms SPEC |
embedded+kafka | Platform selection (name, preset, or comma list) |
--kafka HOST:PORT |
localhost:29092 | External Kafka bootstrap server |
--surgewave-standalone ADDR |
localhost:9092 | Surgewave standalone address |
--surgewave-image IMAGE |
surgewave:latest | Surgewave Docker image |
--kafka-image IMAGE |
confluentinc/cp-kafka:7.6.0 | Kafka Docker image |
--redpanda-image IMAGE |
redpandadata/redpanda:latest | Redpanda Docker image |
--output FILE |
— | Save JSON results |
--report FILE |
— | Generate markdown report |
--surgewave-only |
false | Skip all non-Surgewave platforms |
BenchmarkDotNet Integration
All BenchmarkDotNet microbenchmarks are accessible through native BDN arguments passed directly to the main benchmark entry point.
List Available Benchmarks
dotnet run --project benchmarks/Kuestenlogik.Surgewave.Benchmarks -c Release -- --list tree
Filter by Name
dotnet run --project benchmarks/Kuestenlogik.Surgewave.Benchmarks -c Release -- --filter "*Serialization*"
dotnet run --project benchmarks/Kuestenlogik.Surgewave.Benchmarks -c Release -- --filter "*StorageBenchmarks*"
dotnet run --project benchmarks/Kuestenlogik.Surgewave.Benchmarks -c Release -- --filter "*SpscRingBuffer*"
Filter by Category
# Run all benchmarks in a single category
dotnet run --project benchmarks/Kuestenlogik.Surgewave.Benchmarks -c Release -- --allCategories=Storage
dotnet run --project benchmarks/Kuestenlogik.Surgewave.Benchmarks -c Release -- --allCategories=Latency
# Run benchmarks matching any listed category
dotnet run --project benchmarks/Kuestenlogik.Surgewave.Benchmarks -c Release -- --anyCategories=P99,Latency
Category Shortcut Commands
The main entry point provides shorthand commands that expand to --allCategories=<X>:
| Command | Equivalent Category |
|---|---|
unit |
Unit |
storage |
Storage |
simd |
Simd |
compression |
Compression |
serialization |
Serialization |
protocol |
Protocol |
streams |
Streams |
sharedmemory |
SharedMemory |
protocols |
ProtocolComparison |
transport-compare |
TransportComparison |
statestore |
StateStore |
Available BDN Categories
| Group | Categories |
|---|---|
| Primary | Unit, Storage, Integration, Comparison, Latency, Streams, SharedMemory |
| Transport | ProtocolComparison, TransportComparison |
| Features | Serialization, Compression, Protocol, Simd, BufferPool, Throughput |
| Streams | StateStore, Topology, Window, Join, Serde |
| Latency | P50, P90, P99, P99.9, P99.99, EndToEnd |
| Systems | Kafka, Redpanda, Native, Embedded |
JSON and Markdown Export
BenchmarkDotNet exports results automatically. To also get a markdown report:
dotnet run --project benchmarks/Kuestenlogik.Surgewave.Benchmarks -c Release -- \
--allCategories=Storage \
--exporters=json,markdown
BDN writes results to BenchmarkDotNet.Artifacts/results/ by default. Surgewave overrides
the artifacts path to artifacts/benchmarks/ for consistency.
Custom Category Filter Shorthand
# Long form
dotnet run --project benchmarks/Kuestenlogik.Surgewave.Benchmarks -c Release -- --category=P99
# Short form
dotnet run --project benchmarks/Kuestenlogik.Surgewave.Benchmarks -c Release -- -c=P99
Regression Detection
The Kuestenlogik.Surgewave.Benchmarks.Regression project detects performance regressions by comparing
BenchmarkDotNet JSON reports against a stored baseline. It is designed for CI integration.
Commands
compare — Detect Regressions
dotnet run --project benchmarks/Kuestenlogik.Surgewave.Benchmarks.Regression -c Release -- \
compare results.json benchmarks/benchmark-baseline.json \
-o regression-report.md \
--config benchmarks/regression-config.json \
--fail-on-regression
| Argument | Description |
|---|---|
<results-json> |
BDN JSON report from the current run |
<baseline-json> |
Baseline to compare against |
-o <file> |
Write markdown report to file (optional) |
--config <file> |
Custom thresholds config (optional) |
--fail-on-regression |
Exit code 1 if any regression detected |
Prints a markdown report to stdout. Exits 0 if no regressions; exits 1 with
--fail-on-regression when regressions are found.
update-baseline — Merge Results into Baseline
dotnet run --project benchmarks/Kuestenlogik.Surgewave.Benchmarks.Regression -c Release -- \
update-baseline results.json benchmarks/benchmark-baseline.json
Merges new results into the baseline file. New benchmarks are added; existing ones are updated. Use this after verifying a performance improvement is intentional.
report — Generate Markdown Report
dotnet run --project benchmarks/Kuestenlogik.Surgewave.Benchmarks.Regression -c Release -- \
report results.json benchmarks/benchmark-baseline.json \
-o reports/regression-2026-03-21.md \
--config benchmarks/regression-config.json
Always writes the report to the -o file. Does not print to stdout.
Regression Thresholds
Default thresholds are stored in benchmarks/regression-config.json:
{
"latencyThresholdPercent": 15.0,
"throughputThresholdPercent": 10.0,
"allocationThresholdPercent": 20.0,
"excludedBenchmarks": [],
"categoryOverrides": {}
}
A regression is flagged when a benchmark worsens beyond the relevant threshold.
CI Integration
Example GitHub Actions step:
- name: Run benchmarks
run: |
dotnet run --project benchmarks/Kuestenlogik.Surgewave.Benchmarks -c Release -- \
--allCategories=Unit --exporters=json
- name: Check for regressions
run: |
dotnet run --project benchmarks/Kuestenlogik.Surgewave.Benchmarks.Regression -c Release -- \
compare \
"BenchmarkDotNet.Artifacts/results/report.json" \
"benchmarks/benchmark-baseline.json" \
-o "regression-report.md" \
--fail-on-regression
The baseline file benchmarks/benchmark-baseline.json is committed to source control and
updated manually via update-baseline after deliberate performance changes.
Real-World Scenarios
The Kuestenlogik.Surgewave.Benchmarks.RealWorld project runs scenario-based tests against a
multi-broker cluster under realistic conditions: disk I/O, replication, sustained load,
and failure recovery.
Unlike BenchmarkDotNet microbenchmarks, these are long-running scenario tests (up to
--duration seconds each) with configurable cluster topology.
dotnet run --project benchmarks/Kuestenlogik.Surgewave.Benchmarks.RealWorld -c Release -- [scenario] [options]
Scenarios
| Scenario | Description |
|---|---|
throughput |
Max throughput measurement (msg/s and MB/s) |
latency |
End-to-end latency percentiles (P50/P90/P99/P99.9/P99.99) |
scaling |
Linear scaling verification (1, 2, 3, 5 brokers) |
replication |
Replication overhead (RF=1 vs RF=3) |
consumer |
Consumer performance scaling (1, 3, 5 consumers) |
failover |
Failover impact measurement (crash + recovery time) |
storage |
Storage engine comparison (Memory/File/Arrow) |
all |
All scenarios in sequence (default) |
Options
| Option | Default | Description |
|---|---|---|
--brokers N |
3 | Number of brokers in the cluster |
--messages N |
100000 | Messages to produce |
--message-size N |
100 | Message size in bytes |
--duration N |
60 | Max duration per scenario in seconds |
--batch-size N |
1000 | Producer batch size |
--output FILE |
— | Save results to JSON |
--compare FILE |
— | Compare against a baseline JSON |
--report FILE |
— | Generate markdown report |
Examples
# Throughput: 1M messages, save results
dotnet run --project benchmarks/Kuestenlogik.Surgewave.Benchmarks.RealWorld -c Release -- \
throughput --messages 1000000
# Latency: single broker, 10K messages
dotnet run --project benchmarks/Kuestenlogik.Surgewave.Benchmarks.RealWorld -c Release -- \
latency --brokers 1 --messages 10000
# Scaling: verify linear scale-out
dotnet run --project benchmarks/Kuestenlogik.Surgewave.Benchmarks.RealWorld -c Release -- \
scaling --messages 500000
# Replication overhead
dotnet run --project benchmarks/Kuestenlogik.Surgewave.Benchmarks.RealWorld -c Release -- \
replication --messages 200000
# Storage engine comparison, 1 KB messages
dotnet run --project benchmarks/Kuestenlogik.Surgewave.Benchmarks.RealWorld -c Release -- \
storage --message-size 1024
# All scenarios, 5-minute cap per scenario, save full report
dotnet run --project benchmarks/Kuestenlogik.Surgewave.Benchmarks.RealWorld -c Release -- \
all \
--messages 500000 \
--duration 300 \
--output results/realworld.json \
--report results/realworld-report.md
# Compare current run against committed baseline
dotnet run --project benchmarks/Kuestenlogik.Surgewave.Benchmarks.RealWorld -c Release -- \
throughput --compare benchmarks/baselines/realworld-baseline.json
Multi-Broker Cluster Setup
The real-world benchmark suite starts --brokers Surgewave broker instances in-process
and wires them together as a cluster. No external infrastructure is required. The
cluster is torn down automatically when the benchmark finishes.
For the failover scenario, one broker is intentionally crashed mid-run and the suite
measures recovery time and message loss (expected: zero with RF >= 2).
Reference: Baseline Performance
The committed baseline (benchmarks/benchmark-baseline.json) records expected numbers
for the current hardware. Numbers below are from an Intel Core i7-1360P (13th Gen,
12 cores/16 threads), 16 GB RAM, Windows 11, .NET 10.
Embedded Throughput (1M messages, 100 bytes, batch 1000)
| Protocol | Producer | Consumer |
|---|---|---|
| Surgewave Native | 1,500,000 msg/s (143 MB/s) | 2,500,000 msg/s (238 MB/s) |
| Kafka Protocol | 1,589,825 msg/s (152 MB/s) | 2,202,643 msg/s (210 MB/s) |
Latency (Memory storage, Native protocol, 100 bytes)
Note: Latency percentiles below are planned targets, not measured values. Run the regression suite for current numbers (see Regression Suite).
| Percentile | Produce | Consume | End-to-End |
|---|---|---|---|
| P50 | (target: sub-millisecond) | — | (target: sub-millisecond) |
| P99 | (target: low single-digit ms) | — | ~15 ms (measured) |