Loading...

How Discord Actually Works

A deep dive into Discord's architecture, why one server's crash never touches another thanks to Elixir's Guild Process model, and the eight-year database saga from MongoDB to Cassandra to a custom Rust-powered ScyllaDB setup storing trillions of messages.

By udthedeveloper•12 min read•
14 views
How Discord Actually Works

Every Discord server, from a two-person group chat to a community with hundreds of thousands of members, runs as its own isolated little universe behind the scenes. A massive gaming server having a meltdown during a big announcement doesn't slow down, crash, or even slightly affect the tiny study group server three friends set up last week. That isolation isn't a coincidence. It's the direct result of a specific, deliberate choice about how to model a "server" in code, made early on and never really abandoned since.

This article looks at two of the biggest engineering stories inside Discord: the process model that keeps millions of independent communities from ever stepping on each other's toes, and the genuinely dramatic, twice-repeated story of outgrowing a database at massive scale first MongoDB, then Cassandra, and eventually a custom-tuned setup built around ScyllaDB and Rust. A good amount of this comes straight from Discord's own engineering blog, with independent technical breakdowns filling in the surrounding detail.

Illustration of many independent Discord servers running in isolation

One Server, One Process: The Guild Process Model

Here's the foundational idea that shapes almost everything else about how Discord behaves under load. A breakdown on TechAhead explains that Discord's real-time layer runs on Elixir, letting the platform isolate every single server internally called a "Guild" as its own lightweight process running on the BEAM virtual machine. Because each server gets its own dedicated process, one busy or crashing server never impacts the millions of other connected communities running alongside it.

A large apartment building where every unit has its own separate electrical circuit captures the idea well a short circuit in one apartment trips that unit's breaker and nothing else, rather than plunging the entire building into darkness. Discord's Guild Process model works the same way for servers: an overloaded process handling one massive, chaotic community can crash and restart cleanly, without the millions of other, smaller Guild Processes running elsewhere on the same infrastructure ever noticing.

Server A (huge, active) → its own Guild Process → handles a crash spike
Server B (small, quiet) → its own Guild Process → completely unaffected
Server C (medium)       → its own Guild Process → completely unaffected
 
One process crashing and restarting never touches the others
Diagram of isolated Guild Processes preventing cascading failures

When Elixir Wasn't Fast Enough on Its Own, They Reached for Rust

Here's a genuinely interesting technology decision, and a good lesson in knowing exactly which tool to reach for and when. Elixir's BEAM virtual machine is superb at massive concurrency juggling millions of lightweight, isolated processes at once but it isn't naturally built for raw, CPU-intensive number-crunching. The same TechAhead piece explains what happened when Discord hit that ceiling: rather than abandoning Elixir, they extended it with Rust through NIFs, or Native Implemented Functions, keeping Elixir in charge of concurrency while handing the genuinely CPU-heavy work serialization and permission checks over to Rust.

A guide on ggprompts's Architecture Map gives a concrete, striking example of exactly how much this mattered: solving what the same source calls the "member list sorted insertion problem" keeping a sorted, correctly-ordered member list up to date as members are constantly added, removed, and reordered using Rust via a tool called Rustler achieved a 160x improvement over pure Elixir at the same scale. The same source reports that response times for this specific task dropped to microseconds, per-node capacity increased to 8 million "Read States," and the latency spikes that used to occur under load were eliminated entirely.

A kitchen where one chef handles taking orders and coordinating the whole dining room brilliantly, but hands off any task requiring genuine heavy lifting butchering a large cut of meat, say to a specialist brought in specifically for that one job. Elixir keeps orchestrating everything, but the specific, narrow, computationally expensive task gets handed to the tool actually built for raw speed.

Diagram of Elixir handling concurrency while Rust handles CPU-intensive tasks

The Database Story: Outgrowing MongoDB, Then Outgrowing Its Replacement Too

