feat: fold chaos scenarios into long_horizon phase types (#12 partial, #13)#14
Conversation
…variance, not regression) Re-running pg-boss at 128 workers on identical code gave 4,971 jobs/s, vs 4,051 in the published matrix and 5,768 in the previous bulk-everywhere matrix. Three reads, ~30% spread — same run-to-run envelope we see on awa-128 between matrix runs. The 64-worker row (4,541) is more stable across runs and is the better headline.
Consolidate chaos.py's runner into the long_horizon phase substrate (issues #12 partial, #13). Chaos scenarios become named phase compositions and the chaos-specific orchestration moves into phase-type hooks that share the sample stream, wait-event sampler, and per-phase aggregator with every other scenario. New phase types: - postgres-restart - pg-backend-kill(rate=N) - pool-exhaustion(idle_conns=N) - repeated-kill(instance=I,period=Ns) New scenarios (chaos_crash_recovery, chaos_postgres_restart, chaos_repeated_kills, chaos_pg_backend_kill, chaos_pool_exhaustion) all desugar to warmup -> baseline -> chaos -> recovery and the writer aggregates jobs_lost / chaos_recovery_time_s into summary.json against the trailing recovery phase. chaos.py is left in the repo as deprecated reference for leader_failover / retry_storm / priority_starvation, which are system-specific and not yet migrated.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b9f4deede9
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| except psycopg.Error: | ||
| # PG might bounce mid-loop in combined chaos | ||
| # scenarios; reconnect on the next tick. | ||
| break |
There was a problem hiding this comment.
Keep pg-backend kill loop reconnecting after query errors
When cur.execute raises inside _pg_backend_kill_loop, the handler immediately breaks out of the loop, which ends the background thread for the rest of the phase. In practice, a transient DB error (for example during a restart or brief connection interruption) will disable backend-kill injection entirely, so chaos_pg_backend_kill runs can silently devolve into a mostly clean phase and corrupt the experiment signal.
Useful? React with 👍 / 👎.
| chaos_samples = _rate_samples(chaos_labels, "completion_rate") | ||
| chaos_end_t: float | None = None | ||
| if chaos_samples: | ||
| chaos_end_t = chaos_samples[-1][0] |
There was a problem hiding this comment.
Compute recovery time even when chaos has no completion samples
chaos_recovery_time_s is gated on having at least one chaos-phase completion_rate sample, because chaos_end_t is derived only from completion_rate rows. For severe outages, adapters can skip rate samples during chaos, and then this path leaves chaos_recovery_time_s as None even if recovery samples clearly show when throughput returns, dropping a key metric in the exact failure modes this aggregation is meant to cover.
Useful? React with 👍 / 👎.
Summary
Consolidate the legacy
chaos.pyrunner into thelong_horizon.pyphase substrate. Chaos scenarios become named phase compositions and the chaos-specific orchestration moves into phase-type hooks that share the sample stream, wait-event sampler, and per-phase aggregator with every other scenario. Closes #13. Partial-closes #12 — thechaos.pyhalf is gone; cross-system chaos runs still need to land scenario-by-scenario via the new phase types.New phase types (
bench_harness/phases.py+hooks.py)postgres-restartdocker compose stop postgresfor the first half of the duration,docker compose startfor the rest. Drives the harness-managed compose lifecycle via callbacks the orchestrator stashes inruntime.state.pg-backend-kill(rate=N)pg_terminate_backend(pid)against the SUT'sdatnameevery1/Nseconds (filtered to active / idle-in-tx). Uses datname rather than application_name because adapters don't uniformly set it.pool-exhaustion(idle_conns=N)max_connectionsis below the request — the chaos point is "what happens under pressure," not "fail the run if PG can't fit."repeated-kill(instance=I,period=Ns)period, composing the existingkill_worker/start_workerpool methods. Re-usesparse_durationfor the period spec.Existing
kill-worker/start-workerare unchanged.New scenarios
chaos_crash_recovery,chaos_postgres_restart,chaos_repeated_kills,chaos_pg_backend_kill,chaos_pool_exhaustion— all warmup → baseline → chaos → recovery shapes, ~4 minutes each.Aggregator
bench_harness/writers.py::compute_summarynow derives, for any clean / recovery phase that immediately follows a chaos / lifecycle span:jobs_lost= ∫enqueue_rate − ∫completion_rate over the chaos+recovery span (rate × window_sintegrated).chaos_recovery_time_s= elapsed_s from end of chaos until completion_rate first re-attains 90% of the baseline median.baseline_completion_rate_median(sanity-check field).These attach to the recovery phase block in
summary.json. Duplicate-completions tracking (chaos.py originally hashed per-job idempotency keys) is out of scope for this PR — the harness doesn't currently track per-job ids; tracked as follow-up.Pending (NOT migrated)
The following chaos.py scenarios stay in the deprecated runner because they are system-specific:
leader_failover(awa-only — depends on awa's leader election semantics)retry_storm(needs adapter-driven retry-storm shape)priority_starvation(needs priority-aware adapter producer)chaos.pyis not deleted. It carries a deprecation banner pointing at the new scenarios and emits aDeprecationWarningon import.Smoke run
summary.jsonrecovery block:{ "jobs_enqueued": 2359542.83, "jobs_completed": 150448.27, "jobs_lost": 2209094.56, "chaos_recovery_time_s": 4.62, "baseline_completion_rate_median": 457.20 }(The absolute jobs_enqueued is inflated because awa's depth-target producer reports very large rates while the kill phase has zero workers; the metric mechanics are correct but the awa-side instrumentation in that mode is something to revisit.
chaos_recovery_time_sof 4.6 s is the trustworthy number — completion rate came back to ≥90% of baseline within one sample window after the restart.)index.html,plots/, and per-phase wait-event histograms all render (wait-event sampler runs by default, untouched).Tests
5 new tests: scenario resolution for each new chaos scenario + a synthetic-csv test that exercises the writer's chaos aggregator end-to-end.
Test plan
pytest tests/ -q— 95 pass (was 85; +10 new)chaos_crash_recoveryagainst awa with 2 replicas — emittedjobs_lost,chaos_recovery_time_s, renderedindex.htmlchaos_*scenario — follow-upleader_failover/retry_storm/priority_starvation— follow-up issue