Skip to content

Benchmarking

This page shows how to measure a server’s throughput and latency with ricochet-bench, an Apache Bench-style tool that drives every protocol the server serves.

ricochet-bench is not in the release assets. Build it from the source tree with Go 1.25.7 or later:

Terminal window
git clone --branch v1.1.0 https://github.com/stephanfeb/go-ricochet.git
cd go-ricochet
go build -o ricochet-bench ./cmd/ricochet-bench
ricochet-bench [flags] <server-multiaddr> <server-peer-id>

The multiaddr is the server’s address without the /p2p/ part; the peer ID comes separately. Both are in the server’s startup output. See First run & your peer ID.

Flag Default Meaning
-n 1000 Total requests to measure
-c 10 Concurrent workers
-protocol msa Scenario to run (see below)
-payload-size 1024 Payload size in bytes
-duration none Run for a duration instead of a fixed count, for example 30s or 1m
-warmup 10 Warmup requests before measuring
-batch-size 100 Documents or messages per request, for sda-batch and msa-batch
-docs 500 Vault size for the sync-* scenarios
-v off Show libp2p and UDX diagnostic logs

If -c is larger than -n and no -duration is given, -c is lowered to -n.

-protocol Operation Measures
msa SendMessage Message submission
maa RetrieveMessages Message retrieval; seeds a mailbox first
sda PutDocument + GetDocument Document round trip
sfa AppendFeedEntry + GetFeedEntry Feed append and read; creates feeds first
sca PutCollectionItem + QueryCollection Collection write and query; creates collections first
mma CreateMailbox + DeleteMailbox Mailbox admin round trip
mixed Random mix of the six above Combined workload
sda-batch PutDocuments Batched document writes, -batch-size per request
msa-batch SendMessages Batched submission, -batch-size per request
sync-cold PutDocument × -docs First upload of a vault, every document new
sync-warm PutDocument × -docs Re-upload after edits, every document replaced
sync-noop ListDocuments + compare Nothing changed: list and compare ETags, write nothing

In the sync-* scenarios one “request” is a whole vault pass. Their defaults change accordingly: unless you set them, -n becomes 10, -warmup 1 and -c 1.

Terminal window
# Message submission
ricochet-bench -n 1000 -c 10 -protocol msa \
/ip4/127.0.0.1/udp/55223/udx 12D3KooW...
# Documents with 4 KB payloads
ricochet-bench -n 5000 -c 20 -protocol sda -payload-size 4096 \
/ip4/127.0.0.1/udp/55223/udx 12D3KooW...
# A one-minute mixed workload
ricochet-bench -duration 60s -c 50 -protocol mixed \
/ip4/127.0.0.1/udp/55223/udx 12D3KooW...
# Batched writes, 100 documents per request
ricochet-bench -n 200 -c 8 -protocol sda-batch -batch-size 100 \
/ip4/127.0.0.1/udp/55223/udx 12D3KooW...

A run against a development server on the same laptop, with PostgreSQL local:

========================================
Ricochet Bench - MSA (Message Submission)
========================================
Server: /ip4/127.0.0.1/udp/55999/udx/p2p/12D3KooW...oY6B3C4R
[1/4] Creating 10 workers... done
[2/4] Connecting to server... connected
[3/4] Warming up (10 requests)... done
[4/4] Benchmarking 1000 requests with 10 workers...
========================================
RESULTS
========================================
Concurrency Level: 10
Total Requests: 1000
Payload Size: 1024 bytes
Completed: 1000
Failed: 0
Total time: 1.297s
Requests/sec: 770.86
Latency Distribution:
min: 628.0us
p50: 2.0ms
p75: 6.8ms
p90: 25.8ms
p95: 76.6ms
p99: 188.7ms
max: 397.4ms
========================================
  • Requests/sec is completed requests over total time. For the batch and sync scenarios, a second line reports the items per second (documents/sec or messages/sec) and the total items, since one request carries many.
  • Latency distribution is per request, from the client’s side.
  • Failed requests are summarised under Errors: with a count per distinct message (the top five), followed by the error rate.

Ten workers on one machine do not saturate a server. Raise -c until requests per second stops rising; the latency percentiles at that point tell you what clients will see at capacity.

Each worker creates its own libp2p host with a fresh identity and its own connection to the server, so workers do not contend on one connection’s stream multiplexer. Scenarios that need existing data (maa, sfa, sca, mixed) create it before the warmup. The warmup runs before measuring and is not counted.

Ctrl+C stops a run; a second Ctrl+C exits immediately.

While a benchmark runs, the server’s own figures tell you where the limit is:

  • ricochet_admission_waited_total and ricochet_admission_shed_total: requests waiting for, or shed from, admission control. Shedding means the server is at its bound. See Admission & rate limits.
  • ricochet_db_pool_conns_acquired against ricochet_db_pool_conns_max: whether the database pool is the bottleneck.
  • ricochet_request_duration_seconds by protocol and operation: the server-side share of the latency the benchmark reports.

See Metrics & the ops view.

  • Every worker is a new peer. Per-peer limits (admission_control.max_in_flight_per_peer, rate limits, max_mailboxes_per_owner) apply to each worker separately.
  • Authentication blocks it. With features.enable_authentication on, the workers’ fresh identities are not on the trusted list, and the server refuses them. Benchmark a server with authentication off.
  • It writes real data. Benchmark against a test database, or clean up afterwards. Retention removes old messages in time, but documents, feeds and collections stay.
  • -c is capped by the server’s connection limit. The development preset allows only 100 connections.