Skip to content

Commit c588c6f

Browse files
committed
docs(run-engine): add CK virtual-time benchmark results
Real flag OFF-vs-ON A/B from a local homelab with a production-like multi-cluster topology (not production, no prod data or traffic). Micro arm: a light key behind a backlog goes from first-served at step 480 to step 4, wait p95 down ~70%, contention fairness 0.34 to 1.0, with the balanced and lone-key cases unchanged and drain steps identical (work-conserving). End-to-end arm across three worker clusters: the victim tenant start latency drops ~38% mean while the flood tenant is unchanged. Relative numbers only; single box, not prod scale.
1 parent 2fbe9cd commit c588c6f

1 file changed

Lines changed: 80 additions & 0 deletions

File tree

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# CK virtual-time scheduling: A/B benchmark results (2026-07-27)
2+
3+
Run on a **local homelab box, not production**. It is a *production-like*
4+
topology only in shape: one shared control plane (Postgres + one shared run-queue
5+
Redis + ClickHouse + the webapp/engine on this branch) and three separate managed
6+
worker clusters registered as distinct worker groups, all as containers on a
7+
single machine. No production data, traffic, or infrastructure was involved.
8+
Method and harness: `2026-07-26-ck-vtime-benchmark.md`.
9+
10+
All numbers are **relative** (flag OFF vs ON, identical load, same box). Absolute
11+
throughput on this single-box homelab is nowhere near prod scale and is not
12+
reported as such; only the OFF-vs-ON delta is meaningful.
13+
14+
## Arm 1: queue-level micro-benchmark (the isolated, defensible numbers)
15+
16+
Real `RunQueue` driven directly against a dedicated Redis, flag OFF (age-ordered
17+
CK dequeue) vs ON (virtual time), 5 trials, quantum 1 / window multiplier 3.
18+
Every arm served every message exactly once (conservation checked), so the
19+
comparison is sound. Wait is in logical dequeue steps.
20+
21+
| scenario | metric | baseline (OFF) | vtime (ON) | delta |
22+
| --- | --- | --- | --- | --- |
23+
| **ckSkew** (heavy backlog + light keys) | light-key first-serve step | 480 | 4 | **−99%** |
24+
| | light-key wait p50 | 556 | 96 | −83% |
25+
| | light-key wait p95 | 628 | 188 | −70% |
26+
| | light-key wait p99 | 636 | 196 | −69% |
27+
| | Jain fairness (contention) | 0.34 | 1.00 | +192% |
28+
| | drain step (work conservation) | 636 | 636 | 0% |
29+
| **ckTrickle** (bulk + trickle keys) | trickle first-serve step | 480 | 4 | −99% |
30+
| | trickle wait p95 | 592 | 172 | −71% |
31+
| | Jain fairness | 0.50 | 1.00 | +100% |
32+
| **ckSybil** (20 keys flood + 1 light; caps can't fix) | light first-serve step | 25 | 2 | −92% |
33+
| | light wait p95 | 34 | 27 | −21% |
34+
| **ckManyKeys** (61 variants > pass-1 window) | light first-serve step | 72 | 9 | −88% |
35+
| | drain step (no starvation) | 81 | 79 | drains fully |
36+
| **ckBalanced** (4 symmetric keys, no-harm) | worst-key wait p95 | 92 | 92 | 0% |
37+
| **ckHeavyIdle** (lone key, work conservation) | drain step | 59 | 59 | 0% (exact) |
38+
| (all scenarios) | Redis ops per dequeue+ack | baseline | +7% to +23% | cost |
39+
40+
Reading it: a light key behind a backlog goes from waiting out the whole backlog
41+
(first-serve step 480) to being served almost immediately (4), wait p95 drops
42+
~70%, and contention fairness goes from lopsided (Jain 0.34) to even (1.00). The
43+
symmetric and lone-key cases are untouched, and drain steps are identical, so the
44+
fair order is work-conserving and does no harm. The cost is a modest per-dequeue
45+
Redis op increase.
46+
47+
## Arm 2: end-to-end on the three worker regions (realism check)
48+
49+
Deployed task on one shared base queue, per-run concurrency keys, environment
50+
concurrency ceiling pinned to 5 so the CK dequeue order is the bottleneck.
51+
Noisy-neighbor load: tenant A floods across 30 keys (240 runs), tenant B sends 30
52+
runs across 3 keys, runs spread across the three regions, 1.5s hold. Metric is
53+
per-run start latency (`startedAt - createdAt`, ms). Flag flipped OFF vs ON with a
54+
control-plane redeploy between arms; identical load each arm.
55+
56+
| tenant | metric | baseline (OFF) | vtime (ON) | delta |
57+
| --- | --- | --- | --- | --- |
58+
| **B (victim, 3 keys)** | start latency mean | 221,476 | 136,344 | **−38%** |
59+
| | start latency p50 | 219,901 | 133,930 | **−39%** |
60+
| | start latency p95 | 259,092 | 221,061 | −15% |
61+
| | start latency p99 | 259,136 | 221,078 | −15% |
62+
| A (flood, 30 keys) | start latency mean | 95,130 | 100,136 | +5% |
63+
| A | start latency p50 | 95,049 | 95,689 | ~0% |
64+
65+
Reading it: under age order the light tenant waits behind the flood (mean ~221s);
66+
with virtual time it takes fair turns and drops to ~136s (mean −38%, p50 −39%),
67+
while the flood tenant is essentially unchanged (+5%, it stops jumping the queue).
68+
The smaller p95/p99 gain reflects per-**key** fairness: tenant B's keys carry
69+
deeper per-key backlogs here (10 runs/key vs the flood's 8), so B's last runs
70+
stay in the fair rotation longer. Absolute latencies are the cap-5 serialization
71+
plus per-run worker startup on a single box; only the OFF-vs-ON delta is the
72+
signal.
73+
74+
## Bottom line
75+
76+
Both arms agree: the fair order removes the starvation of a light concurrency key
77+
behind a large or sharded backlog, does not harm the balanced or lone-key cases,
78+
stays work-conserving, and costs a small, bounded per-dequeue Redis overhead. The
79+
micro-benchmark isolates the scheduler (the defensible numbers); the end-to-end
80+
run confirms the same effect on real deployed runs across the worker regions.

0 commit comments

Comments
 (0)