Skip to main content

Live set load run

The Live Hub promises that a completed set is on the board at once. Whether that holds when a whole group lifts cannot be read off the code, and the server's cost for one set had not been measured. The load run measures it: up to twenty simulated lifters complete sets into one live session, and for every set the run records how long it takes from the request to the set's appearance on the session's set stream — the stream a wall display follows.

The run is tests/live/load/live-set-load.ts, started through the live test tests/live/offline/17-live-set-load.live.test.ts. It talks to backdev only, with one disposable owner that is deleted again.

Running it​

ENODE_LIVE_LOAD=1 npx vitest run --config tests/live/vitest.config.ts \
tests/live/offline/17-live-set-load

Without ENODE_LIVE_LOAD=1 the test is skipped: it stores a few hundred sets on backdev and takes about five minutes, so it does not ride along with the rest of the live suite. The report is printed and written to tests/live/out/live-set-load-<run>.txt; every set's record is in the .json beside it.

The profile is set with environment variables:

VariableDefaultMeaning
ENODE_LOAD_LIFTERS1,2,5,10,20How many lifters send at the same time, one step after the other.
ENODE_LOAD_SETS6Sets every lifter completes in a step.
ENODE_LOAD_REST_MS3000A lifter's rest between two sets, varied by a quarter either way.
ENODE_LOAD_BURSTS3Rounds, after the steps, in which every lifter completes a set in the same instant. 0 leaves the step out.
ENODE_LOAD_REPS5Repetitions per set. Each is two reps on the wire, down and up, with one sensor package each.
ENODE_LOAD_STANDINGSon0 leaves out the standings reads a tablet makes after each set it delivered.

The run loads a server other people use. On 2026-10-07 backdev stopped answering for about a minute (requests timed out, then 502) right after the third run of that morning. That run had cleaned up by deleting its twenty athletes five at a time, and nine of those deletes ran into the 15 s deadline of a request. The likely cause is that cleanup and not the sets — the two runs before it, which deleted one athlete after the other, had no such effect — but only the server's log for 07:46 to 07:49 UTC can say. The cleanup deletes one athlete at a time since. Run the full profile when nobody else is testing against backdev, and not several times in a row.

What is real and what is simulated​

  • Real: the wire. A set is built by the app's own mapper (toLiveSessionSetCreateDto) with reps and gzipped sensor packages, and sent to the route the app sends to, from a tablet with a device session of its own. Every lifter has a tablet.
  • Real: what a tablet does after a set, where the server does not push to it. Such a tablet re-reads the session's standings once the set is delivered (packages/core/src/live-session/standings-follow.ts), paced by the app's own scheduler (read-scheduler.ts): one read on its way, one more remembered, two seconds apart. The run uses that scheduler, because those reads are part of what a set costs. Until 2026-10-07 a tablet read when the set was queued and again when it was delivered, and dropped the second read while the first was on its way; the first runs below measured that.
  • Not in the run: a tablet the server pushes to. A tablet signed in as the session's owner follows the set topic, reads once and nothing per set (backdev, 2026-10-07). The run measures the costlier way, which is what an athlete's own phone does today.
  • Simulated: the pace. A lifter in a gym completes a set every two to three minutes. In the run a lifter completes one every three seconds, so twenty lifters send like several hundred. The steps therefore show where the path gives way, not what a training looks like.
  • Realistic: the last step. Every lifter completes a set in the same instant — a group that lifts on a signal.
  • Not in the run: the live outbox. The outbox is one queue per device, and one Node process has one. It is checked by 12-live-outbox and by the self-test's Live chapter.

Reading the report​

One line per step:

ColumnMeaning
sets/sSets per second the step sent, over its whole length.
answer msFrom sending the request to having read the server's answer.
on the stream msFrom sending the request to the set's live_session_set.created frame on the set stream. This is what a wall waits for.
standings read ms, kBOne standings read of a tablet (GET /workout_live_session_sets/reduced/:id), and the size of the largest answer in the step.
staleSets after which the tablet's standings do not show the set it has just completed: the read that followed the delivery failed, or came back without the set.
failedCreates that were not answered 201.
lost, twiceSets the session does not hold after the step, or holds more than once.
not shown, shown twiceStored sets with no frame on the stream, or with more than one.

p50 is the middle value, p95 the value 95 of 100 sets stay under. Under the table the report prints the time to the stream for every single set, in the order the sets were sent.

The test fails when a step has a value other than 0 in failed, lost, twice, not shown or shown twice. The times are a measurement, not a limit; the test asserts none.

What the first runs showed​

Four runs against backdev on 2026-10-07 between 09:30 and 10:00, 912 sets in all. Each is one run on a server that other tests used at the same time, so the times are an order of magnitude, not a benchmark.

