Data Architecture
Offline Sync Strategy: Single-Owner Write Model
Core Principle
Every score partition has exactly one authoritative writer at any given time. This eliminates multi-writer sync conflicts entirely by design.
Scenarios
Solo Shooter A single shooter uses their own app instance and tracks their own scores. One device, one writer. No sync issues.
Small Group (e.g. church security team, friends) One person has ownership of the event and is responsible for entering and managing all scores. One device, one writer. No sync issues.
Instructor with Students The instructor owns the event, enters all scores on their device. One device, one writer. No sync issues.
Large Event with Multiple ROs Each RO is assigned a specific set of shooters (relay/lane). Each RO is the sole writer for their group. Separate partitions, no cross-RO conflicts. No sync issues.
Ownership and Authority
- The event owner is always the authority on scores.
- The owner may delegate to ROs, each of whom owns their assigned group of shooters.
- Shooters do not self-report scores in organized events — the owner/RO is the single source of truth.
Data Characteristics
All Data is Minimal
Every data structure in the system — both read-down and write-up — should be designed to be as small as possible. With a strict schema and efficient serialization, all packets stay compact. This has compounding benefits: fast sync on poor connections, low storage on-device, and minimal bandwidth costs at scale.
Read-Down: Event Structural Data
Drill definitions, course structure, roster, and event configuration are downloaded to devices before the event starts. This data is also small — drills are structured fields (text, enums, numbers, scoring parameters), not large binary blobs. These structures are defined as protobuf messages (see Protobuf Schema Strategy) and generated into typed code for all platforms. A drill definition might be 200-500 bytes. A full event with 20 drills, a roster of 30 shooters, and course configuration is still likely under 50 KB.
Images (drill diagrams, target layouts) are the exception — these are larger, but can be cached independently and fetched ahead of time. They don’t need to be part of the core sync payload.
Once an event begins, this structural data is locked and immutable. Devices only create new score entries while offline.
Write-Up: Score Data
A score entry is roughly 100-200 bytes (defined as a protobuf Score message). An entire event’s worth of scores is a few KB at most.
Append-Only Scores
Scores are never edited, only appended. If a correction is needed, a void/correction entry is submitted — the original is never modified. This makes sync a simple queue flush with no merge logic.
Edge Cases and Mitigations
1. Roster Changes Mid-Event
A shooter arrives late or leaves early. Since roster edits belong solely to the event owner and queue the same way as scores (single writer), this stays clean.
2. Wrong Shooter Scored
RO records a score under Shooter A but meant Shooter B. Since scores are append-only, the RO submits a void flag on the incorrect entry and creates a new correct entry. Both are attributed to the same RO.
3. Device Swap Mid-Event
RO’s phone dies; they log in on a new device and continue. The old device still has unsynced scores in its local queue. When the old device eventually recovers and comes back online, its queued scores flush and merge fine (append-only, no conflict). To guard against the RO re-entering scores on the new device that were already captured on the old one, a composite deduplication key can be used:
event_id + drill_id + shooter_id + timestamp
4. Multi-Day Events
An instructor scores across multiple sessions over several days. As long as the owner stays the same and scores keep appending, this works. If the instructor hands off to a co-instructor on a later day, a clean ownership transfer or co-owner concept is needed.
5. Shooter in Multiple Contexts Simultaneously
A shooter could be enrolled in an instructor’s class and also tracking personal drills on their own device. Both are writing scores for the same person but in separate event/org contexts. Scores are scoped to their event/org, so there is no conflict.
6. Single Point of Failure
If the owner’s device dies mid-event, unsynced scores are unavailable until the device recovers. Mitigations:
- Opportunistic background sync whenever the device briefly gets signal.
- Minimal data size means sync completes quickly even on poor connections.
- This mirrors the real-world risk of a lost paper scorecard — acceptable tradeoff for the simplicity gained.
Architectural Benefits of This Model
- No CRDTs, event sourcing, or operational transforms needed — eliminates an entire category of complexity and bugs.
- Trivial sync implementation — local queue flush, no diffing or merging.
- Matches physical reality — one RO per lane/relay, one instructor per class.
- Simple mental model for users — “this is my scorecard, I own it.”
- Scales naturally — adding more ROs just adds more independent write partitions.