Screening read benchmark¶
The opt-in ProjectScreeningReadBenchmark compares the existing broad FullStats calculation
against the production materialized screening adapter on the same deterministic corpus in a
single-node MongoDB replica set. It reuses the Phase 0 dataset generator, percentile sampler and
JSON results format. The ordinary test lanes skip it unless SYRF_STATS_DATASET is set.
The authoritative side is deliberately the broad GetFullProjectStatsAsync facet query, because
that is what ProjectScreeningStatisticsQueryAdapter falls back to on main today. A
screening-only source query is a separate, currently unmerged change, so no variant here measures
one.
Run¶
From the PR worktree, with Docker available:
SYRF_STATS_DATASET=PS-DS-02 SYRF_STATS_ITERATIONS=100 SYRF_STATS_WARMUP=10 \
SYRF_STATS_RESULTS_DIR=/tmp/feat024-screening-read-results \
dotnet test src/libs/project-management/SyRF.ProjectManagement.Mongo.Data.Tests/SyRF.ProjectManagement.Mongo.Data.Tests.csproj \
--configuration Release --filter FullyQualifiedName~ProjectScreeningReadBenchmark
Use PS-DS-01 and ten iterations for a harness smoke run; that is not a performance gate sample.
Run the named datasets from the capacity baseline in
separate processes to compare representative shapes. Record the commit, command and machine/load
conditions alongside each emitted JSON artifact. Do not enable flags in a deployed environment to
run this test: its source data, controls, rebuilds and injected Stale state live only in Testcontainers.
What it proves¶
| Variant | Expected authoritative Study aggregations per read | Result |
|---|---|---|
| Broad FullStats | 1 | Unchanged independent screening oracle |
| Missing projection | 1 | Reader refusal reason Missing, then authoritative fallback |
| Fresh projection | 0 | Real snapshot reader and current permissions; lazy delegate never invoked |
| Serving disabled | 1 | Flag-off authoritative fallback; mandatory source checks still apply |
| Stale projection | 1 | Exact fallback, with no stale materialized response |
Fold mode, 0 pending (fold_fresh_0_pending) |
0 | The project's control in fold mode; the real reader with the pending overlay serves the backfilled row |
Fold mode, 32 pending (fold_fresh_32_pending) |
0 | 32 real fold-path screening saves on 32 distinct Studies stay pending; the overlay adds them to the stored row, compared with a recomputed oracle |
The two fold-mode cases are async point-fold acceptance 5
(design, section 9): Fresh reads with 0 and with 32
pending Studies stay within this benchmark's existing p95 gate. Before them the run builds
IX_Study_PendingStatistics through the operator's index operation and puts the backfilled
project's control in fold mode at this binary's protocol, which is the state the staging pilot's
enable-then-rebuild leaves. The saves are the real ProjectScreeningFoldSave over the real
repository write, never folded during the measurement, so every timed read runs the pending probe
and the overlay. The artifact records foldRead0PendingP95Improvement,
foldRead32PendingP95Improvement and their gate flags against the broad read.
Fold-mode run 2026-10-03¶
PS-DS-02 scale 1 (corpus fingerprint unchanged), ten warmups and 100 measured reads per variant,
Release .NET 10, MongoDB 8.0.28, at 75498052f on #3949:
artifact. Exact
parity and the aggregation counts passed for every variant, and neither fold-mode case evaluated the
authoritative query.
| Variant | p50 (ms) | p95 (ms) | p95 below the broad read |
|---|---|---|---|
| Broad FullStats | 866.87 | 1199.80 | baseline |
| Fresh materialized (not fold mode) | 10.17 | 12.25 | 99.0% |
| Fold mode, 0 pending | 11.75 | 13.71 | 98.9% |
| Fold mode, 32 pending | 15.57 | 19.15 | 98.4% |
Both fold-mode cases pass the existing gate (at least 20% below the broad read). The overlay costs about 1.5 ms at p95 with nothing pending (the pending probe and the fold context) and about 7 ms with 32 pending Studies (reading their pending sets and deriving the entries). The host was not idle (one-minute load 35-55 on 48 CPUs during the run); the gate is relative to the broad read measured in the same run, and the margin is two orders of magnitude, so the verdict does not depend on that load, but the absolute milliseconds are indicative only.
Correctness and aggregation counts fail the run when violated: every variant's complete scalar and tally output is compared against the broad-facet oracle, and the Fresh read additionally asserts that the lazy authoritative delegate is never evaluated. Timing does not fail ordinary tests: the artifact records p50/p95 and whether the observed Fresh read p95 improves at least 20% over the broad read. Variants run sequentially; host contention and sample ordering remain possible influences. The artifact includes the corpus fingerprint, server/runtime details and current-row BSON sizes.
The command recorder measures authoritative source commands on the instrumented client only, not every MongoDB round trip. Its zero on a Fresh read means zero authoritative Study aggregates, not zero MongoDB commands: the real materialized reader still loads its controls, authorization, guards, fences and rows inside one snapshot on the uninstrumented context. The latency sample does include that reader work. Rebuild and initial history construction occur outside timed reads.
Activation evidence still required¶
The committed artifact records PS-DS-02 scale 1: 5,000 target studies, ten warmups and 100 measured reads per variant, Release .NET 10, MongoDB 8.0.28, on a shared 48-processor development host without container resource caps. Exact parity and the expected aggregation counts passed. This run measured:
| Variant | p50 (ms) | p95 (ms) |
|---|---|---|
| Broad FullStats | 932.15 | 1077.01 |
| Missing fallback | 912.79 | 969.71 |
| Fresh materialized | 10.14 | 11.97 |
| Serving disabled | 900.63 | 1132.36 |
| Stale fallback | 962.16 | 1516.02 |
The Fresh read is the only variant that avoids the broad aggregation, and its p95 is 98.9% below the broad baseline. Read the other three as one group, not as a ranking: each is the broad query plus a reader refusal, so they scatter around the broad baseline — here from 969.71 to 1516.02 ms p95, some above it and one below — and that spread is uncontrolled shared-host noise around the same aggregation rather than a measured difference between fallback reasons. That is the expected shape: the projection helps only when it is servable, and refusing costs little enough to disappear into host noise.
The corpus fingerprint D0D56518026AAE4659B141C95E6F7D5380FA4084E20A2A618F565BA41F4A56EA is
identical to the Phase 0 PS-DS-02 baselines, so this run measured the same generated corpus.
The recorded aggregation reduction and p95 improvement are both derived from the run's own samples and round-trip counts, not asserted: this run recorded one authoritative aggregate for the broad read and zero for the Fresh read. The current projection was one 2,252-byte BSON row. That excludes history, indexes and source storage, so it is not a capacity acceptance result. Sequential variants and uncontrolled host contention limit the timing comparison; the raw artifact preserves the environment and corpus.
This is development evidence for repository/adapter reads. It does not measure HTTP middleware or
the endpoint's outer authorization, the existing reviewer request, Project Overview polling, or the
legacy SignalR producer's broad aggregation. Those costs must be measured in a controlled endpoint
and page run before claiming an overall workload reduction. In particular the full-stats endpoint
on main is equality-gated — the flag-on path still runs the authoritative aggregation and serves
the stored section only when every value matches — so this read-path improvement is not available
to that endpoint today (follow-up #3311).
The required activation gates remain: at least 20% lower read p95 and 80% fewer authoritative aggregations for the actual consumer workload, less than 10% source-write p95 regression, ½/5/10-reviewer conflict/retry evidence, bounded history/index/storage and rollback proof, followed by separately authorized staging activation and at least seven actual days of soak. A passing correctness run or an observed read improvement here does not close those gates.