MemberJunction / MemberJunction/MJ
Scaled integration tests + performance baseline for cache-invalidation broadcast fan-out (mock browser endpoints)
- Dominant language
- TSQL
- Stars
- 29
- Forks
- 6
- Avg merge
- 2d 1h
- Merged PRs (30d)
- 323
Description
## Why
#4250 documents that every entity save broadcasts one cache-invalidation message to every connected browser, with no grouping per unit of work — a bulk write like enabling field-level security (~84 permission rows on `MJ: Employees`) emits ~84 messages × N clients. The proposed fix is transaction-scoped batching of the transport. Before and after that lands, we need **numbers, not estimates**: at MJ's typical scale (hundreds to low thousands of connected clients) the current overhead may well be tolerable — which changes how aggressively #4250 needs to be scheduled — and once batching exists, its effect should be measurable, not asserted. Suggested by Amith in the design discussion around #4250.
## What
Additions to the deterministic/mutation integration suite (`@memberjunction/integration-test-suite`) that exercise the broadcast path at scale against a live MJAPI:
1. **Mock "browser" endpoints** — a fixture that opens N GraphQL `cacheInvalidation` subscriptions over WebSocket (N configurable; start with ~50–200 in CI, higher for local perf runs). Each mock client counts messages, bytes, and timestamps; none of them needs to be a real browser — the subscription contract is the surface under test.
2. **Bulk-write drivers** — trigger representative units of work and measure the fan-out:
- a multi-row entity-layer burst (the FLS flag flip is a ready-made ~84-row driver, or any `RunInEntityTransaction` loop);
- a `TransactionGroup` submit (the batched-dashboard shape);
- a single save (the baseline unit).
3. **Assertions + recorded metrics**:
- correctness invariants: every connected client receives every invalidation (today: N×rows messages; post-#4250: one batch per unit of work carrying all per-record payloads);
- performance recorded in `TestRun.ResultDetails` as trend data (messages/client, total bytes, wall time from first save to last delivery), the same visibility-not-gating posture as IT87's FLS21 refresh-cost check — no wall-clock pass/fail beyond generous sanity ceilings.
4. **Config kill-switch coverage** (once #4250's configuration option to disable browser broadcasting exists): with broadcasting off, clients receive nothing and reconverge via the smart cache checks — pin that the escape hatch actually disconnects the firehose without breaking convergence.
## Notes for implementation
- The suite already has the patterns to reuse: client-transport bundles gate on MJAPI liveness and skip-as-pass when it's down; secondary `GraphQLDataProvider` connections exist in the FLS client bundle (watch the hard-singleton trap documented there); mutation-gated tier for the write drivers.
- Related: #4250 (the batching design this measures), #3242 (unfiltered `RecordData` on the same surface — a batch message shape interacts with the permission-filtering redesign), #2125 (scoped notifications umbrella).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Contributor guide
Research direction
Start in the deterministic/mutation integration suite in @memberjunction/integration-test-suite, reading the client-transport bundles, the FLS client bundle, and IT87's FLS21 refresh-cost check. Trace cacheInvalidation subscriptions, RunInEntityTransaction, TransactionGroup, and TestRun.ResultDetails against a live MJAPI. Done means scaled mock clients cover the listed write paths, record trend metrics without tight timing gates, and verify the broadcast kill-switch and reconvergence behavior once #4250's option exists.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- graphql
- Domain
- api, backend, performance, testing-qa
- Issue type
- Feature
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 52/100