Skip to content

feat(journeys): ramp journeys together to catch interference, and --expect - #9

Merged
pyjeebz merged 7 commits into
mainfrom
feat/journeys-combined
Nov 2, 2026
Merged

pyjeebz merged 7 commits into
mainfrom
feat/journeys-combined

Conversation

@pyjeebz

@pyjeebz pyjeebz commented Nov 2, 2026

Copy link
Copy Markdown
Owner

Stacked on #8 (itself on #7). The base is #8's branch, so the diff shows only this slice. It gets retargeted to main once #7 and #8 merge.

After ramping each journey alone, prescale run ramps them all together, with the users split between journeys by weight. Each user stays on its journey for the stage, so a weight is that journey's share of the users in the app at once, and the number on each journey at every level is exact. If the journey that breaks first does so with no more of its own users than it surely held alone (the low end of its band), the report flags interference: it competes with the other journeys for something they share, such as a database pool, CPU or a cache. No journey shows that on its own. --no-combined skips this pass.

--expect N says whether you'd hold N users at peak. A URL run compares its verdict. In a journey run, N is split across journeys by weight: each journey must hold its share alone, and all journeys together must hold N. A journey that wasn't load-tested still takes its share and makes the answer unknown (❔). It reports only; it doesn't change the exit code (--fail-under does).

Saved runs gain combined (mix, survives, band, culprit journey and step, interference) and expect. Both are additive under schema_version: 1.

How it was verified

The example shop, with checkout plus a one-step "quickbuy" journey (GET /checkout). Both draw from the shop's 25-connection pool.

$ SHOP_PASSWORD=demo prescale run http://localhost:8421 --dir … -u 200 -s 2 --expect 60
checkout alone: survives 20 (band 20–30)   quickbuy alone: survives 20
All journeys together (50/50): breaks at 30 users in total, at checkout › place order
Interference  checkout broke with 15 of its own users, but held 20 alone. It competes with quickbuy …
Expected  🛑 ~60 users: checkout ~30 (holds 20 alone), quickbuy ~30 (holds 20), all together holds 20
  • browse + checkout with the default weights (60/5), --expect 300: together they held through 400 users; no interference. The answer is still 🛑, because checkout's share (~23) is above the ~20 it holds alone.
  • Unit tests: apportionment adds up and follows the weights. With a 50/50 mix where one journey thinks 1s per walk, exactly 5 of 10 users are on each, and each user walks only its own journey. Also covered: interference true, false and held; --expect in URL and journey mode, with untested journeys; --no-combined.
  • ruff check cli/src: clean · pytest cli/tests -q: 304 passed.

Risk

Tier: risky · Public surface: Result combined and expect, plus the JSON Schema; flags --no-combined and --expect; combined-pass steps labelled journey › step. Rollback: revert; the fields are additive and runs stay local.

Fresh-context review blocked a real flaw in the first version. Weight decided which journey each walk started, not how many users were in each journey at once. A slow journey (checkout's 1s think) held far more concurrent users than its weight said. So the mix, the "users at break" figure behind interference, and the --expect split were estimates, and wrong. The first version's interference result on the shop was that artifact. The fix pins each user to one journey, apportioned by weight, and the evidence above was re-measured after it. Also from the review:

  • an untested journey makes --expect unknown instead of being silently dropped
  • the help text and README say --expect doesn't change the exit code
  • the combined ramp's warnings are kept
  • the interference rule compares against the low end of the journey's band alone

Notes for review

The HTML report doesn't show journeys, the combined pass or --expect yet. That's slice 4 ("journeys everywhere": show, HTML, compare, the PR comment and MCP).



Generated by Claude Code

Copilot AI balanced review requested due to automatic review settings November 2, 2026 15:40

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota. Try requesting a review at a lower effort level.

# Conflicts:
#	CHANGELOG.md
#	README.md
#	cli/src/prescale_cli/commands/run.py
#	cli/src/prescale_cli/journeys/load.py
#	cli/src/prescale_cli/journeys/replay.py
#	cli/src/prescale_cli/loadtest.py
#	cli/src/prescale_cli/render.py
#	cli/src/prescale_cli/schema/result.schema.json
#	cli/tests/test_journeys_load.py
@pyjeebz
pyjeebz changed the base branch from feat/journeys-at-load to main November 2, 2026 15:59
@pyjeebz
pyjeebz merged commit 54f8f59 into main Nov 2, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants