BetterSWE DigestWhat is BetterSWE?

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.

Two engineers running an account platform that connects five games to hundreds of millions of players

The setup

Two engineers, five billion-dollar games

Two engineers running an account platform that connects five games to hundreds of millions of players

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

A one-way polling API evolving into a two-way channel through proxy servers

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.

  1. Phase 1 (One-way HTTP API + event queue)
  2. Phase 2 (Two-way channel via proxy fleet)
  3. 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

A single generic pub-sub abstraction standing in for a pile of purpose-built services

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 ones
The whole model: a subscribable topic over a two-layer map

Values 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

Chat messages and a presence card carried as identical tuples

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}
Two features, one data structure
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

A managed ScyllaDB Cloud cluster absorbing a stream of small writes

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
The three criteria the database had to meet

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

Three backend tiers with sharded topics writing through to ScyllaDB

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)
Endpoints post events; routers persist before fanning out

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 feature funnels through the same batched RPC

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

A primary shard with sequence numbers and a passive backup shard, with a client 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. 1.Real-Time Persisted Events at Supercell — Edvard Fagerholm, ScyllaDB Summit 2024
  2. 2.How Supercell Handles Real-Time Persisted Events with ScyllaDB
  3. 3.Real-Time Persisted Events at Supercell | ScyllaDB
  4. 4.Real-Time Persisted Events at Supercell
  5. 5.Supercell Case Study
  6. 6.News × Supercell
  7. 7.Real-time data streaming made simple with Apache Kafka | TO THE NEW Blog
  8. 8.🔥 Streaming Data Architecture: Build Real-Time Systems with Kafka & Stream Processing
  9. 9.A Data Team's Guide to Real-time Analytics for Apache Kafka®
  10. 10.Real-Time Analytics and Monitoring Dashboards with Kafka and Rockset
  11. 11.Medium
  12. 12.AWS re:Invent 2018: Supercell – Scaling Mobile Games (GAM301)
  13. 13.How Supercell uses the Databricks Data Intelligence ...
  14. 14.AWS : Supercell Case Study
  15. 15.Supercell Delivers Dynamic, Low-Latency Games to Millions of Players Using AWS Edge Services
  16. 16.Real-Time Persisted Events at Supercell | PPTX
  17. 17.5 Real-Life Case Studies of Successful Event Stream Processing | RisingWave
  18. 18.How Supercell Handles Real-Time Persisted Events with ScyllaDB — Cynthia Dunlop, ScyllaDB Blog