Performance-Test-Suite für JMAP-Server (RFC 8620/8621). Misst gleichzeitige Zugriffe und Requests/s für Lese- und Schreiboperationen und vergleicht mehrere Server bzw. Storage-Backends (Filesystem/RocksDB, PostgreSQL, S3).
# Suite installieren
uv venv .venv && uv pip install -e . -p .venv/bin/python
# oder: pip install -e .
# Lokale Server starten (Docker)
cd docker && docker compose up -d && ./provision.sh && cd ..
# Konfigurierte Server anzeigen und prüfen
jmap-bench servers --check
# Testdaten einspielen und Benchmark fahren
jmap-bench run --server stalwart_rocksdb --seed --concurrency 1,10,50 --duration 15
jmap-bench run --server cyrus --seed --concurrency 1,10,50 --duration 15
# Vergleichsreport (Markdown)
jmap-bench compare results/*.json --out results/comparison.mdZugangsdaten kommen aus .env (gitignored, siehe .env.example):
# Einfachste Form (Servername: "remote")
JMAP_SERVER=mail.example.com
JMAP_ACCOUNT=user@example.com
JMAP_PASSWORD=secret
# Oder mehrere Server:
JMAP_SERVERS=fastmail,prod
JMAP_FASTMAIL_URL=https://api.fastmail.com/.well-known/jmap
JMAP_FASTMAIL_TOKEN=fmu1-...
JMAP_PROD_URL=https://mail.firma.de
JMAP_PROD_USER=bench@firma.de
JMAP_PROD_PASS=secretjmap-bench run --server remote --seed --concurrency 1,10,50Hinweis: Die Suite legt auf dem Zielkonto zwei Ordner an (PerfTest mit
Seed-Mails, PerfWrite für Schreibtests) und räumt erzeugte Mails nach jedem
Szenario wieder ab. Trotzdem: nur gegen Test-Accounts laufen lassen.
| Szenario | Art | Beschreibung |
|---|---|---|
mailbox_get |
read | Mailbox/get, alle Ordner |
email_query |
read | Email/query, neueste 20 einer Mailbox |
email_query_get |
read | Query + Email/get in einem Request (Inbox-Refresh) |
email_get_body |
read | Voller Body einer zufälligen Mail |
email_set_create |
write | Email/set create (~--body-size Bytes) |
email_import |
write | Blob-Upload + Email/import (≈ Zustellung) |
email_set_flags |
write | Keyword-Toggle (kleiner Metadaten-Write) |
mixed_80_20 |
mixed | 80 % Lesen / 20 % Schreiben |
Jedes Szenario läuft pro Concurrency-Stufe (--concurrency 1,10,50,100)
--warmup Sekunden warm und misst dann --duration Sekunden. Ausgegeben
werden op/s, Latenz p50/p95/p99/max und Fehlerzahl; Ergebnisse landen als
JSON unter results/.
docker/compose.yaml startet:
| Name | Port | Server | Backend |
|---|---|---|---|
stalwart_rocksdb |
8080 | Stalwart | RocksDB (Filesystem, embedded) |
stalwart_postgres |
8081 | Stalwart | PostgreSQL (alle Stores) |
stalwart_s3 |
8082 | Stalwart | RocksDB-Metadaten + S3-Blobs (MinIO) |
cyrus |
8090 | Cyrus IMAP 3.8 | twoskip/Filesystem |
Diese vier Namen sind in der Suite vorkonfiguriert (jmap-bench servers) und
brauchen keine .env-Einträge. Account: perf@example.com /
jmap-perf-2026-Zk8xQvNw (Stalwart v0.16 lehnt schwache Passwörter ab;
Cyrus: perf, Passwort beliebig — Auth ist im Testmodus).
Die Docker-Variante läuft auf Stalwart v0.16 (Konfiguration liegt im
Datastore, docker/provision.sh legt Domain/Account an und hebt die
Throttles per JMAP-Registry-API an). Der native Build unten nutzt
weiterhin 0.15.6 mit TOML-Konfiguration.
Cyrus kennt nur sein eigenes Filesystem-Backend; die Backend-Matrix (DB/S3) gibt es nur bei Stalwart.
Falls Docker nicht verfügbar ist (z. B. eingeschränkte CI-Umgebung):
# Cyrus (Ubuntu/Debian)
apt install cyrus-imapd cyrus-common cyrus-caldav cyrus-admin sasl2-bin
sudo servers/cyrus/start-cyrus.sh
# Stalwart aus dem Quellcode (crates.io) mit allen drei Backends
cargo install stalwart --locked --features "postgres s3" --root /opt/stalwart-bin
# Achtung: das crates.io-Paket 0.15.6 hat einen Packaging-Bug (4× E0433 im
# DAV-Modul). Workaround: in ~/.cargo/registry/src/*/stalwart-0.15.6/crates/dav/src/calendar/
# in copy_move.rs und delete.rs `crate::DavErrorCondition` → `crate::dav::DavErrorCondition`
# und `dav_proto::schema::…` → `crate::dav_proto::schema::…` ersetzen, dann erneut bauen.
# (Mit Docker-Image nicht nötig.)
sudo servers/stalwart/start-stalwart.sh rocksdb
sudo servers/stalwart/start-stalwart.sh postgres # braucht laufendes PostgreSQL (DB/User: stalwart)
sudo servers/stalwart/start-stalwart.sh s3 # braucht MinIO auf :9000 mit Bucket "stalwart"jmap-bench compare results/stalwart_rocksdb.json results/cyrus.json --out results/comparison.mderzeugt Markdown-Tabellen pro Szenario (op/s und p95 je Server und
Concurrency-Stufe) plus eine Peak-Throughput-Übersicht. Beispielreport:
results/comparison.md.
Peak-Durchsatz (op/s, bestes Ergebnis über Concurrency 1/10/50, 500 Mails à 4 KB,
vollständige Tabellen in results/comparison.md):
| Szenario | Stalwart RocksDB | Stalwart PostgreSQL | Stalwart RocksDB+S3 | Cyrus 3.8 |
|---|---|---|---|---|
| email_query | 871 | 635 | 1236 | 1156 |
| email_query_get | 717 | 420 | 811 | 270 |
| email_get_body | 1432 | 663 | 2215 | 1042 |
| email_set_create | 409 | 203 | 421 | 68 |
| email_import | 191 | 137 | 193 | 40 |
| email_set_flags | 1638 | 536 | 1669 | 147 |
| mixed_80_20 | 301 | 396 | 393 | 152 |
Kernbefunde:
- Schreiben: Stalwart (RocksDB) schafft ~6× so viele Email-Creates und ~10× so viele Flag-Updates wie Cyrus. Cyrus serialisiert Writes pro Mailbox (Lock) — mehr Concurrency erhöht dort nur die Latenz (p95 bei c=50: 869 ms create), nicht den Durchsatz.
- Lesen: Cyrus ist bei Einzel-Query/Body-Reads konkurrenzfähig (schnelle twoskip-Indizes), fällt aber beim kombinierten query+get (Inbox-Refresh) deutlich zurück (270 vs. 717–811 op/s).
- Backends (Stalwart): PostgreSQL kostet gegenüber RocksDB je nach Szenario 25–70 % Durchsatz (jeder Store-Zugriff geht übers Netz zur DB). S3-Blobs kosten beim Import Latenz (p95 44 ms vs. 9 ms bei c=1), skalieren aber gut, weil nur Blobs ausgelagert sind und Metadaten in RocksDB bleiben.
- Alle Läufe fehlerfrei; Stalwart-Defaults (4 parallele Requests, 1000 req/min Rate-Limit, Upload-Quota) wurden für die Messungen angehoben — sonst misst man nur die Drossel.
- op/s zählt logische Operationen;
email_importbesteht aus zwei HTTP-Requests (Upload + Import). - Läufe auf derselben Maschine wie der Server messen auch den Client-Overhead; für belastbare Zahlen Suite und Server trennen.
- Cyrus serialisiert Schreibzugriffe auf eine Mailbox (Mailbox-Lock) — mehr Concurrency erhöht dort nur die Latenz, nicht den Durchsatz.