Skip to content

Commit a496880

Browse files
committed
No seeding
fix yarn lock
1 parent 58e4f85 commit a496880

File tree

17 files changed

+665
-824
lines changed

17 files changed

+665
-824
lines changed

.cursor/rules/benchmarking.mdc

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ When working on **`packages/react`** or comparing data-client to other React dat
1616

1717
- **Where it lives**: `examples/benchmark-react/`
1818
- **How to run**: From repo root: `yarn build:benchmark-react`, then `yarn workspace example-benchmark-react preview &` and in another terminal `cd examples/benchmark-react && yarn bench`
19-
- **What it measures**: Browser-based mount/update duration, ref-stability counts, sorted-view (Query memoization), optional memory (heap delta), startup metrics (FCP/TBT), and React Profiler commit times. Compares data-client, TanStack Query, SWR, and a plain React baseline.
19+
- **What it measures**: Browser-based init/update duration, ref-stability counts, sorted-view (Query memoization), optional memory (heap delta), startup metrics (FCP/TBT), and React Profiler commit times. Compares data-client, TanStack Query, SWR, and a plain React baseline.
2020
- **CI**: `.github/workflows/benchmark-react.yml` runs on changes to `packages/react/src/**`, `packages/core/src/**`, `packages/endpoint/src/schemas/**`, `packages/normalizr/src/**`, or `examples/benchmark-react/**` and reports via `rhysd/github-action-benchmark` (customSmallerIsBetter). CI runs **data-client only** (hot-path scenarios) to track regressions; competitor libraries (TanStack Query, SWR, baseline) are for local comparison only.
2121
- **Report viewer**: Open `examples/benchmark-react/bench/report-viewer.html` in a browser and paste `react-bench-output.json` to view a comparison table and charts. Toggle "React commit" and "Trace" filters. Use "Load history" for time-series.
2222

@@ -26,8 +26,8 @@ See `@examples/benchmark-react/README.md` for methodology, adding a new library,
2626

2727
Use this mapping when deciding which React benchmark scenarios are relevant to a change:
2828

29-
- **Mount scenarios** (`mount-100-items`, `mount-500-items`, `bulk-ingest-500`)
30-
- Exercises: initial render with pre-populated cache
29+
- **Init scenarios** (`init-100`, `init-500`)
30+
- Exercises: full fetch + normalization + render pipeline (ListView auto-fetches from list endpoint)
3131
- Relevant for: `@data-client/react` hooks, `@data-client/core` store initialization
3232
- All libraries
3333

@@ -58,8 +58,8 @@ Use this mapping when deciding which React benchmark scenarios are relevant to a
5858

5959
| Category | Scenarios | Typical run-to-run spread |
6060
|---|---|---|
61-
| **Stable** | `mount-*`, `update-single-entity`, `ref-stability-*`, `sorted-view-mount-*` | 2–5% |
62-
| **Moderate** | `update-shared-author-*`, `bulk-ingest-*`, `sorted-view-update-*` | 5–10% |
61+
| **Stable** | `init-*`, `update-single-entity`, `ref-stability-*`, `sorted-view-mount-*` | 2–5% |
62+
| **Moderate** | `update-shared-author-*`, `sorted-view-update-*` | 5–10% |
6363
| **Volatile** | `memory-mount-unmount-cycle`, `startup-*`, `(react commit)` suffixes | 10–25% |
6464

6565
Regressions >5% on stable scenarios or >15% on volatile scenarios are worth investigating.

examples/benchmark-react/README.md

Lines changed: 50 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,14 @@ The repo has two benchmark suites:
1111

1212
## Methodology
1313