This is genuinely one of the most dramatic infrastructure stories in this entire series, and Discord's own engineering blog tells it in detail. A breakdown on Sujeet Jaiswal's engineering blog frames it as a case study in outgrowing databases twice first as a single MongoDB replica set, then as a Cassandra cluster, and eventually landing on a heavily customized ScyllaDB setup, evolving across roughly eight years as message volume climbed from millions into the trillions.

A write-up on Saifeddine Rajhi's blog fills in exactly where the first breaking point hit: Discord started in 2015 on a single MongoDB replica set, and by that November just months after launch message volume had already reached 100 million, and the data and its index had grown too large to fit comfortably in available memory, causing real slowdowns. That first migration moved Discord onto Cassandra, a distributed database explicitly designed to handle exactly this kind of write-heavy, ever-growing dataset across many machines rather than one.

The Second Wall: When Cassandra's Own Design Became the Bottleneck

Cassandra held up for years, but it eventually ran into its own, different kind of ceiling. The same TechAhead piece explains the core issue directly: Cassandra's Java-based garbage collection process, which periodically pauses to clean up unused memory, started causing real, unpredictable latency issues once Discord's message volume reached the trillions. A separate breakdown on Medium by Adarsh Gupta adds a second, related problem: Cassandra's architecture, while genuinely strong at handling data writes, struggled with data retrieval especially when many users tried accessing the same busy conversation simultaneously, creating what's known as a hot partition, where one specific piece of data gets so much simultaneous traffic it becomes its own bottleneck.

A single, extremely popular cash register at a busy store, with four other registers sitting comparatively idle nearby, simply because everyone happens to be lining up at the same one the total capacity across all five registers is fine, but the uneven distribution of where people actually queue up is the real problem. A hot partition is exactly that same uneven-distribution problem, just happening inside a database instead of a checkout line.

The fix was migrating again, this time to ScyllaDB a database built in C++ specifically to eliminate the garbage-collection pauses that had been plaguing Cassandra. A deeper technical breakdown from Sujeet Jaiswal's engineering blog explains ScyllaDB's key structural advantage: a shard-per-core architecture, where each individual CPU core is assigned its own subset of the data and processes requests for that data completely independently, with its own dedicated memory allocator, and no shared mutable state between cores at all. The direct payoff, according to the same source, is that a hot partition landing on one core no longer degrades performance for completely unrelated queries being served by other cores on that same machine a fundamental structural improvement over Cassandra, where a garbage-collection pause or lock contention issue could drag down an entire process, not just the one narrow piece of data actually under pressure.

Cassandra's problem:
  Garbage collection pauses → unpredictable latency, platform-wide
  Hot partitions → hurt overall node performance, not just that one query
 
ScyllaDB's shard-per-core fix:
  Each CPU core → its own data subset, own memory, own I/O
  A hot partition on core 3 → doesn't touch performance on cores 1, 2, 4, 5...
Diagram of ScyllaDB's shard-per-core architecture isolating problem partitions

The Numbers That Made the Migration Worth the Pain

A write-up on Saifeddine Rajhi's blog lays out exactly how much this migration paid off in hard numbers: p99 read latency the worst-case experience for 99% of requests dropped to 15 milliseconds, down from a range of 40 to 125 milliseconds under Cassandra. Write latency at that same p99 mark fell to 5 milliseconds, down from a range of 5 to 70 milliseconds. Sujeet Jaiswal's breakdown adds an equally striking infrastructure-efficiency number: the same workload that had required a 177-node Cassandra cluster ran comfortably on just 72 ScyllaDB nodes afterward a genuinely dramatic reduction in raw hardware footprint for handling the same trillions of messages.

Diagram summarizing the measured performance and efficiency gains from the ScyllaDB migration

The Layer Between the App and the Database That Made It All Work

Here's a genuinely important architectural insight that's easy to miss if you only focus on which database Discord happened to be using at any given moment. The same Sujeet Jaiswal piece makes a sharp observation: Discord didn't just swap one database for another they added an entirely new data services layer sitting between the application and the database itself, and this layer, not any single database swap alone, is what actually absorbed the kind of read amplification that no database could have handled cleanly on its own.

