This folder contains a runnable benchmark harness for guardian-server.
Scope:
- Compare Filesystem vs Postgres backend behavior.
- Measure scaling with users/accounts/signers/operations.
- Validate HTTP rate-limiting and body-size enforcement.
- Miden node already running on
localhost:57291. - Rust toolchain and
cargo. - Docker (required for Postgres benchmark path).
- Optional:
k6(HTTP checks) andjq(summary formatting).
config/common.env: shared server and workload defaults.fs.env: Filesystem backend paths.postgres.env: Postgres docker/database settings.profiles.toml: small/medium/large profile presets.
loadgen/: Rust load generator (guardian-server-bench-loadgen).k6/: HTTP checks for body-limit and rate-limit behavior.scripts/: orchestration and metrics scripts.sql/: Postgres stats queries.reports/: curated benchmark reports.results/: run outputs.
The harness records:
- Throughput:
ops_per_sec,success_ops_per_sec. - Latency:
p50,p95,p99,max. - Reliability: total/success/failed operation counts and sample errors.
- Server resources: sampled
%CPU,RSS,VSZ. - Postgres query stats (Postgres runs only).
pg_stat_statementsis collected when available; otherwisepg_stat_databasesnapshots are still collected. - HTTP middleware behavior (k6 scripts): status distribution for 429 and 413 checks.
Load generator scenarios:
state-read: authenticatedget_stateworkload.state-write: repeatedconfigureworkload.mixed: configurable mix of reads and writes.state-sync: deterministic4 x get_state+1 x push_deltacycle.canonicalization: push one delta per seeded account, then wait for terminal status.
Each run seeds accounts before measured operations:
- Accounts are multisig+GUARDIAN accounts generated locally.
- Signer count per account is configurable.
- Seeding time is reported separately from measured run time.
Run Filesystem benchmark:
./crates/server/bench/scripts/run_fs.shRun Postgres benchmark (Postgres via the dedicated Compose file):
./crates/server/bench/scripts/run_postgres.shRun Postgres benchmark with canonicalization load:
./crates/server/bench/scripts/run_postgres_canonicalization.shRun Postgres state-sync benchmark (defaults to 1000 users, 1 push per 4 reads):
./crates/server/bench/scripts/run_postgres_state_sync.shRun Postgres Falcon vs ECDSA comparison:
./crates/server/bench/scripts/run_postgres_auth_compare.shrun_postgres_canonicalization.sh defaults to BENCH_SCENARIOS=canonicalization.
Generate a markdown report from a suite run:
./crates/server/bench/scripts/generate_report.sh <suite_index_path>Current workflow note:
- The canonical report for the current benchmark set is maintained in
crates/server/bench/reports/. generate_report.shis a helper for suite-index based runs and is optional.
Stop the Postgres container when done:
docker compose -f docker-compose.postgres.yml downRun both:
./crates/server/bench/scripts/run_matrix.shcrates/server/src/main.rs defaults to:
NetworkType::MidenDevnetSome(CanonicalizationConfig::new(10, 24))
If you want benchmark scripts to drive network/canonicalization through environment variables, change:
.network(NetworkType::MidenDevnet)
.with_canonicalization(Some(CanonicalizationConfig::new(10, 24)))to:
.network(
NetworkType::from_env("GUARDIAN_NETWORK_TYPE")
.unwrap_or_else(|error| panic!("Failed to resolve network type: {error}")),
)
.with_canonicalization({
let canonicalization_enabled = std::env::var("GUARDIAN_CANONICALIZATION_ENABLED")
.ok()
.map(|value| !matches!(value.to_ascii_lowercase().as_str(), "0" | "false" | "no" | "off"))
.unwrap_or(true);
if canonicalization_enabled {
let check_interval_seconds = std::env::var("GUARDIAN_CANONICALIZATION_CHECK_INTERVAL_SECS")
.ok()
.and_then(|value| value.parse::<u64>().ok())
.unwrap_or(10);
let max_retries = std::env::var("GUARDIAN_CANONICALIZATION_MAX_RETRIES")
.ok()
.and_then(|value| value.parse::<u32>().ok())
.unwrap_or(24);
Some(CanonicalizationConfig::new(
check_interval_seconds,
max_retries,
))
} else {
None
}
})This keeps normal server defaults unchanged while allowing benchmark runs to tune those values.
Main knobs are in config/common.env:
BENCH_USERSBENCH_ACCOUNTSBENCH_SIGNERS_PER_ACCOUNTBENCH_AUTH_SCHEME(falconorecdsa)BENCH_TRANSPORT(grpcorhttp)BENCH_OPS_PER_USERBENCH_MIXED_WRITE_PERCENTBENCH_STATE_SYNC_READS_PER_PUSHGUARDIAN_SERVER_START_TIMEOUT_SECSBENCH_SKIP_PREBUILDBENCH_SERVER_LOG_LEVELBENCH_SCENARIOS(comma-separated, optional override)BENCH_ENABLE_CANONICALIZATIONBENCH_CANONICALIZATION_POLL_INTERVAL_MSBENCH_CANONICALIZATION_TIMEOUT_SECS
Server knobs used for all benchmark runs:
GUARDIAN_NETWORK_TYPE=MidenLocalGUARDIAN_RATE_BURST_PER_SECGUARDIAN_RATE_PER_MINGUARDIAN_MAX_REQUEST_BYTESGUARDIAN_CANONICALIZATION_ENABLEDGUARDIAN_CANONICALIZATION_CHECK_INTERVAL_SECSGUARDIAN_CANONICALIZATION_MAX_RETRIES
Note: GUARDIAN_NETWORK_TYPE and canonicalization env knobs take effect only when the runtime code switch above is enabled.
Postgres-specific values are in config/postgres.env.
On macOS, set GUARDIAN_PQ_LIB_DIR if libpq is not on the default linker path.
Each run creates a timestamped folder in results/, containing:
server.logserver_metrics.csvloadgen_state-read.jsonloadgen_state-write.jsonloadgen_mixed.json- optional
loadgen_state-sync.json - optional
loadgen_canonicalization.json - optional
k6_*.log - optional
pg_metrics.txt summary.txt
- Compare Filesystem and Postgres runs with the same values from
common.env. - Use at least 3 repetitions per profile and compare median values.
- Keep the node, machine, and background load consistent between runs.
- Treat 429/413 checks as correctness checks, not throughput tests.