Section 5: What Deserves a Disk - Data Modeling
Executive Summary: This section decides where every piece of data lives. Reactions get no database row at all: they exist as a tiny in-memory accumulator in the meeting owner and leave behind only a per-minute rollup for analytics. The raised-hand queue gets the opposite treatment: a single versioned document per meeting, written through to a regional document store with conditional writes before anyone is told it happened. We walk the hand document field by field, separate ver from order, pick the store against three alternatives, and home the supporting records.
The interviewer leans back: "So what's your schema?" The reflex is to draw a reactions table with id, meetingId, participantId, type, createdAt. Resist it. The Staff-level order is access patterns first, then shape, then store, and Sections 2 and 3 already handed us the access patterns:
- Reactions are read exactly once, by the fan-out, within 1 second of being sent. Nobody ever reads them again. Late joiners see nothing from before they joined.
- Hands are read on every join (75K snapshot reads/s at the top of the hour), changed about 17K times a second worldwide, and must survive the death of the process holding them.
One feature has no reader after one second. The other has readers for as long as the meeting runs. That difference decides everything below.
Reactions: Data That Never Lands
Each meeting owner keeps one small accumulator per meeting:
{ "counts": [0, 0, 41, 0, 0], "senders": ["p-maya", "p-..."], "lastFlushAt": "2026-10-29T17:12:03.250Z" }
- counts[5]: like, heart, applause, laugh, surprised. Incremented on every accepted
reaction.send, read and zeroed on every flush. - senders: a ring buffer of at most 10 participant IDs, so a small meeting can show "Maya and 2 others" while a big one just shows a number.
- lastFlushAt: what makes Section 7's leading-edge throttle work. If nothing has gone out in the last 250 ms, flush now; otherwise wait for the next tick.
Beside it sits a per-participant token bucket (two numbers: tokens left and last refill time) enforcing 10 counted reactions per second. Five counters plus ten 16-byte IDs is a few hundred bytes per meeting, so even at ~10K meetings per owner node the whole reaction state is a few megabytes. If the owner crashes, it is all lost, and that is fine: in-flight reactions were worthless a second later anyway.
The Pushback:
Interviewer: "Surely you want to store reactions. What about analytics?"
You: "Writing a row per reaction means 50K writes a second at peak and 200K in a burst, for data with no reader, and nobody will ever read those rows. What the organizer actually wants is 'applause spiked at 10:12', and that's an aggregate. So the owner emits one ReactionRollup per meeting-minute, asynchronously, to the analytics stream. If the stream is down, we drop the rollup rather than slow the meeting."
{ "meetingId": "mtg-7f3a", "minute": "2026-10-29T17:12Z", "counts": [12, 30, 1843, 7, 4] }
At about 50 bytes a row, one row per meeting-minute gives at most 87.5 GB/day, or 2.6 TB over 30 days. That is the upper bound, because a quiet minute writes nothing. Its flip trigger is explicit: if compliance demands it, or Section 11's "reactions in the recording" arrives, reactions need durable timestamps on a different path.
Quick check before moving on:
Sign in to continue reading
The rest of this lesson is available with a free account. Signing in with Google or Microsoft is free.
Sign in to read the full lesson