Conversation
|
A preview of 8c15319 is uploaded and can be seen here: ✨ https://revisit.dev/study/PR1311 ✨ Changes may take a few minutes to propagate. |
…-startup-performance # Conflicts: # src/storage/tests/highLevelSupabase.spec.ts # src/storage/tests/primitivesSupabase.spec.ts
There was a problem hiding this comment.
Pull request overview
This PR optimizes normal participant startup by replacing repeated sequence-assignment collection scans with bounded, race-safe allocation using stable sequenceIndex and creationIndex metadata across Firebase, Supabase, and LocalStorage. It also reduces redundant reads by reusing prefetched sequence/modes data, switching completion checks to a single assignment lookup, and skipping stored-config downloads when the active config hash is unchanged.
Changes:
- Add atomic/bounded sequence-assignment allocation (
sequenceIndex,creationIndex) with legacy compatibility and rejected-slot reuse across providers. - Reduce startup I/O by reusing prefetched modes + sequence array in
Shell, switching completion to a single assignment lookup, and narrowing Supabase verification reads. - Add/extend contract and concurrency tests to lock in “no scans” behavior and uniqueness under concurrent starts.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| src/tests/utils.ts | Extends the test storage engine with the new allocation primitive. |
| src/storage/tests/primitivesSupabase.spec.ts | Enhances Supabase mock query builder to support new bounded operations used by allocator logic. |
| src/storage/tests/primitivesFirebase.spec.ts | Extends Firestore mock surface (query/count/transaction) needed by new allocator paths. |
| src/storage/tests/highLevelSupabase.spec.ts | Adds high-level Supabase tests for “no scans”, legacy compatibility, and concurrency uniqueness. |
| src/storage/tests/highLevelFirebase.spec.ts | Adds high-level Firebase tests for “no scans”, legacy compatibility, and concurrency uniqueness. |
| src/storage/tests/highLevel.spec.ts | Adds provider-agnostic tests for no-scan startup, bootstrap reuse, and concurrency behavior. |
| src/storage/engines/types.ts | Introduces allocator metadata/types and bootstrapping data plumbing; updates startup and completion logic. |
| src/storage/engines/SupabaseStorageEngine.ts | Implements Supabase allocator (CAS/version counter), legacy index derivation, and bounded verification/lookup behavior. |
| src/storage/engines/LocalStorageEngine.ts | Implements serialized local allocation and updates rejection handling for reusable slots. |
| src/storage/engines/FirebaseStorageEngine.ts | Implements Firestore transaction-based allocator, legacy index derivation via count aggregates, and bounded completion lookup. |
| src/components/tests/Shell.spec.tsx | Asserts Shell passes prefetched modes + sequence array into participant initialization. |
| src/components/Shell.tsx | Reuses the prefetched sequence array and passes bootstrap data into participant initialization. |
Comments suppressed due to low confidence (1)
src/storage/engines/FirebaseStorageEngine.ts:322
- The reusable rejected assignment lookup uses
limit(1)without an explicit sort. Firestore does not guarantee any stable order here, so this can change which rejected slot is reused and break the existing "oldest rejected first" behavior.
getDocs(query(
sequenceAssignmentCollection,
where('rejected', '==', true),
where('claimed', '==', false),
limit(1),
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…-startup-performance # Conflicts: # src/components/Shell.tsx

Summary
This PR improves study-startup performance and sequence-assignment correctness.
It removes participant-history-dependent collection scans from normal startup, eliminates redundant startup reads and large object downloads, and assigns stable sequence and participant indexes using provider-specific concurrency controls. Once the allocator is initialized, the amount of assignment work performed for a new or returning participant is bounded and does not grow with the number of prior participants.
Closes #1176.
Performance changes
sequenceIndexandcreationIndexvalues on new assignments so returning participants can use a point read.The important bound is on client-visible database work: after allocator initialization, normal assignment startup uses a fixed number of point reads, bounded queries, and writes rather than downloading work proportional to participant history. This does not impose a literal wall-clock timeout—authentication, provider latency, retries, and network throughput can still vary—and Firestore transactions may produce more raw HTTP protocol entries even while eliminating redundant logical reads and unbounded document downloads.
Measured effect
For CALVI with approximately 33 assignments, cache-disabled Firefox HARs showed:
devdownloaded the approximately 3.35 MB sequence array twice per load.Cache-Control: private, max-age=0, so the seconddevdownload was not expected to become a browser-cache hit when cache was enabled.devversus 4.17 seconds on the PR. At roughly 33 assignments, the removed collection scans were inexpensive and the bounded Firebase transaction replaced much of the saved elapsed time.The immediate benefit is therefore lower transferred data and less duplicate parsing, especially on cold or mobile connections. The database benefit grows with participant history because normal startup no longer downloads the complete assignment collection.
Config-change startup still regenerates and uploads the expanded sequence array; that work is intentionally deferred to the follow-up issues listed below.
Correctness changes
Automated tests start 12 participants concurrently for Firebase, Supabase, and local storage and verify unique sequence and creation indexes. Provider tests also cover fresh startup, initialized allocators, legacy returning assignments, legacy rejected-slot reuse, oldest rejected-slot selection, chained replacement rejection, and participant-specific completion lookup.
Correctness caveats
Firebase deployment requirement
This PR introduces two Firestore compound queries that were not required by the previous full-scan implementation. Firebase's existing automatic single-field indexes, and any unrelated composite indexes already in a project, do not replace them.
Each Firebase project/database must create these collection-scoped composite indexes once:
rejectedascendingtimestampascendingrejectedascendingclaimedascendingtimestampascendingThe first index is used only when deriving the position of a legacy assignment without stored allocator metadata. The second query runs during fresh Firebase participant allocation even when no rejected assignment is found, so a fresh Firebase project must create that index before its first participant starts.
These indexes are required once per Firebase project/database, not once per study. They apply to the shared
sequenceAssignmentcollection scope and serve dynamically namedprod-{studyId}anddev-{studyId}study collections. If an index already exists in a test project, it may have been created while testing this PR and remains available regardless of which application branch is deployed.When Firestore reports a missing index, the application shows the exact Firebase-generated creation link, explains that setup is a one-time action for the study owner, and provides open/copy controls.
Follow-up work
Validation
yarn unittest --run— 1,980 passed, 1 skippedyarn typecheckyarn lintyarn build