How Discord Scaled to 11M+ Concurrent Users: Why Cassandra Failed & ScyllaDB Won
The inside story of trillion-message storage, JVM garbage collection death spirals, and migrating to C++ thread-per-core architecture.
SystemSloth Architecture Team
Distributed Systems Research
1. The Trillion-Message Milestone: Discord's Data Explosion
From single MongoDB instance to 177 Cassandra nodes storing billions of daily chats
In 2015, Discord launched on a single MongoDB replica set. Within months, as voice and text chat skyrocketed across gaming communities, MongoDB began choking under the write load: data and indexes exceeded RAM, and page faults skyrocketed.
In 2017, Discord migrated its core message storage to Apache Cassandra. Cassandra seemed like the ideal fit: a distributed, masterless, LSM-tree based NoSQL database optimized for heavy sequential writes and linear horizontal scaling.
Scale by the Numbers
By 2022, Discord was ingesting over 4 million messages every second, storing trillions of historical messages across 177 Cassandra nodes, and servicing over 11 million concurrent voice and text users.
2. The Cassandra Breakdown: JVM GC Pauses & Compaction Storms
Why even world-class JVM tuning failed to save Discord's Cassandra clusters
Despite massive engineering investments into JVM tuning (CMS, G1GC, ZGC) and OS page cache optimization, Cassandra began exhibiting catastrophic tail latencies. In a real-time chat application where users expect instant message delivery, Discord observed P99 latencies spiking past 1,000ms, and in worst cases, 5,000ms+ timeouts.
Three structural flaws caused the breakdown at scale:
- Stop-the-World JVM Garbage Collection: With multi-gigabyte heaps, Java GC cycles periodically paused threads for 1 to 3 seconds. Nodes froze, gossip heartbeats failed, and neighboring nodes erroneously marked them as down.
- Tombstone Accumulation: When users or automated bots delete messages or channels, Cassandra doesn't immediately remove the row from disk. Instead, it writes a 'tombstone' marker. Reading through channels with high bot activity required scanning tens of thousands of tombstones, triggering severe read timeouts.
- SSTable Compaction Storms: As writes flooded the LSM-trees, Cassandra background compaction threads starved read threads of disk IOPS and CPU, driving cascading node degradation.
3. The ScyllaDB Breakthrough: C++ Thread-per-Core Architecture
A drop-in Cassandra replacement without the JVM overhead
Instead of rewriting their entire data access layer, Discord evaluated ScyllaDB—a complete reimplementation of Apache Cassandra in C++20 built on top of the Seastar asynchronous framework.
ScyllaDB fundamentally eliminates the performance bottlenecks of Cassandra through three architectural principles:
| Architecture Dimension | Apache Cassandra (Java) | ScyllaDB (C++ / Seastar) |
|---|---|---|
| Execution Model | Multi-threaded with JVM thread pooling & locks | Shared-nothing thread-per-core (1 thread pinned per CPU core) |
| Memory Management | JVM Managed Heap (GC pauses, object allocation overhead) | Custom direct C++ memory allocator, zero Garbage Collection |
| Disk I/O | Standard Linux page cache / mmap | Direct I/O (O_DIRECT) with custom AIO scheduler bypassing kernel buffers |
| Hot Partitions | Thread contention and lock convoy on busy keys | CPU core isolation; hot partition does not starve other cores |
| P99 Read Latency | Spikes to 1,000ms - 5,000ms during compaction/GC | Consistent 8ms - 15ms at peak load |
4. Sharding by Snowflake & Bounding Partition Sizes
How Discord designs partition keys to prevent unbounded Cassandra/ScyllaDB wide rows
In wide-column stores, unbounded partition sizes are fatal. If a Discord channel has 50 million messages over 5 years, putting all messages into a single partition `(channel_id)` creates a multi-gigabyte row that overwhelms memory buffers and destroys read performance.
Discord solved this by bucketing messages into discrete time slices using their 64-bit Snowflake ID generation scheme. A Discord Snowflake ID encodes an epoch timestamp in its most significant 42 bits.
By taking the timestamp component and grouping messages into roughly 10-day buckets, Discord defined a composite primary key that guarantees no partition ever exceeds ~100MB, allowing ScyllaDB to query message history with pinpoint accuracy.
CREATE TABLE messages (
channel_id bigint,
bucket int, -- (timestamp >> 22) / (10 * 86400 * 1000)
message_id bigint,
author_id bigint,
content text,
attachments list<text>,
PRIMARY KEY ((channel_id, bucket), message_id)
) WITH CLUSTERING ORDER BY (message_id DESC);
-- Fetching the latest 50 messages in a channel:
SELECT * FROM messages
WHERE channel_id = 987654321
AND bucket = 204
ORDER BY message_id DESC
LIMIT 50;5. Real-World Results: Slashing Latency by 98%
The impact of moving from 177 Cassandra nodes to ScyllaDB
The migration delivered startling efficiency improvements across Discord's infrastructure:
1. Node Reduction: Discord reduced their massive cluster footprint from 177 beefy Cassandra nodes down to just 72 ScyllaDB nodes, saving millions in annual cloud compute costs.
2. Latency Elimination: Tail P99 read latency dropped from unstable 1,000ms+ spikes to a rock-solid 15ms. Even P99.9 latencies remained below 30ms during global peak gaming hours.
3. Operational Simplicity: No more manual GC tuning, no more tombstone scan crashes, and automated self-throttling compactions that never starve active traffic.
Key Architectural Takeaways
- 1At extreme scale, language runtime overhead (JVM GC pauses) becomes a primary latency bottleneck.
- 2Thread-per-core architectures and Direct I/O maximize modern NVMe and multi-core CPU hardware efficiency.
- 3Partition keys must always be bounded; derive composite keys using time-bucketed Snowflake IDs to prevent wide-row degradation.
- 4Zero-copy C++ database architectures can deliver 10x throughput with 1/3 the server footprint.
Frequently Asked Questions
Can ScyllaDB really be used as a drop-in replacement for Cassandra?
Yes. ScyllaDB implements the Cassandra Query Language (CQL), the same network protocol (port 9042), and the same SSTable disk formats. Clients can use existing Cassandra drivers (Node.js, Go, Python, Java) without changing their application queries.
What is the thread-per-core model?
In ScyllaDB's Seastar framework, each CPU core runs an independent event loop. Memory is split evenly across cores, and there is zero cross-thread locking or cache contention. Work is dispatched via non-blocking queues, ensuring predictable microsecond execution.
Why did Discord use 10-day buckets for channel messages?
A 10-day window balances partition count with row size. Even in hyperactive channels with tens of thousands of messages a day, 10 days of chat data stays well under the recommended 100MB partition size limit, preventing wide-row degradation.
Ready to Design Real-Time Chat Application (WhatsApp / Messenger / Discord)?
Open the interactive canvas to architect this system step-by-step. Drag microservices, configure database partitions, handle failover scenarios, and get instant AI feedback on your tradeoffs.