Nothing on this rig is real. Simulated tenants, obviously synthetic data (
Rig Patient 000123),
nhs_numberdeliberately left NULL, no seed taken from any live database. It runs on the auxiliary host in
its own container, its own network and its own reserved port range, touches no production container,
database or network, and the only commands its tool can run are the rig's own scripts. What it measures
is the platform's shape under client count — which is real.
The rig is an instrument, not a dashboard. It answers two different questions, and they come from two
different panels: what the maths says (a model whose every constant is labelled) and what actually
happened (a load run, with its own log and CSV). Reading one as if it were the other is the main way to be
confidently wrong here, so the panels are described separately below.
https://scaling.veripath.co.uk/scaling/ — sign in with your veripath realm account, the same one the
site and manuals hosts use. This hostname accepts realm-native identities only: a sign-in through the
"Partner Organisations" broker is refused before a session is created and returns you to the tool with
?auth_error=idp_not_permitted. That is deliberate — an internal ops surface should not be reachable by an
identity brokered in from a partner realm.
The page is three panels — the model, the run, the state — and refreshes the state panel every 15 seconds.
Five inputs, then Calculate. Nothing is sent anywhere except the rig's own tool.
| Input | What it is | What to put in it |
|---|---|---|
| Clients (N) | how many client practices (one VPS each in the SIAAS model) | the fleet size you are asking about |
| Requests/s per client | the demand one practice puts on the platform | the honest figure — see §6.2. This single number decides the whole answer |
| Queries per request | how many database queries a tenant-backed page costs | 5 is the mid-point of the measured 1–20 sensitivity range; move it to see the spread |
| App worker processes | web workers serving the app tier | 2, which is what is deployed today |
| Connection pooler | whether requests reach the database through the pooler | in front (today) is production's shape; direct is the A/B |
What comes back:
Change one input at a time and press Calculate again — the panel is cheap to re-run, and single-variable
moves are how you learn which pressure you are actually looking at.
| Input | Meaning |
|---|---|
| Clients | how many simulated clients the generator drives |
| Total requests/s | the whole fleet's rate — not per client (the model panel takes per client; this one does not) |
| Seconds | run length, 5–900 |
| Label | names the CSV the run writes, so a reading can be traced later |
Start run brings up the generator and the tunnels and streams its log; Stop is one button and is the
correct end to any run. When it finishes, the summary line gives requests served, elapsed seconds, the
achieved rate, p50 and p95 latency, and the split between 200s and everything else.
Every run also writes its own CSV on the rig host. The CSV is the record — a table on a screen is a
convenience, and a number worth quoting belongs in the wiki with its label and arm shape attached (§7).
The honest picture of what is up right now: the rig's containers with uptime, how many tunnel processes are
running, which reserved tunnel legs are bound, the manifest (how many clients and which seeded databases they
draw on), the register rows the application builds its database aliases from, and when the last run happened.
If this panel looks wrong, stop and fix it before trusting a reading — a run over half a manifest measures
the rig, not the platform.
| Pressure | What drives it | What "binding" looks like |
|---|---|---|
| Hub memory | one tunnel session per simulated client, at a measured cost per session, against a 70%-of-RAM trigger | memory percentage climbing towards the trigger as N rises |
| Central database connections | the collection poll and the app tier's own connections | bounded by design today: the poll is one staggered sweep, not one connection per client, and the app reaches the database through the pooler. Unbounded if either is undone — which is why the panel will show a cliff only in that case |
| App tier | requests/s required against the capacity of the worker processes | the required rate reaching the available rate. This is the most conservative line in the model: it assumes one request at a time per worker, where the measured tenant-page ceiling with the pooler is higher |
| Metric volume | rows written per client per day against a 5M-row trigger | a shrinking "days to 5M" — a data-retention problem arriving, not a capacity wall |
The fleet size is decided by how much demand one practice generates, so the curve must be drawn against a
per-client rate, not against a generator's saturation. Two reference points, and neither is a realistic
demand figure on its own:
The useful move is to run the ladder at a rate you can defend from the estate's own data, and to say out loud
which rate a reading was taken at.
"Binds first" is a ratio, not a verdict: it names the component closest to its own limit and how close.
The knee is the largest client count that still fits at the rate entered. Both move with the rate, which is
why a reading without its rate is not a reading. Two rungs of the same curve show the two regimes: at
0.02 req/s per client the hub ends the fit at about 498 clients whichever arm is in front, because
the demand never reaches the app tier's ceiling; at 0.2 req/s per client the app tier ends it — at
68 clients with connections direct and 263 with the pooler in front. Same fleet, same load, one
component changed, and the knee moves fourfold.
The model panel is a model: fast, complete, and only as good as its constants — which is why each one
carries its label. The rig is a measurement: slow, partial, and real. Use the model to choose what to
measure, and the run to replace the assumptions with numbers. The four assumptions currently in the model are
its to-do list: queries per request, staff per client, seconds between requests, and the memory cost of an
extra worker process.
| Arm | Shape | What it is for |
|---|---|---|
| fidelity (default) | one tunnel connection per simulated client, as a real client box does | the honest per-client cost, including the hub's per-session memory and CPU |
| multiplex | one connection carrying every client's leg | driving N high when the rig host's own process budget would otherwise become the ceiling |
Neither changes the application load — same tenants, same paths, same seeded databases, same request mix.
The difference is the hub-side per-session cost, which the multiplex arm deliberately takes out. A number
must be labelled with the arm that produced it, or it cannot be compared with any other number.
N: many logical clients share adocker compose down, no prune, nothing outside its own network.