System DesignAdvancedcase studyPart 7 of 8 in Database Sharding Mastery

Database Sharding Part 7: Case Study - Scaling Discord to Billions

How Discord moved from MongoDB to ScyllaDB and implemented Bucketized Sharding to handle massive channel growth.

Sachin SarawgiApril 20, 20265 min read5 minute lesson

Database Sharding Part 7: Case Study - Scaling Discord to Billions

We have spent the last 6 parts of this series exploring the theoretical mathematics, routing algorithms, and migration playbooks for distributed databases. Now, it is time to look at one of the most famous real-world implementations of these concepts: Discord's migration from MongoDB to Cassandra, and eventually to ScyllaDB.

Discord-like messaging workloads are brutal for storage systems. They generate billions of append-heavy writes, demand incredibly low read-latency for message history, and suffer from extreme data skew.

This case study highlights the most practical lesson in modern database architecture: Sharding by channel_id or user_id is not enough when your data distribution follows a power law.

1. The Core Architectural Challenge

In the early days, Discord stored messages in MongoDB. As they grew to billions of messages, MongoDB's index sizes exceeded available RAM, causing catastrophic disk paging and latency spikes. They migrated to Apache Cassandra, a distributed, masterless database designed for massive write throughput.

The initial Cassandra data model was logical:

  • Partition Key: channel_id
  • Clustering Key: message_id (TimeUUID)

Under this model, all messages for a specific channel live on the same physical server (shard) and are sorted sequentially by time. When a user opens a channel, Cassandra executes a lighting-fast sequential disk read to fetch the last 50 messages.

The "Fortnite" Skew Problem

This model works perfectly for a private Discord server with 10 friends.

But Discord hosts massive public servers. The official Fortnite or Midjourney channels have millions of active users generating thousands of messages per second. Under the naive channel_id partitioning scheme, the entire Fortnite channel—potentially terabytes of data—is forced onto a single physical shard.

This causes two catastrophic failures:

  1. Write Hotspots: One physical server hits 100% CPU attempting to ingest the massive influx of messages, while the rest of the cluster sits idle.
  2. Compaction Death: Cassandra uses Log-Structured Merge (LSM) trees. It must periodically merge (compact) old data files on disk. Compacting a multi-terabyte partition belonging to a single channel takes so long that the server exhausts its disk I/O, leading to dropped writes and cluster instability.

2. The Solution: Bucketized Sharding

Discord engineers realized they had hit the Celebrity Problem (discussed in Part 3). They could not store an infinitely growing channel in a single partition.

They invented Bucketized Sharding (or Time-Window Sharding).

Instead of partitioning solely by channel_id, they modified the Partition Key to be a composite key: partition_key = (channel_id, bucket_id)

How Bucketing Works

A bucket_id represents a bounded chunk of time or a bounded number of messages.

For a small, private channel with low traffic, the bucket_id might simply represent the year (e.g., bucket_id = 2026). All messages for that year fit comfortably on one shard.

For the massive Fortnite channel, Discord calculates the bucket_id dynamically based on time windows (e.g., a new bucket every 10 days). When the time window rolls over, the application starts writing new messages to a brand new bucket_id.

The architectural impact is massive:

  • The multi-terabyte channel is instantly broken into hundreds of small, manageable partitions.
  • Because these partitions use a composite key, Cassandra's Consistent Hashing algorithm scatters them randomly across the entire physical cluster.
  • The write hotspot is completely eliminated. The load of a massive channel is evenly distributed across every server in the datacenter.

3. The Read Path Penalty

As we learned in Part 5, fixing a write hotspot often ruins the read latency. If the data is scattered across hundreds of buckets, how do you read a channel's history without performing a brutal scatter-gather query?

Discord optimized the read path by explicitly avoiding scatter-gather.

When a user opens the Fortnite channel, the application doesn't ask the database for "the last 50 messages." Instead, the application knows the current time. It calculates the current bucket_id and queries the database for exactly that bucket.

If the current bucket only has 10 messages, the application sequentially queries the previous bucket_id to fetch the remaining 40 messages. Because the application knows exactly which physical buckets to ask for, it performs targeted O(1) reads, preserving P99 read latency while completely solving the write bottleneck.

4. The Final Migration: Cassandra to ScyllaDB

Even with the perfect data model, Discord eventually hit the physical limitations of the Java Virtual Machine (JVM).

Apache Cassandra is written in Java. As Discord scaled to trillions of messages, the Java Garbage Collector (GC) became a major source of latency spikes. When the GC runs a "Stop-The-World" pause to clean up memory, the database node completely freezes for a few hundred milliseconds, causing timeouts across the microservice architecture.

Discord executed a final zero-downtime migration to ScyllaDB.

ScyllaDB is a drop-in replacement for Cassandra written entirely in C++. It bypasses the Linux kernel using an architectural pattern called "Thread-per-Core", and manages its own memory, completely eliminating Garbage Collection pauses. By combining the Bucketized Sharding data model with a C++ storage engine, Discord achieved sub-millisecond P99 latency on a dataset spanning trillions of rows.

Summary: Lessons for your Architecture

You do not need to be the size of Discord to learn from their architecture:

  1. Never create unbound partitions. If your shard key allows a single entity to grow to terabytes over time, you will eventually crash your database.
  2. Use Bucketized Sharding to artificially split massive partitions into time-bounded chunks.
  3. Control the Read Path by calculating exactly which buckets hold the data, avoiding expensive scatter-gather queries.

