Module 6 · Lesson 4 of 4
Presence: who's online
Heartbeats, timeouts and the cost of knowing who's here.
Gatepass wants to show “2,341 people are looking at this show” on hot events, and a green dot next to friends who are browsing the same gig. That's presence: knowing who is here right now. It sounds like a counter. It's really a question about how you notice that someone has left, when leaving usually makes no sound at all.
Leaving is silent
Joining is easy to detect: the phone opens a connection, so the server knows. Leaving rarely announces itself. A fan who closes the app properly sends a goodbye. A fan whose battery dies, who walks into a basement, or whose phone OS freezes the app in the background sends nothing. Their TCP connection may look open on the server for minutes.
So presence can't rely on “disconnect” events. It has to work the other way round: someone is present only while they keep proving it.
Heartbeats and timeouts
Each phone sends a small heartbeat every few seconds while the seat map is open. The server records “user 81 was last seen on event 42 at 10:00:07”. A user counts as present if their last heartbeat is recent enough. If no heartbeat arrives within the timeout, they're gone, whatever the socket says.
The two numbers you pick are a tradeoff you can state precisely:
- Heartbeat interval sets the cost. 100,000 viewers beating every 10 seconds is 10,000 writes a second.
- Timeout sets how wrong you can be. With a 30-second timeout, a fan who left is still shown as present for up to 30 seconds. A timeout shorter than two or three heartbeat intervals marks people as gone because of one late packet on a shaky network, and their dot flickers.
A common choice is a heartbeat every 10–15 seconds and a timeout of 30–45 seconds, so a single lost heartbeat never removes anyone.
Presence is a lease, not a fact. Someone is “here” only until their last heartbeat expires.
Where presence lives
Presence data is hot, small, short-lived and fine to lose. That's exactly what an in-memory store is for. Don't write heartbeats to Postgres: 10,000 updates a second of data nobody needs tomorrow would crowd out the bookings that matter.
Two common shapes in Redis:
- One key per user with a TTL.
SET presence:event42:asha 1 EX 30on every heartbeat. Redis expires the key for you when the beats stop. Checking one friend is a singleEXISTS. Counting everyone means scanning keys, which is slow. - One sorted set per event, scored by last-seen time.
ZADD presence:event42 <now> ashaon every heartbeat. Counting viewers isZCOUNT presence:event42 <now-30s> +inf, and a background job trims old entries withZREMRANGEBYSCORE.
Gatepass uses the sorted set for “people looking at this show” and per-user keys for friends' green dots.
Counting at scale: approximate is fine
The headline number has a different shape from the green dot. Nobody can tell 2,341 viewers from 2,358, so don't pay for exactness:
- Round it. Show “2.3k people are looking” and update it every 5–10 seconds, not on every heartbeat.
- Compute it once. One job counts each event every 5 seconds and publishes the number through the same pub/sub channel as seat updates. One hundred thousand phones read a broadcast, rather than each server counting.
- Estimate huge sets. For unique viewers across a day, a HyperLogLog counts millions of users in about 12 KB with roughly 1% error. Module 19 shows how.
Presence and privacy
“Online now” is personal data. A fan might not want their friends to see they're browsing a particular comedy night at 2 a.m. Make it opt-in, show it only to accepted friends, and show coarse states (“active recently”) rather than exact timestamps. A system design decision here is also a product and trust decision.
Check yourself
Gatepass sends a heartbeat every 10 seconds. Ops sets the presence timeout to 8 seconds to make the viewer count “more accurate”. What happens?
Your turn
Design the “2.3k people are looking at this show” badge for an on-sale with 200,000 viewers across 12 WebSocket servers. What data, which writes, and how does the number reach the phones?
Stuck? Separate the per-user heartbeat path from the per-event count you broadcast.
Key takeaways
- Leaving is usually silent, so presence must be proven with heartbeats and expired with timeouts.
- The heartbeat interval sets the cost; the timeout sets how long a departed user still looks present. Keep the timeout at 2–3 times the interval.
- Store presence in memory (Redis keys with TTLs or a sorted set by last-seen time), never in your main database.
- Viewer counts can be rounded, computed once per event, and broadcast, instead of computed per request.
- Presence fan-out to friends grows quickly, and “online now” is private data. Limit both.
Finished reading?
Mark it complete to tick it off in the course.