ADR-0008 — fakeUserOnline inflates the online counter¶
Status: Accepted Date: 2026-04-16 Author(s): Platform engineering
Context¶
The player-facing UI displays a "live players" counter updated every 10 seconds via the Server.UsersOnlineUpdated websocket event. The real online count comes from ONLINE_USERS_KEY, a Redis sorted set where members are user IDs and scores are epoch-ms of last activity. The set is swept every minute by zremrangebyscore with a 30-second TTL (ONLINE_USER_TTL_SECONDS at apps/rt/src/online-tracker/const.ts:3).
On a quiet environment — local dev, staging, or off-peak production — the real zcard returns single digits. Product requires the counter to "look healthy" rather than show the true value.
Decision¶
OnlineTrackerService at apps/api/src/user/online-tracker.service.ts:18 maintains a fakeUserOnline variable:
- Initial value: 500
- Drift: every 10 seconds,
fakeUserOnline += Math.floor(Math.random() * 10 - 5)— a random walk of −5 to +4 per tick - Floor: clamped to minimum 180 (
line 31-33) - Broadcast: the cron at
line 20(@Evo.Cron(CronExpression.EVERY_10_SECONDS)) emitsUsersOnlineUpdatedwithvalue: zcard(ONLINE_USERS_KEY) + fakeUserOnline
The getOnlineCount() method at line 9 returns the real zcard without inflation. Only the websocket broadcast adds the fake count.
Alternatives considered¶
-
Show the real count. Rejected by product: a counter showing "3 players online" undermines user confidence. The inflation is a deliberate product decision, not an engineering hack.
-
Use a fixed floor (e.g., always add 500). Rejected: a static number looks artificial. The random walk makes the counter wiggle naturally — viewers see it drift up and down, which reads as organic activity.
-
Seed bot users into the
ONLINE_USERS_KEYzset. Rejected: pollutes the real online-user data. Bot entries would appear in admin dashboards, analytics queries, and any code that iterates zset members. Keeping the inflation in the broadcast layer means the underlying data stays clean. -
Server-side rendered count with client-side animation. Rejected: the counter must stay in sync across all connected clients. A server-authoritative broadcast is the simplest way to ensure consistency.
Consequences¶
- Any reliability or scale analysis using the
UsersOnlineUpdatedpayload is measuring inflated data. The real count is only available viaOnlineTrackerService.getOnlineCount()orredis-cli -a cache ZCARD online_users. - The counter never drops below 180, even with zero real users. If a stakeholder asks "why doesn't the count go to zero overnight?" — this is why.
- Grafana panels graphing the websocket-emitted count show the inflated value. To graph real usage, add a Prometheus gauge around
getOnlineCount()or scrape the zset directly. - Bot detection or abuse monitoring that uses the broadcast count will produce false signals. Always use the raw zcard for security or analytics purposes.
- The
enable_coming_soonfeature flag (line 24-26) suppresses the broadcast entirely when enabled — the counter disappears from the UI rather than showing zero.
References¶
apps/api/src/user/online-tracker.service.ts:18—fakeUserOnline = 500apps/api/src/user/online-tracker.service.ts:20-46—notifyOnlineUserscronapps/api/src/user/online-tracker.service.ts:9-11—getOnlineCount()(real, uninflated)apps/rt/src/online-tracker/const.ts:1,3—ONLINE_USERS_KEY,ONLINE_USER_TTL_SECONDSlibs/shared/src/events/api.events.ts:11—Server.UsersOnlineUpdatedevent