System Design · Issue 5
How 2 Engineers Built Supercell's Cross-Game Social Backend
Chat, presence, and friends for five billion-dollar games, run by two people on a single abstraction.

The setup
Two engineers, five billion-dollar games

Supercell ID is the account layer behind Hay Day, Clash of Clans, Boom Beach, Clash Royale, and Brawl Stars, five games that have each earned over $1B. It handles account recovery, friend requests, cross-game promotion, chat, presence, and teaming up for hundreds of millions of players. The entire cross-game social backend was built and run by two engineers.
Supercell ID
- Chat
- Presence
- Friends
- Teaming up
- Cross-promo
That team size drives the whole design. Two people cannot maintain a bespoke chat service, a separate presence service, a notification service, and a friend-graph service, each with its own storage, scaling story, and on-call rotation. The system had to stay small enough for one engineer to hold in their head.
How it grew
From a recovery API to a two-way channel

Supercell ID began as a plain account-recovery and device-migration tool. A client hit an HTTP API, got back signed tokens, and presented them to game servers. Async notifications like a friend-request approval rode a simple event queue: the API posted an event, and the game backend polled it and forwarded it to the client over the existing game socket. One direction, low volume, nobody watching the latency.
When Edvard Fagerholm joined in late 2020 to work on cross-promotion across the five games, he needed something the game socket couldn't give him: two-way communication independent of any single game. So clients started connecting to a fleet of proxy servers, and a routing layer pushed events straight back to them. Still modest, just enough for cross-promo and friend requests.
- Phase 1 (One-way HTTP API + event queue)
- Phase 2 (Two-way channel via proxy fleet)
- Phase 3 (Absorb social features from games)
That channel turned out to be far more general than its first use. Friend graphs, teaming up, chat, and presence, all the social features normally baked into each game server, could live on this shared channel instead. Pulling them into Supercell ID meant any new game in development would get them for free, which is what accelerated the game teams.
The core decision
One abstraction instead of five systems

The goal was stated up front: one simple abstraction, not a pile of purpose-built systems. What they landed on is a hierarchical key-value store with change data capture. Top-level keys are topics, and topics are subscribable. Under each topic sits a two-layer map, map(string, map(string, string)).
Change Data Capture (CDC) — Every mutation to the store is emitted as an event. Here, writing under a topic automatically fans the change out to everyone subscribed to that topic.
One rule does all the work: subscribe to a topic, receive its mutations. Chat, presence, and friend requests all reduce to exactly that, so there is no per-feature code path in the routing layer.
topic (subscribable)
|
+- map(string -> map(string -> value))
|
stamped by its source;
client drops stale onesValues in the innermost map are timestamped by whatever produced them, since each source knows its own correct ordering. A client that receives an update older than what it already holds simply throws it away. Reconciliation is last-write-wins, done on the client, which keeps the server out of the ordering business entirely.
What the data looks like
Chat and presence in the same shape

A chat room is a topic. The room ID is the top-level key, so subscribing to it means subscribing to the room. The second-level key is a timestamp-UUID, which gives each message an order and makes history queryable. The inner map is the payload: message text, metadata, reactions.
Presence, used heavily in their newer game mo.co, has the same shape. The player ID is the topic, a fixed key presence holds one entry, and its value carries weapon, level, and status like in battle. In the teaming-up menu you watch a friend's build and status change in real time.
CHAT
<room ID> topic
+- <timestamp-uuid> message order
+- {message, metadata, reactions}
PRESENCE
<player ID> topic
+- "presence"
+- {weapon: sword, level: 29, status: in battle}Chat and presence are the same data structure with different strings inside it.
Supercell ID never interprets any of this. To the routing and storage layer the values are opaque strings it forwards and stores. Clients don't need the friend graph either, since routing is entirely Supercell ID's job.
Picking the database
Why ScyllaDB Cloud

The data pattern is a lot of small writes, single-field changes like level: 10 → 11, streaming in as players act in-game. That workload plus the two-person team set three hard requirements for the database.
requirement why it mattered -------------------------------------------------------- many small writes, low latency level: 10 -> 11, all day hierarchical data model topic -> key -> key -> value managed backups + cluster ops no infra team of its own
They picked ScyllaDB Cloud, the fully managed offering. It handles many small low-latency writes, models the topic → key → key → value structure cleanly, and runs backups and cluster operations as a service. That last point mattered most: with no dedicated database or infra team, the ops burden had to be near zero.
The request path
APIs, proxies, and event routers