This concludes our 7-part deep dive into Database Sharding. You now have the theoretical foundations, the mathematical algorithms, and the practical case studies needed to scale a relational database beyond its vertical ceiling.

📚

Recommended Resources

Designing Data-Intensive ApplicationsBest Seller

The definitive guide to building scalable, reliable distributed systems by Martin Kleppmann.

View on Amazon
Kafka: The Definitive GuideEditor's Pick

Real-time data and stream processing by Confluent engineers.

View on Amazon
Apache Kafka Series on Udemy

Hands-on Kafka course covering producers, consumers, Kafka Streams, and Connect.

View Course

Practical engineering notes

Get the next backend guide in your inbox

One useful note when a new deep dive is published: system design tradeoffs, Java production lessons, Kafka debugging, database patterns, and AI infrastructure.

No spam. Just practical notes you can use at work.

Sachin Sarawgi

Written by

Sachin Sarawgi

Engineering Manager and backend engineer with 10+ years building distributed systems across fintech, enterprise SaaS, and startups. CodeSprintPro is where I write practical guides on system design, Java, Kafka, databases, AI infrastructure, and production reliability.

Continue Series

Database Sharding Mastery

Lesson 7 of 8 in this learning sequence.

Next in series
1

Intermediate

Database Sharding Part 1: The Vertical Ceiling

Database Sharding Part 1: The Vertical Ceiling In the early days of a startup, database scaling is a solved problem. You go into the AWS RDS or GCP Cloud SQL console, select a larger instance type, click "Modify," and yo…

2

Intermediate

Database Sharding Part 2: Partitioning vs. Sharding

Database Sharding Part 2: Partitioning vs. Sharding When engineering teams realize their database is struggling to keep up with load, the words "partitioning" and "sharding" are often thrown around interchangeably in arc…

3

Advanced

Database Sharding Part 3: The Shard Key Blueprint

Database Sharding Part 3: The Shard Key Blueprint Once you have accepted the architectural complexity of sharding (as discussed in Part 2), you are faced with the single most critical decision in distributed database des…

4

Advanced

Database Sharding Part 4: Consistent Hashing Internals

Database Sharding Part 4: Consistent Hashing Internals In Part 3, we successfully identified userid as our shard key. Now, we must write the mathematical algorithm that routes an incoming query containing userid: 1045 to…

5

Advanced

Database Sharding Part 5: The Scatter-Gather Problem

Database Sharding Part 5: The Scatter-Gather Problem When you implement a sharded database architecture, you establish a primary Shard Key (as discussed in Part 3) to route your queries. If your Shard Key is userid, retr…

6

Advanced

Database Sharding Part 6: Zero-Downtime Re-sharding

Database Sharding Part 6: Zero-Downtime Re-sharding Eventually, your sharding strategy will need to change. Perhaps you outgrew your initial 4-shard cluster and need to expand to 16 shards. Or perhaps you realized your S…

7

Advanced

Database Sharding Part 7: Case Study - Scaling Discord to Billions

Database Sharding Part 7: Case Study - Scaling Discord to Billions We have spent the last 6 parts of this series exploring the theoretical mathematics, routing algorithms, and migration playbooks for distributed database…

8

Expert

Query Optimization: The Hidden Cost of Cross-Shard Joins

Query Optimization: The Hidden Cost of Cross-Shard Joins In a sharded database, the "Scatter-Gather" query is the silent performance killer. When you perform a join on columns that aren't the shard key, your proxy has to…

Keep Learning

Move through the archive without losing the thread.

Related Articles

More deep dives chosen from shared tags, category overlap, and reading difficulty.

DatabasesIntermediate

Database Sharding Part 1: The Vertical Ceiling

Database Sharding Part 1: The Vertical Ceiling In the early days of a startup, database scaling is a solved problem. You go into the AWS RDS or GCP Cloud SQL console, select a larger instance type, click "Modify," and yo…

Apr 20, 20265 min read
Deep DiveDatabase Sharding Mastery
#database-scaling#sharding#performance
DatabasesAdvanced

Database Sharding Part 5: The Scatter-Gather Problem

Database Sharding Part 5: The Scatter-Gather Problem When you implement a sharded database architecture, you establish a primary Shard Key (as discussed in Part 3) to route your queries. If your Shard Key is userid, retr…

Apr 20, 20265 min read
Deep DiveDatabase Sharding Mastery
#query-optimization#sharding#latency
DatabasesAdvanced

Database Sharding Part 6: Zero-Downtime Re-sharding

Database Sharding Part 6: Zero-Downtime Re-sharding Eventually, your sharding strategy will need to change. Perhaps you outgrew your initial 4-shard cluster and need to expand to 16 shards. Or perhaps you realized your S…

Apr 20, 20265 min read
PlaybookDatabase Sharding Mastery
#data-migration#zero-downtime#sharding
DatabasesIntermediate

Database Sharding Part 2: Partitioning vs. Sharding

Database Sharding Part 2: Partitioning vs. Sharding When engineering teams realize their database is struggling to keep up with load, the words "partitioning" and "sharding" are often thrown around interchangeably in arc…

Apr 20, 20265 min read
Deep DiveDatabase Sharding Mastery
#database-scaling#partitioning#sharding

More in System Design

Category-based suggestions if you want to stay in the same domain.