Skip to content

Performance

Test setup

ComponentConfiguration
Targethttp://juice-shop:3000
Test packjuice-shop-detect (1 HTTP GET + 2 asserts + 1 finding)
Scopelocal-juice-shop, concurrency 25 per worker
Worker envBENCHMARK_MODE=false, HELLION_VERBOSE_EVENTS=false, STATE_BATCH_SIZE=64, PG_POOL_SIZE=12
Postgresmax_connections=300, shared_buffers=256MB
Benchmark scripttests/bench.sh
Poll endpointGET /runs/stats (aggregated counts, not full run list)

Each run creates one Postgres row and a small set of high-signal events (finding.created, target.completed, errors). State updates use batched UNNEST inserts and split patch/event flushes.

Running benchmarks

bash
# Start the stack
docker compose up -d --build

# Default: 1000 runs
./tests/bench.sh

# Custom run count and timeout
RUNS=10000 TIMEOUT_SEC=600 ./tests/bench.sh

# From inside the compose network
API=http://control-api:8080 RUNS=1000 ./tests/bench.sh

The script reports three metrics:

MetricMeasures
Queue timeBulk create via POST /runs/bulk until all run records exist
Worker timeFirst poll after queue until completed == RUNS
Total timeQueue + worker (end-to-end)

Reference results

Measured on Windows 11 host (i7-12700F), Docker Desktop and a single worker-rust replica.

Summary

RunsQueue (ms)Worker (ms)Total (ms)Queue rateWorker rateTotal rate
100762924231,316/s342/s236/s
1,0007386599413,699/s1,156/s1,006/s
10,0001683,8924,12259,524/s2,569/s2,426/s
100,0001,02043,78244,87098,039/s2,284/s2,229/s

Observations

Queue path scales well. Bulk insert via pgx.Batch and NATS publish keeps queue throughput above 60k runs/sec even at 10k runs.

Worker throughput plateaus around 2.2–2.6k runs/sec with one worker at concurrency 25. The bottleneck shifts from HTTP (Juice Shop) to Postgres event writes as run volume grows.

Tail latency grows at 100k. Progress is steady until ~70k completed, then completion rate slows as the target and database contend.

Bottlenecks

StageTypical limitNotes
Bulk queue60k–100k runs/secPostgres batch insert + NATS flush
NATS deliveryVery highQueue group hellion-http-workers
HTTP requestsTarget-dependentJuice Shop dies under heavy parallel load
Postgres writes~2–3k runs/secEvent batching + patch-only flushes for status

Tuning

Worker throughput

VariableDefaultEffect
worker_concurrency (scope YAML)25Max parallel jobs per worker process
BENCHMARK_MODEfalsetrue skips event inserts; fastest mode
HELLION_VERBOSE_EVENTSfalsetrue stores every event; significantly slower
STATE_BATCH_SIZE64Events flushed in bulk when buffer fills
PG_POOL_SIZE12Connections per worker; keep replicas × PG_POOL_SIZE < max_connections

Scale workers horizontally:

bash
docker compose up -d --scale worker-rust=4

With 4 replicas at concurrency 25, theoretical parallelism is 100 in-flight jobs. Size Postgres accordingly:

max_connections >= (worker_replicas × PG_POOL_SIZE) + headroom for control-api

Postgres

yaml
postgres:
  command: ["postgres", "-c", "max_connections=300", "-c", "shared_buffers=256MB"]

Increase max_connections and shared_buffers when scaling workers or raising concurrency.

Modes

ModeEvents storedUse case
BENCHMARK_MODE=trueNoPeak throughput, load testing
HELLION_VERBOSE_EVENTS=falseHigh-signal onlyProduction-like benchmarks
HELLION_VERBOSE_EVENTS=trueAll eventsDebugging, full audit trail

Example compose override for peak benchmark throughput:

yaml
worker-rust:
  environment:
    BENCHMARK_MODE: "true"
    HELLION_VERBOSE_EVENTS: "false"

Example for full event capture (slower):

yaml
worker-rust:
  environment:
    BENCHMARK_MODE: "false"
    HELLION_VERBOSE_EVENTS: "true"
    STATE_BATCH_SIZE: "128"

Monitoring during a run

Poll aggregated status (cheap):

bash
curl http://localhost:8080/runs/stats

Example response:

json
{"queued":0,"running":12,"completed":9988,"cancelled":0,"failed":0,"total":10000}

Watch worker logs for flush errors:

bash
docker compose logs -f worker-rust
ScriptPurpose
tests/bench.shQueue + worker end-to-end benchmark
tests/queue-only.shMeasure bulk queue speed only
tests/e2e.shFunctional correctness (single run)

Native tools, weird experiments, and practical performance work.