Continuous load generator
load.sh on brings up three containers on the production server:
loadgen-soak: k6 with a steady ~50 VUs, restarts every 50 min to pick up rotated tokensloadgen-matrix: k6 cycling throughbaseline-limit,mixed-strategy,burst-open, andrisk-stresson a looploadgen-token: alpine sidecar doing OAuth PKCE every 50 min and writing the access token to a shared volume
This is the continuous load generator. For the on-demand harness (single runs against local dev) see k6 load testing.
What it’s for
Section titled “What it’s for”Demonstrates how the platform behaves under near-real-world trade flow. Useful for:
- Live demos: visitors land on the deployment URL and see real orders moving through the pipeline, real fills, real candles updating
- Performance baselines: k6 percentile metrics ship to Prometheus
- Surfacing regressions: if a deploy slows order acks below baseline, the trend on the Grafana dashboard shifts visibly
It is not a substitute for the synthetic probe. Loadgen tests throughput, not liveness.
One-time setup
Section titled “One-time setup”# On the server, drop the credentials file (mode 600)ssh <user>@<server-host>ADMIN_PW=$(sudo grep ^OAUTH2_USER_SECRETS /opt/stacks/veta/.env \ | head -1 | cut -d= -f2- | tr ';' '\n' | grep ^admin: | cut -d: -f2-)echo "LOADGEN_OAUTH_PASSWORD=$ADMIN_PW" | sudo tee /opt/stacks/veta/.env.loadgensudo chmod 600 /opt/stacks/veta/.env.loadgensudo chown miles:miles /opt/stacks/veta/.env.loadgenThe password is read from OAUTH2_USER_SECRETS (per-user) for the admin
account, not from OAUTH2_SHARED_SECRET. The two diverged in 2026-05
and loadgen broke for 5 days before this was diagnosed. See the history
note below.
Optional tuning vars in the same file:
| Env | Default | Effect |
|---|---|---|
LOADGEN_OAUTH_USERNAME | admin | Which user the load runs as |
LOADGEN_SOAK_VUS | 50 | Steady VUs on soak |
LOADGEN_SOAK_DURATION | 50m | Soak iteration length before token refresh |
LOADGEN_MATRIX_SLEEP | 30 | Pause (seconds) between matrix iterations |
LOADGEN_MATRIX_SCENARIOS | baseline-limit.js mixed-strategy.js burst-open.js risk-stress.js | Scripts the matrix cycles through |
Daily use
Section titled “Daily use”# Switch on (requires .env.loadgen present)ssh <user>@<server-host> '/opt/stacks/veta/scripts/load.sh on'
# Check it's runningssh <user>@<server-host> '/opt/stacks/veta/scripts/load.sh status'
# Tail logsssh <user>@<server-host> '/opt/stacks/veta/scripts/load.sh logs'
# Switch offssh <user>@<server-host> '/opt/stacks/veta/scripts/load.sh off'Auto-start on every deploy
Section titled “Auto-start on every deploy”scripts/homelab-deploy.sh includes compose.loadgen.yml whenever
.env.loadgen exists on the server. Every auto-pull tick therefore brings
loadgen up automatically, so there is no “loadgen was off, then a deploy
re-started it” surprise.
Removing loadgen permanently: delete /opt/stacks/veta/.env.loadgen.
History note
Section titled “History note”The first loadgen install hard-coded LOADGEN_OAUTH_PASSWORD to the value
of OAUTH2_SHARED_SECRET. When OAUTH2_USER_SECRETS was added in 2026-05
(per-user passwords), admin got its own secret, and verifyOAuthCredentials
in user-service prefers the per-user secret over the shared one when a user
is in the map. Loadgen was sending the wrong password and getting 401, which
the gateway logged as auth_failure ~1,100 lines/sec, contributing to the
2026-05-14 disk-fill incident.
Fix landed in PR #234’s gateway log-throttle plus a one-off .env.loadgen
rotation on the server. The post-fix install command above extracts the
correct per-user password automatically.
Source
Section titled “Source”compose.loadgen.yml: k6 + token-refresh service definitionsscripts/load.sh: on/off/status/logs wrapperscripts/loadgen/: token-refresh + loop scripts
Related
Section titled “Related”- k6 load testing: on-demand harness for single runs
- Synthetic probe: liveness check, not throughput
- Performance: Grafana percentile baselines that loadgen feeds