Skip to content

Commit 2c98dde

Browse files
authored
demo(benchmark-react): Use response-size-based network simulation delays (#3810)
Replace fixed per-method network delays with a formula: 40ms base latency + 2ms per record in the response. This more realistically models how network time scales with payload size, naturally penalizing large list refetches relative to normalized cache propagation. Made-with: Cursor
1 parent ee441c6 commit 2c98dde

File tree

7 files changed

+56
-46
lines changed

7 files changed

+56
-46
lines changed

examples/benchmark-react/README.md

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -55,34 +55,34 @@ Illustrative **relative** results with **baseline = 100%** (plain React useState
5555

5656
| Category | Scenarios (representative) | data-client | tanstack-query | swr | baseline |
5757
|---|---|---:|---:|---:|---:|
58-
| Navigation | `getlist-100`, `getlist-500`, `getlist-500-sorted` | ~96% | ~98% | ~99% | **100%** |
59-
| Navigation | `list-detail-switch-10` | **~949%** | ~199% | ~203% | 100% |
60-
| Mutations | `update-entity`, `update-user`, `update-entity-sorted`, `update-entity-multi-view`, `unshift-item`, `delete-item`, `move-item` | **~4486%** | ~96% | ~99% | 100% |
61-
| Scaling (10k items) | `update-user-10000` | **~2006%** | ~84% | ~103% | 100% |
58+
| Navigation | `getlist-100`, `getlist-500`, `getlist-500-sorted` | ~95% | ~97% | ~99% | **100%** |
59+
| Navigation | `list-detail-switch-10` | **~851%** | ~233% | ~247% | 100% |
60+
| Mutations | `update-entity`, `update-user`, `update-entity-sorted`, `update-entity-multi-view`, `unshift-item`, `delete-item`, `move-item` | **~4442%** | ~97% | ~99% | 100% |
61+
| Scaling (10k items) | `update-user-10000` | **~6408%** | ~94% | ~100% | 100% |
6262

6363

6464
## Latest measured results (network simulation on)
6565

66-
Median ops/s per scenario; range is approximate 95% CI margin from the runner (`stats.ts`). **Network simulation** applies the per-RPC delays in `bench/scenarios.ts` (`NETWORK_SIM_DELAYS`, e.g. `fetchIssueList` 80 ms, `updateUser` 50 ms) so list refetches after an author update pay extra latency compared to normalized propagation.
66+
Median ops/s per scenario; range is approximate 95% CI margin from the runner (`stats.ts`). **Network simulation** uses response-size-based delays (`NETWORK_SIM_CONFIG` in `bench/scenarios.ts`: 40 ms base latency + 1 ms per 20 records) so list refetches after an author update pay extra latency compared to normalized propagation.
6767

68-
Run: **2026-03-21**, Linux (WSL2), `yarn build:benchmark-react`, static preview + `env -u CI npx tsx bench/runner.ts --network-sim true` (all libraries; memory scenarios not included). Numbers are **machine-specific**; use them for relative comparison between libraries, not as absolutes.
68+
Run: **2026-03-22**, Linux (WSL2), `yarn build:benchmark-react`, static preview + `env -u CI npx tsx bench/runner.ts --network-sim true` (all libraries; memory scenarios not included). Numbers are **machine-specific**; use them for relative comparison between libraries, not as absolutes.
6969

7070
| Scenario | data-client | tanstack-query | swr | baseline |
7171
|---|---:|---:|---:|---:|
7272
| **Navigation** | | | | |
73-
| `getlist-100` | 11.20 ± 0.03 | 11.27 ± 0.02 | 11.43 ± 0.07 | 11.55 ± 0.02 |
74-
| `getlist-500` | 9.78 ± 0.12 | 10.01 ± 0.13 | 10.16 ± 0.13 | 10.22 ± 0.07 |
75-
| `getlist-500-sorted` | 9.82 ± 0.16 | 10.08 ± 0.13 | 10.21 ± 0.07 | 10.29 ± 0.06 |
76-
| `list-detail-switch-10` | 6.93 ± 1.02 | 1.45 ± 0.04 | 1.48 ± 0.08 | 0.73 ± 0.00 |
73+
| `getlist-100` | 18.48 ± 0.02 | 18.62 ± 0.07 | 19.12 ± 0.02 | 19.34 ± 0.09 |
74+
| `getlist-500` | 11.45 ± 0.21 | 11.92 ± 0.18 | 11.96 ± 0.04 | 12.06 ± 0.08 |
75+
| `getlist-500-sorted` | 11.48 ± 0.39 | 11.81 ± 0.22 | 12.00 ± 0.34 | 12.08 ± 0.37 |
76+
| `list-detail-switch-10` | 6.13 ± 0.74 | 1.68 ± 0.07 | 1.78 ± 0.12 | 0.72 ± 0.00 |
7777
| **Mutations** | | | | |
78-
| `update-entity` | 357.14 ± 11.48 | 7.01 ± 0.02 | 7.02 ± 0.02 | 7.22 ± 0.00 |
79-
| `update-user` | 333.33 ± 14.44 | 7.01 ± 0.02 | 7.17 ± 0.03 | 7.22 ± 0.01 |
80-
| `update-entity-sorted` | 312.50 ± 23.44 | 7.08 ± 0.00 | 7.07 ± 0.03 | 7.28 ± 0.01 |
81-
| `update-entity-multi-view` | 357.14 ± 52.30 | 6.82 ± 0.34 | 6.88 ± 0.39 | 7.14 ± 0.36 |
82-
| `update-user-10000` | 97.09 ± 7.73 | 4.07 ± 0.02 | 4.97 ± 0.02 | 4.84 ± 0.03 |
83-
| `unshift-item` | 285.71 ± 4.90 | 6.92 ± 0.02 | 7.16 ± 0.00 | 7.16 ± 0.02 |
84-
| `delete-item` | 312.50 ± 9.77 | 6.93 ± 0.01 | 7.15 ± 0.01 | 7.16 ± 0.01 |
85-
| `move-item` | 285.71 ± 10.61 | 6.39 ± 0.02 | 6.83 ± 0.00 | 6.82 ± 0.00 |
78+
| `update-entity` | 333.33 ± 4.22 | 6.95 ± 0.00 | 6.94 ± 0.02 | 7.17 ± 0.00 |
79+
| `update-user` | 322.58 ± 11.79 | 6.97 ± 0.01 | 7.15 ± 0.00 | 7.15 ± 0.02 |
80+
| `update-entity-sorted` | 285.71 ± 30.41 | 7.04 ± 0.01 | 7.05 ± 0.02 | 7.23 ± 0.01 |
81+
| `update-entity-multi-view` | 344.83 ± 16.69 | 5.89 ± 0.77 | 5.89 ± 0.82 | 5.97 ± 0.05 |
82+
| `update-user-10000` | 98.04 ± 5.79 | 1.44 ± 0.01 | 1.53 ± 0.00 | 1.53 ± 0.01 |
83+
| `unshift-item` | 285.71 ± 11.11 | 6.89 ± 0.02 | 7.11 ± 0.01 | 7.11 ± 0.01 |
84+
| `delete-item` | 312.50 ± 14.76 | 6.87 ± 0.01 | 7.09 ± 0.01 | 7.10 ± 0.00 |
85+
| `move-item` | 256.41 ± 8.77 | 6.34 ± 0.06 | 6.80 ± 0.01 | 6.77 ± 0.01 |
8686

8787
[Measured on a Ryzen 9 7950X; 64 GB RAM; Ubuntu (WSL2); Node 24.12.0; Chromium (Playwright)]
8888

examples/benchmark-react/bench/runner.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
LIBRARIES,
1111
RUN_CONFIG,
1212
ACTION_GROUPS,
13-
NETWORK_SIM_DELAYS,
13+
NETWORK_SIM_CONFIG,
1414
} from './scenarios.js';
1515
import { computeStats, isConverged } from './stats.js';
1616
import { parseTraceDuration } from './tracing.js';
@@ -187,8 +187,9 @@ async function runScenario(
187187

188188
if (networkSim) {
189189
await (bench as any).evaluate(
190-
(api: any, delays: Record<string, number>) => api.setMethodDelays(delays),
191-
NETWORK_SIM_DELAYS,
190+
(api: any, config: { baseLatencyMs: number; recordsPerMs: number }) =>
191+
api.setNetworkSim(config),
192+
NETWORK_SIM_CONFIG,
192193
);
193194
}
194195

@@ -257,8 +258,9 @@ async function runScenario(
257258
(api: any, [action, n]: [string, number]) => api[action](n),
258259
[preMountAction, mountCount],
259260
);
261+
const preMountTimeout = networkSim ? 60000 : 10000;
260262
await page.waitForSelector('[data-bench-complete]', {
261-
timeout: 10000,
263+
timeout: preMountTimeout,
262264
state: 'attached',
263265
});
264266
const preMountTimedOut = await harness.evaluate(el =>

examples/benchmark-react/bench/scenarios.ts

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,10 @@
11
import type { BenchAPI, Scenario, ScenarioSize } from '../src/shared/types.js';
22

3-
/** Per-method network latency used when --network-sim is enabled (default: on). */
4-
export const NETWORK_SIM_DELAYS: Record<string, number> = {
5-
fetchIssueList: 80,
6-
fetchIssue: 50,
7-
fetchUser: 50,
8-
createIssue: 50,
9-
updateIssue: 50,
10-
updateUser: 50,
11-
deleteIssue: 50,
12-
deleteUser: 50,
3+
/** Response-size-based network simulation used when --network-sim is enabled (default: on).
4+
* Delay per request = baseLatencyMs + ceil(recordCount / recordsPerMs). */
5+
export const NETWORK_SIM_CONFIG = {
6+
baseLatencyMs: 40,
7+
recordsPerMs: 20,
138
};
149

1510
export interface RunProfile {

examples/benchmark-react/src/shared/benchHarness.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ import { captureSnapshot, getReport } from './refStability';
66
import {
77
flushPendingMutations,
88
seedIssueList,
9-
setMethodDelays,
109
setNetworkDelay,
10+
setNetworkSim,
1111
} from './server';
1212
import type { BenchAPI } from './types';
1313

@@ -337,7 +337,7 @@ export function useBenchState() {
337337
captureRefSnapshot,
338338
getRefStabilityReport,
339339
setNetworkDelay,
340-
setMethodDelays,
340+
setNetworkSim,
341341
flushPendingMutations,
342342
setRenderLimit,
343343
...libraryActions,

examples/benchmark-react/src/shared/server.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -136,10 +136,12 @@ export function setNetworkDelay(ms: number): void {
136136
});
137137
}
138138

139-
export function setMethodDelays(delays: Record<string, number>): void {
139+
export function setNetworkSim(
140+
config: { baseLatencyMs: number; recordsPerMs: number } | null,
141+
): void {
140142
worker.postMessage({
141143
id: nextId++,
142-
method: 'setMethodDelays',
143-
params: { delays },
144+
method: 'setNetworkSim',
145+
params: { config },
144146
});
145147
}

examples/benchmark-react/src/shared/server.worker.ts

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,16 @@ declare const self: DedicatedWorkerGlobalScope;
88
// ── NETWORK DELAY ────────────────────────────────────────────────────────
99

1010
let networkDelayMs = 0;
11-
let methodDelays: Record<string, number> = {};
11+
let networkSim: { baseLatencyMs: number; recordsPerMs: number } | null = null;
1212

13-
function respond(id: number, method: string, value: unknown) {
13+
function respond(id: number, _method: string, value: unknown) {
1414
const json = JSON.stringify(value);
15-
const delay = methodDelays[method] ?? networkDelayMs;
15+
const recordCount = Array.isArray(value) ? value.length : 1;
16+
const delay =
17+
networkSim ?
18+
networkSim.baseLatencyMs +
19+
Math.ceil(recordCount / networkSim.recordsPerMs)
20+
: networkDelayMs;
1621
if (delay <= 0) {
1722
self.postMessage({ id, result: json });
1823
} else {
@@ -250,10 +255,14 @@ const methods: Record<string, (params: any) => unknown> = {
250255
seedIssueList: ({ issues }: { issues: Issue[] }) => seedIssueList(issues),
251256
setNetworkDelay: ({ ms }: { ms: number }) => {
252257
networkDelayMs = ms;
253-
methodDelays = {};
258+
networkSim = null;
254259
},
255-
setMethodDelays: ({ delays }: { delays: Record<string, number> }) => {
256-
methodDelays = delays;
260+
setNetworkSim: ({
261+
config,
262+
}: {
263+
config: { baseLatencyMs: number; recordsPerMs: number } | null;
264+
}) => {
265+
networkSim = config;
257266
},
258267
};
259268

examples/benchmark-react/src/shared/types.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,12 @@ export interface BenchAPI {
1717
init(count: number): void;
1818
updateEntity(id: number): void;
1919
updateUser(login: string): void;
20-
/** Set simulated per-request network latency (ms). 0 disables and clears per-method delays. */
20+
/** Set simulated per-request network latency (ms). 0 disables and clears network sim. */
2121
setNetworkDelay(ms: number): void;
22-
/** Set per-method network latency overrides (e.g. { fetchIssueList: 80, fetchIssue: 50 }). */
23-
setMethodDelays(delays: Record<string, number>): void;
22+
/** Enable/disable response-size-based network simulation. Delay = baseLatencyMs + ceil(recordCount / recordsPerMs). Pass null to disable. */
23+
setNetworkSim(
24+
config: { baseLatencyMs: number; recordsPerMs: number } | null,
25+
): void;
2426
/** Wait for all deferred server mutations to settle before next iteration. */
2527
flushPendingMutations(): Promise<void>;
2628
unmountAll(): void;

0 commit comments

Comments
 (0)