14-
- **What we measure:** Wall-clock time from triggering an action (e.g. `mount(100)` or `updateAuthor('author-0')`) until the harness sets `data-bench-complete` (after two `requestAnimationFrame` callbacks). Optionally we also record React Profiler commit duration and, with `BENCH_TRACE=true`, Chrome trace duration.
14+
- **What we measure:** Wall-clock time from triggering an action (e.g. `init(100)` or `updateAuthor('author-0')`) until the harness sets `data-bench-complete` (after two `requestAnimationFrame` callbacks). Optionally we also record React Profiler commit duration and, with `BENCH_TRACE=true`, Chrome trace duration.
1515
- **Why:** Normalized caching should show wins on shared-entity updates (one store write, many components update), ref stability (fewer new object references), and derived-view memoization (`Query` schema avoids re-sorting when entities haven't changed). See [js-framework-benchmark "How the duration is measured"](https://github.com/krausest/js-framework-benchmark/wiki/How-the-duration-is-measured) for a similar timeline-based approach.
1616
- **Statistical:** Warmup runs are discarded; we report median and 95% CI. Libraries are interleaved per round to reduce environmental variance.
17-
- **No CPU throttling:** Runs at native speed with more samples (3 warmup + 30 measurement locally, 15 in CI) for statistical significance rather than artificial slowdown.
17+
- **No CPU throttling:** Runs at native speed with more samples for statistical significance rather than artificial slowdown. Small (cheap) scenarios use 3 warmup + 15 measurement runs locally (10 in CI); large (expensive) scenarios use 1 warmup + 4 measurement runs.
1818

1919
## Scenario categories
2020

21-
- **Hot path (in CI, data-client only)** — JS-only: mount, update propagation, ref-stability, sorted-view, bulk-ingest. No simulated network. CI runs only `data-client` scenarios to track our own regressions; competitor libraries are benchmarked locally for comparison.
21+
- **Hot path (in CI, data-client only)** — JS-only: init (fetch + render), update propagation, ref-stability, sorted-view. No simulated network. CI runs only `data-client` scenarios to track our own regressions; competitor libraries are benchmarked locally for comparison.
2222
- **With network (local comparison)** — Same shared-author update but with simulated network delay (consistent ms per "request"). Used to compare overfetching: data-client needs one store update (1 × delay); non-normalized libs typically invalidate/refetch multiple queries (N × delay). **Not run in CI** — run locally with `yarn bench` (no `CI` env) to include these.
2323
- **Memory (local only)** — Heap delta after repeated mount/unmount cycles.
2424
- **Startup (local only)** — FCP and task duration via CDP `Performance.getMetrics`.
@@ -27,20 +27,19 @@ The repo has two benchmark suites:
2727

2828
**Hot path (CI)**
2929

30-
- **Mount** (`mount-100-items`, `mount-500-items`) — Time to mount 100 or 500 item rows (unit: ms).
30+
- **Init** (`init-100`, `init-500`) — Time to show a ListView component that auto-fetches 100 or 500 items from the list endpoint, then renders (unit: ms). Exercises the full fetch + normalization + render pipeline.
3131
- **Update single entity** (`update-single-entity`) — Time to update one item and propagate to the UI (unit: ms).
3232
- **Update shared author** (`update-shared-author-duration`) — 100 components, shared authors; update one author. Measures time to propagate (unit: ms). Normalized cache: one store update, all views of that author update.
3333
- **Update shared author (scaling)** (`update-shared-author-500-mounted`, `update-shared-author-1000-mounted`) — Same update with 500/1000 mounted components to test subscriber scaling.
3434
- **Ref-stability** (`ref-stability-item-changed`, `ref-stability-author-changed`) — Count of components that received a **new** object reference after an update (unit: count; smaller is better). Normalization keeps referential equality for unchanged entities.
3535
- **Sorted view mount** (`sorted-view-mount-500`) — Mount 500 items through a sorted/derived view. data-client uses `useQuery(sortedItemsQuery)` with `Query` schema memoization; competitors use `useMemo` + sort.
3636
- **Sorted view update** (`sorted-view-update-entity`) — After mounting a sorted view, update one entity. data-client's `Query` memoization avoids re-sorting when sort keys are unchanged.
37-
- **Bulk ingest** (`bulk-ingest-500`) — Generate fresh data at runtime, ingest into cache, and render. Exercises the full normalization pipeline.
3837
- **Optimistic update** (`optimistic-update`) — data-client only; applies an optimistic mutation via `getOptimisticResponse`.
3938
- **Invalidate and resolve** (`invalidate-and-resolve`) — data-client only; invalidates a cached endpoint and immediately re-resolves. Measures Suspense boundary round-trip.
4039

4140
**With network (local comparison)**
4241

43-
- **Update shared author with network** (`update-shared-author-with-network`) — Same as above with a simulated delay (e.g. 50 ms) per "request." data-client uses 1 request; other libs use N requests (one per affected item query) to model overfetching.
42+
- **Update shared author with network** (`update-shared-author-with-network`) — Same as above with a simulated delay (e.g. 50 ms) per "request." data-client propagates via normalization (no extra request); other libs invalidate/refetch the list endpoint.
4443

4544
**Memory (local only)**
4645

@@ -57,19 +56,18 @@ These are approximate values to help calibrate expectations. Exact numbers vary
5756

5857
| Scenario | data-client | tanstack-query | swr | baseline |
5958
|---|---|---|---|---|
60-
| `mount-100-items` | ~similar | ~similar | ~similar | ~similar |
61-
| `update-shared-author-duration` (100 mounted) | Low (one store write propagates) | Higher (N cache writes) | Higher (N cache writes) | Higher (full array map) |
62-
| `ref-stability-item-changed` (100 mounted) | ~1 changed | ~1 changed | ~1 changed | ~100 changed |
63-
| `ref-stability-author-changed` (100 mounted) | ~5 changed | ~100 changed | ~100 changed | ~100 changed |
59+
| `init-100` | ~similar | ~similar | ~similar | ~similar |
60+
| `update-shared-author-duration` (100 mounted) | Low (one store write propagates) | Higher (list refetch) | Higher (list refetch) | Higher (list refetch) |
61+
| `ref-stability-item-changed` (100 mounted) | ~1 changed | ~100 changed (list refetch) | ~100 changed (list refetch) | ~100 changed (list refetch) |
62+
| `ref-stability-author-changed` (100 mounted) | ~5 changed | ~100 changed (list refetch) | ~100 changed (list refetch) | ~100 changed (list refetch) |
6463
| `sorted-view-update-entity` | Fast (Query memoization skips re-sort) | Re-sorts on every item change | Re-sorts on every item change | Re-sorts on every item change |
65-
| `bulk-ingest-500` | Normalization pipeline + render | Per-item cache seed + render | Per-item cache seed + render | Set state + render |
6664

6765
## Expected variance
6866

6967
| Category | Scenarios | Typical run-to-run spread |
7068
|---|---|---|
71-
| **Stable** | `mount-*`, `update-single-entity`, `ref-stability-*`, `sorted-view-mount-*` | 2-5% |
72-
| **Moderate** | `update-shared-author-*`, `bulk-ingest-*`, `sorted-view-update-*` | 5-10% |
69+
| **Stable** | `init-*`, `update-single-entity`, `ref-stability-*`, `sorted-view-mount-*` | 2-5% |
70+
| **Moderate** | `update-shared-author-*`, `sorted-view-update-*` | 5-10% |
7371
| **Volatile** | `memory-mount-unmount-cycle`, `startup-*`, `(react commit)` suffixes | 10-25% |
7472

7573
Regressions >5% on stable scenarios or >15% on volatile scenarios are worth investigating.
@@ -84,7 +82,7 @@ Regressions >5% on stable scenarios or >15% on volatile scenarios are worth inve
8482
## Adding a new library
8583

8684
1. Add a new app under `src/<lib>/index.tsx` (e.g. `src/urql/index.tsx`).
87-
2. Implement the `BenchAPI` interface on `window.__BENCH__`: `mount`, `updateEntity`, `updateAuthor`, `unmountAll`, `getRenderedCount`, `captureRefSnapshot`, `getRefStabilityReport`, and optionally `mountUnmountCycle`, `bulkIngest`, `mountSortedView`. Use the shared presentational `ItemRow` from `@shared/components` and fixtures from `@shared/data`.
85+
2. Implement the `BenchAPI` interface on `window.__BENCH__`: `init`, `updateEntity`, `updateAuthor`, `unmountAll`, `getRenderedCount`, `captureRefSnapshot`, `getRefStabilityReport`, and optionally `mountUnmountCycle`, `mountSortedView`. Use the shared presentational `ItemsRow` from `@shared/components` and fixtures from `@shared/data`. The harness (`useBenchState`) provides default `init`, `unmountAll`, `mountUnmountCycle`, `getRenderedCount`, and ref-stability methods; libraries only need to supply `updateEntity`, `updateAuthor`, and any overrides.
8886
3. Add the library to `LIBRARIES` in `bench/scenarios.ts`.
8987
4. Add a webpack entry in `webpack.config.cjs` for the new app and an `HtmlWebpackPlugin` entry so the app is served at `/<lib>/`.
9088
5. Add the dependency to `package.json` and run `yarn install`.
@@ -133,6 +131,44 @@ Regressions >5% on stable scenarios or >15% on volatile scenarios are worth inve
133131
- `BENCH_PORT=<port>` — port for `preview` server and bench runner (default `5173`)
134132
- `BENCH_BASE_URL=<url>` — full base URL override (takes precedence over `BENCH_PORT`)
135133

134+
4. **Filtering scenarios**
135+
136+
The runner supports CLI flags (with env var fallbacks) to select a subset of scenarios:
137+
138+
| CLI flag | Env var | Description |
139+
|---|---|---|
140+
| `--lib <names>` | `BENCH_LIB` | Comma-separated library names (e.g. `data-client,swr`) |
141+
| `--size <small\|large>` | `BENCH_SIZE` | Run only `small` (cheap, full rigor) or `large` (expensive, reduced runs) scenarios |
142+
| `--action <group\|action>` | `BENCH_ACTION` | Filter by action group (`mount`, `update`, `mutation`, `memory`) or exact action name |
143+
| `--scenario <pattern>` | `BENCH_SCENARIO` | Substring filter on scenario name |
144+
145+
CLI flags take precedence over env vars. Examples:
146+
147+
```bash
148+
yarn bench --lib data-client # only data-client
149+
yarn bench --size small # only cheap scenarios (full warmup/measurement)
150+
yarn bench --action mount # init, mountSortedView
151+
yarn bench --action update --lib swr # update scenarios for swr only
152+
yarn bench --scenario sorted-view # only sorted-view scenarios
153+
```
154+
155+
Convenience scripts:
156+
157+
```bash
158+
yarn bench:small # --size small
159+
yarn bench:large # --size large
160+
yarn bench:dc # --lib data-client
161+
```
162+
163+
5. **Scenario sizes**
164+
165+
Scenarios are classified as `small` or `large` based on their cost:
166+
167+
- **Small** (3 warmup + 15 measurement): `init-100`, `update-single-entity`, `update-shared-author-duration`, `ref-stability-*`, `optimistic-update`, `invalidate-and-resolve`, `create-item`, `delete-item`
168+
- **Large** (1 warmup + 4 measurement): `init-500`, `update-shared-author-500-mounted`, `update-shared-author-2000-mounted`, `memory-mount-unmount-cycle`, `update-shared-author-with-network`, `sorted-view-mount-500`, `sorted-view-update-entity`
169+
170+
When running all scenarios (`yarn bench`), each group runs with its own warmup/measurement count. Use `--size` to run only one group.
171+
136172
## Output
137173

138174
The runner prints a JSON array in `customSmallerIsBetter` format (name, unit, value, range) to stdout. In CI this is written to `react-bench-output.json` and sent to the benchmark action.

0 commit comments

Comments
 (0)