Presence Tracking, Kept Deliberately Coarse
A stateless heartbeat model instead of per-instance memory — the same multi-instance lesson that already retired three multiplayer games.
Alongside today's stats rebuild, the site now tracks who's actively on it right now — a genuinely different signal from historical visit counts, and one I had to think through carefully before building, since "who's currently here" sits a lot closer to something that could feel invasive if I got the framing wrong.
Live presence and historical visits answer completely different questions
Today's other change, per-day visit tracking, answers "how much activity happened on a given day, after the fact." Presence answers "who's here right now," which is a live, momentary signal rather than a historical record. Those needed genuinely separate systems rather than one derived from the other — you can't reconstruct a meaningful sense of concurrent live presence from daily aggregate counts, and you shouldn't try to build a historical record out of momentary presence pings either, since the two are answering different timescales of the same underlying question about site activity.
A heartbeat model, not a persistent connection
Rather than holding an open, persistent connection per visitor to know who's currently active, presence works through periodic heartbeat pings — a lightweight, recurring signal that says "I'm still here," with absence of a recent heartbeat implying someone's left. That's a much simpler and more scalable model than maintaining a live connection per visitor, especially across a site running multiple server instances, where a persistent per-visitor connection would need to be tracked and reconciled across instances in a way a simple, stateless heartbeat entirely avoids needing to do.
The heartbeat interval itself is a trade-off between accuracy and cost
A shorter heartbeat interval gives a more accurate, up-to-the-moment read on who's actually here right now, at the cost of more frequent requests hitting the backend from every single active visitor simultaneously. A longer interval is cheaper to run but means presence data is staler at any given instant, potentially still showing someone as active for a stretch after they've actually left. I picked an interval that felt like a reasonable middle point between those two costs, favoring efficiency slightly over perfect real-time accuracy, since a presence feature this ambient doesn't need second-by-second precision to still do its actual job well.
Deciding what "active" should mean took real judgment, not just a timer
Someone who stops sending heartbeats hasn't necessarily left — a backgrounded tab, a brief network hiccup, or a phone screen locking can all interrupt heartbeats temporarily without the person actually being gone. I had to pick a timeout window generous enough to absorb normal, brief interruptions without falsely showing someone as inactive, while still being tight enough that presence reflects something reasonably current rather than showing someone as "active" long after they've genuinely closed the tab and moved on. That's a judgment call more than a technical problem, and I erred toward a slightly longer window rather than a stricter one, since a presence indicator that's occasionally a little stale is a smaller problem than one that flickers between active and inactive on every minor connectivity hiccup.
A stale presence signal fails safely; a wrong one wouldn't
I thought about what the actual failure mode looks like if the timeout window or heartbeat interval turns out to be tuned slightly wrong in either direction, and it matters that the failure here is gentle. A presence count that's a little stale, showing someone as active for a bit longer than they actually were, is a minor, harmless inaccuracy. It's not the kind of failure that misleads anyone into a bad decision or exposes information that shouldn't be exposed. That gentle failure mode is part of why I was comfortable shipping a reasonable first guess at the timing rather than treating it as something that needed to be perfectly correct before this could go live at all.
Multiple server instances made a naive approach genuinely wrong, not just inelegant
This site runs across multiple server instances, which is exactly the same constraint that forced Mafia, Hidden Artist, and Dual Fishy into retirement back in August — in-memory state tied to one specific server instance doesn't survive or stay consistent once traffic can land on a different instance from one request to the next. A naive presence implementation that tracked active visitors in memory on whichever instance happened to handle their heartbeat would produce a different, inconsistent answer depending on which instance you asked, which is the exact failure mode that already killed three other features on this site. Building presence around shared, externally stored state from day one, rather than in-memory tracking per instance, meant this didn't repeat that same mistake a third time.
Why I kept this deliberately coarse rather than granular
I specifically didn't build this to show granular detail like exactly which page someone's currently on or how long they've been there — that level of detail starts crossing from "the site has some activity right now" into something that reads as tracking a specific person's behavior in real time, which isn't something I want any part of this site doing even in service of a reasonable-sounding feature. Presence here answers one coarse, aggregate question — is anyone actively here — and deliberately stops well short of anything that would make a visitor reasonably uncomfortable if they thought carefully about exactly what was being tracked about them while they browsed.
This is a small feature by line count, and I spent a disproportionate amount of that time on restraint rather than capability — deciding what not to build was the actual design work here, more than the heartbeat mechanism itself. A "who's online" feature can very easily slide from a fun ambient signal into something that feels like surveillance depending entirely on how much detail it exposes, and I'd rather this stay firmly on the fun-ambient-signal side of that line even if it means the feature does noticeably less than it technically could have, given more detailed data than I ever actually chose to collect about anyone in the first place.
More from the Devlog