That layer, according to the same source, was written in Rust specifically for two reasons: predictable latency, since Rust has no garbage collector to occasionally pause and clean up after itself, and safe concurrency, since Rust's ownership model prevents entire categories of data races in the request-coalescing logic living inside that layer. Discord's own engineering blog, in the post How Discord Stores Trillions of Messages, describes request coalescing directly: rather than letting many separate, near-identical requests each independently hit the database, this layer combines them, dramatically cutting down on redundant queries reaching the database at all.

A single, capable assistant standing between a crowd of people and one overworked expert, noticing that six different people are all about to ask that expert nearly the exact same question, and simply asking it once on everyone's behalf instead of letting six duplicate questions all land on the expert's desk simultaneously. That's roughly what request coalescing does for Discord's database layer it filters out the redundant, duplicate work before it ever reaches the part of the system that's expensive to query.

Without a data services layer:
  6 near-identical requests → 6 separate database queries → wasted load
 
With Rust-based request coalescing:
  6 near-identical requests → recognized as duplicates → 1 database query
  → result shared back across all 6 original requests
Diagram of request coalescing reducing redundant database queries

Migrating Trillions of Messages Without Anyone Noticing

Here's the genuinely nerve-wracking part of this whole story: how do you actually move trillions of already-existing messages from one database system to a completely different one, without any live user ever seeing a dropped message or a broken conversation?

Discord's own engineering blog describes hitting a real, frustrating wall partway through this exact process: after setting up dual-writing (sending every new message to both Cassandra and ScyllaDB simultaneously) and provisioning an off-the-shelf Spark-based migration tool for the historical backlog, the estimated time to finish migrating everything came back at three months a timeline the team was openly not thrilled about. Their fix was refreshingly scrappy: rather than accept that estimate, they extended their own existing, high-performance Rust data service library to handle large-scale historical data migration directly, reading data in token ranges, checkpointing progress locally using SQLite, and then streaming it into ScyllaDB as fast as the system could handle. That rewritten migrator finished the entire job in nine days.

Plan A: off-the-shelf Spark migrator
  → Estimated completion: ~3 months
 
Plan B: extend their own Rust data service library
  → Read data in token ranges
  → Checkpoint progress locally via SQLite
  → Stream directly into ScyllaDB
  → Actual completion: 9 days

Going three months down a chosen migration path, hitting a wall, and having the confidence and existing tooling to scrap that plan for something ten times faster, built largely from parts you already had lying around, is a genuinely rare thing to see documented this openly. Discord's own blog treats it almost playfully, but the underlying lesson is a serious one: having already invested in a solid, well-understood internal tool (their Rust data service library) meant that when the "standard" off-the-shelf path proved too slow, there was a faster, better-fitted alternative sitting right there, ready to be extended.

Closing illustration summarizing Discord's rapid database migration

Putting It Together

None of the individual pieces here are exotic inventions on their own. Process isolation is a well-known Erlang and Elixir pattern. Cassandra and ScyllaDB are both established distributed databases. Request coalescing is a documented technique for reducing redundant load. What makes Discord's engineering story genuinely compelling is the willingness to keep re-examining tools that had served the platform well for years, and to swap them out entirely twice, for the database layer alone the moment the data made clear that a once-perfect fit no longer was. Elixir stayed until Rust was genuinely needed for the CPU-heavy pieces. Cassandra stayed until its garbage collection pauses became the actual bottleneck. Even an off-the-shelf migration tool stayed in the plan only until a faster, custom-built alternative proved itself worth the extra initial effort.

What This Means Going Forward

As Discord keeps growing past the trillions of messages already stored, and as voice, video, and increasingly complex community features add even more real-time load onto the same underlying infrastructure, the same lesson from this whole story keeps applying: no single technology choice is meant to be permanent. The real skill on display across Discord's engineering blog isn't picking the perfect database or language once and never revisiting it it's building a team and a culture willing to ask, honestly, whether last year's right answer is still this year's right answer, and having the tooling and confidence in place to actually act on it when the answer turns out to be no.

Discussion