No set was lost, stored twice, missing from the stream or on it twice, in any step of any run.

A set of five repetitions (ten reps with a package each) is 32 kB on the wire. A request the server has nothing to do for took about 0.1 s from the same machine.

Full profile, standings reads on (09:33):

LiftersSets/sOn the stream, p50 / p95 msStandings read, p50 / p95 msLargest read
10.29841 / 921242 / 37014 kB
20.48608 / 815288 / 49942 kB
51.30706 / 891567 / 890110 kB
102.42740 / 9971129 / 1637251 kB
202.021818 / 29286518 / 10162531 kB
20 at once1.255465 / 725012454 / 14926653 kB

Full profile, standings reads off (09:39):

LiftersSets/sOn the stream, p50 / p95 ms
10.261031 / 1394
20.50904 / 1302
51.25653 / 1138
102.63626 / 938
205.33651 / 1135
20 at once3.822797 / 3168

What follows from them:

  • One set costs the server about half a second. A set alone was on the stream after 0.4 to 1.4 s, and after 0.5 to 0.7 s in the middle once a run had warmed up; the first step of every run is its slowest, with six sets to go by. The frame arrives within some tens of milliseconds of the answer, so the stream adds next to nothing: the time is the server storing the set.
  • The set path itself does not grow up to twenty lifters. Without the standings reads the time to the stream stayed at about 0.65 s from five lifters to twenty, at 5.3 sets per second.
  • Everybody at once takes seconds. Twenty sets in the same instant were on the stream after 2.8 s in the middle and 3.6 s at the latest, without the standings reads.
  • The cost of a set depends on its reps. With one repetition per set (two reps, 7 kB) the time at ten and twenty lifters was 0.32 to 0.40 s, against 0.63 to 0.65 s with five (run of 09:44).
  • The standings read is what grows. One set weighs 2.4 kB in that read, whatever its reps: 783 bytes are the athlete's profile and 354 bytes per measurement are the metric's definition, both repeated for every set. The read therefore grows with the session — 653 kB at 288 sets — and every tablet sends it up to twice per set. With the reads on, the same ladder got to 2 sets per second instead of 5.3, the time to the stream at twenty lifters was 1.8 s instead of 0.65 s, and a read took 6.5 s in the middle. In the last step reads took up to 15.2 s, which is past the app's deadline for a request (15 s).
  • A slow standings read leaves the tablet's standings without the set just lifted. The read sent when the set is queued reaches the server before the set does. When it is still on its way at the moment the set is delivered, the second read is not sent, and the standings stay as they were until the next set. In the run of 09:55 (ten lifters, 189 kB per read) that happened for 3 of 50 sets, and each time the standings lacked the set. In the run of 09:33 the second read was not sent for any set from ten lifters on.

The two runs with standings reads differ at ten lifters: a read took 1.1 s in the middle at 09:33 and 0.2 s at 09:55, for a session of about the same size. Either something else used the server at 09:33, or the reads slow each other down more than their size explains. One more run on a quiet server would tell.

The last two points are not in the load run's own files. They are passed on in the Live Hub page, and closed by block W9; see the next section.

After the tablet reads by the new rule (2026-10-07, 13:09 and 13:12)​

Block W9 changed when a tablet reads its standings, and the run reads the same way since. Two runs on a backdev that seven other sessions were testing against at the same time: the profile of the run of 09:55 (ENODE_LOAD_LIFTERS=1,5,10 ENODE_LOAD_SETS=5 ENODE_LOAD_BURSTS=1) at 13:12, and its ten-lifter step alone at 13:09.

RunLiftersSetsSets/sOn the stream, p50 / p95 msStandings read, p50 / p95 / max msLargest readStale
09:55, the old rule10502.79555 / 723207 / 396 / 471189 kB3
13:12, the new rule150.26723 / 878242 / 325 / 32512 kB0
5251.29543 / 791140 / 339 / 34471 kB0
10502.78494 / 758174 / 370 / 410189 kB0
10 at once105.841129 / 1264415 / 496 / 496213 kB0
13:09, the new rule10502.56517 / 800165 / 370 / 528118 kB0
  • No set is missing from the standings of the tablet that lifted it: 0 of 140 in the two runs, against 3 of 50 in the ten-lifter step of 09:55. Every set was in the read that followed its delivery, also with ten sets in the same instant.
  • One read per delivered set, where there were up to two. A read still returns the whole session, so its size and its time at a given size are what they were.
  • No set was lost, stored twice, missing from the stream or on it twice.
  • Not run again: the ladder to twenty lifters. The growth of a read with the session is unchanged, so the numbers of 09:33 for twenty lifters stand for a device that reads, until the server sends less per set (handoff S2 and item 5) or admits every linked device to the set topic (P1). A tablet of the session's owner does not read per set at all; the run does not measure it.