The backend splits into three tiers: APIs, proxies, and event routing/storage servers. Topics live on the event routing servers and are sharded across them. A client connects to a proxy, the proxy owns that client's subscriptions, and it routes each subscription to the routing server holding that topic's shard.
client --> proxy --> event router --> ScyllaDB
subs (topic shard) persist first
^
endpoints --------------+
(chat, presence) post-event(batch)Feature endpoints feed data in through a single RPC, post-event, taking a batch of (topic, type, key, value) tuples. Each feature's only job is to rewrite its own domain data into that generic shape. This one contract is what lets a single engineer add features without adding systems.
post-event([
(topic, type, key, value),
(topic, type, key, value),
...
])Every event is persisted to ScyllaDB before it is broadcast. The API is synchronous in a strict sense: a success response guarantees the write already reached the database. If the API said OK, the data is durable, which makes client state easy to reason about.
Staying correct
Ordering at the edge, failover with a refresh

Each topic has a primary shard and a backup shard. The primary tracks per-message sequence numbers in memory, used to spot lost messages. The backup just forwards messages without sequence numbers while it sits passive, so it does no gap detection of its own.
Primary shard
tracks sequence numbers, detects gaps
Backup shard
forwards without sequence numbers
When a failed primary comes back, it doesn't try to stitch the sequence back together seamlessly. It resets the sequence numbers and triggers a client-side state refresh. That is an explicit reconciliation step, chosen over the machinery it would take to keep sequence state perfectly consistent across replicas.
Because applying an update on the client is idempotent, sending the same event twice is harmless, which is what makes the refresh cheap. There is a known rough edge: under some failure scenarios multiple sequence numbers can map to the same message, a caveat in the dedup logic they have not fully closed.
The bottom line
Small team, small surface
Why it matters
A social backend for hundreds of millions of players across five billion-dollar games runs on two engineers because Supercell treated maintenance simplicity as the primary constraint, collapsing chat, presence, friends, and teaming into one pub-sub-over-key-value model with CDC. When a team is that small, a single generic abstraction that every feature rewrites itself into can beat a shelf of specialized services, even where the specialized version would be faster or cleaner in isolation.
The short version
- Supercell ID's cross-game social backend, chat, presence, friends, and teaming, was built and run by **just 2 engineers**.
- Everything reduces to one abstraction: a hierarchical key-value store with **CDC**, where topics are subscribable and any change broadcasts to subscribers.
- Features send data through a single `post-event` RPC of `(topic, type, key, value)` tuples, and the storage layer never interprets the payload.
- **ScyllaDB Cloud** was picked for many small low-latency writes, a hierarchical model, and near-zero ops burden.
- Ordering is pushed to the client (timestamps plus last-write-wins) and failover uses a state refresh, trading seamless continuity for simplicity.
Sources
- 1.Real-Time Persisted Events at Supercell — Edvard Fagerholm, ScyllaDB Summit 2024
- 2.How Supercell Handles Real-Time Persisted Events with ScyllaDB
- 3.Real-Time Persisted Events at Supercell | ScyllaDB
- 4.Real-Time Persisted Events at Supercell
- 5.Supercell Case Study
- 6.News × Supercell
- 7.Real-time data streaming made simple with Apache Kafka | TO THE NEW Blog
- 8.🔥 Streaming Data Architecture: Build Real-Time Systems with Kafka & Stream Processing
- 9.A Data Team's Guide to Real-time Analytics for Apache Kafka®
- 10.Real-Time Analytics and Monitoring Dashboards with Kafka and Rockset
- 11.Medium
- 12.AWS re:Invent 2018: Supercell – Scaling Mobile Games (GAM301)
- 13.How Supercell uses the Databricks Data Intelligence ...
- 14.AWS : Supercell Case Study
- 15.Supercell Delivers Dynamic, Low-Latency Games to Millions of Players Using AWS Edge Services
- 16.Real-Time Persisted Events at Supercell | PPTX
- 17.5 Real-Life Case Studies of Successful Event Stream Processing | RisingWave
- 18.How Supercell Handles Real-Time Persisted Events with ScyllaDB — Cynthia Dunlop, ScyllaDB Blog