How Discord's Gateway Handles Millions of Concurrent WebSocket Connections

Khanh Nguyen
Khanh Nguyen
(Updated: )
Listen to this article0 / 0
A hand-drawn high-contrast diagram illustrating a system bottleneck and massive WebSocket fan-out process node.

Every time someone opens Discord, their app quietly opens one connection to Discord's servers and keeps it open for as long as the app is running. Multiply that by tens of millions of people, and you get a systems problem most engineers never have to solve: how do you keep tens of millions of open connections alive, and instantly tell the right few thousand of them "hey, a message just arrived" — without the whole thing falling over?

A WebSocket Is One Open Phone Line, Not Constant Redialing

Picture the old way apps used to check for new messages: your phone calls the server every few seconds and asks, "anything new?" Most of the time the answer is "no," but the phone call still happens — a full connect, ask, answer, hang-up, over and over. That's called polling, and it wastes a lot of effort on "no" answers.

A WebSocket does something different: the browser and server shake hands once — technically, a normal web request that asks the server to "upgrade" the connection — and then that same connection just stays open. Either side can push something down the line the instant it happens, with no new call needed. This behavior was standardized by internet engineers in 2011 and is what almost every modern chat, game, and live-dashboard app is built on today.

Discord picked this model from day one in 2015, and built its original prototype directly on Elixir, a language that runs on the Erlang VM (BEAM) — a runtime built decades earlier for telecom systems that had to stay up while handling huge numbers of simultaneous, independent conversations.

Below is what that difference actually looks like on the wire.

HTTP polling versus a WebSocket connectionA comparison showing polling as repeated request-and-response calls versus a WebSocket as one connection that stays open for two-way traffic.Two Ways to Ask "Any New Messages?"Polling re-asks on a timer. A WebSocket asks once, then listens.HTTP Polling (the old way)BrowserServer"Any updates?""Nope."Repeats every few seconds, even when nothing changedWebSocket (Discord's way, RFC 6455)BrowserServerOne handshake, then either side can push data anytimeSource: Wikipedia, "WebSocket"

Every Connected User Gets Their Own Tiny, Independent Worker

Here's the part that's easy to get wrong if you're used to typical web servers: Discord doesn't handle your connection with a shared thread pool juggling everyone at once. When your app opens its WebSocket, Discord's engineering blog describes it as spinning up a dedicated "session process" — a lightweight worker with its own private memory, running on the Erlang VM. Think of it less like a seat at a shared table and more like handing every single connected person their own personal assistant who does nothing but watch for messages meant for them.

Every Discord server ("guild," in the company's internal terminology) is represented by its own "guild process" — one coordinator per community. When somebody posts a message, the guild process for that community fans the update out to every session process for the people currently online there, and each session process forwards it down its own open WebSocket to the right device.

This is why one giant, noisy Discord server misbehaving doesn't take down a totally unrelated small server sitting next to it — they don't share a process, a thread, or a lock.

How one message reaches every connected memberA pipeline diagram showing a message traveling from a sender's app through a session process and guild process, then fanning out to multiple recipients' session processes.One Message, Fanned Out to Everyone OnlineBased on Discord's own description of session and guild processesYour Discord App(sends a message)Session Process(1 per connected user)Guild Process(1 per Discord server)Session Process→ Member A's appSession Process→ Member B's appSession Process→ Member C's appThe guild process fans the update out to every connected session at onceSource: Discord Engineering Blog

From Five Million Users to Nearly Two Million on One Server Alone

This model isn't just a diagram — it has a documented track record. Discord's own engineering team has published, at several points over the years, exactly how far it stretched.

Two years after launch, Discord reported it was handling nearly five million concurrent users and millions of events per second, but also admitted where it broke: a single message send inside the Erlang VM could take 30 to 70 microseconds due to process scheduling, and at peak that meant fanning one event out to a huge guild could take almost two full seconds — far too slow to feel "instant." The fix was to stop doing that fan-out serially in one process and instead spread the work, using new open-source tools the team built for the purpose, including a consistent-hashing library called ex_hash_ring.

By 2020, in an interview with the Elixir project's own blog, Discord engineers said the platform had crossed more than 12 million concurrent users company-wide, pushing more than 26 million WebSocket events to clients every second — maintained, notably, by only around five engineers running more than 20 Elixir services.

Then came a narrower but harder problem: not the whole platform's total load, but one single, enormous server. Discord's "Maxjourney" post describes scaling one individual guild process — driven by communities like the Midjourney Discord server growing explosively — from handling tens of thousands of concurrent users up to nearly two million concurrent users on that one server alone.

Four sourced milestones in Discord's WebSocket scalingA timeline of four dated milestones, from Discord's 2015 launch on Elixir to the 2023 Maxjourney project scaling one server to nearly two million concurrent users.Four Milestones From Discord's Own Engineering PostsEach figure is dated to when Discord or its engineers reported it, not a current live count2015Discord launches;prototype built inElixir on the BEAMJul 20175M concurrent users;millions of events/sec(official Discord blog)Oct 202012M+ concurrent users;26M+ events/sec;~5 engineers, 20+ services(Elixir-lang.org interview)Nov 2023Maxjourney: 1 guildscaled to ~2M users(official Discord blog)Source: Discord Engineering Blog; Elixir Project Blog

Why One Overloaded Server Doesn't Take Down the Whole Gateway

Getting one guild process to nearly two million concurrent users wasn't a single trick — it was a stack of targeted fixes, publicly summarized by outlets that reviewed Discord's Maxjourney post: profiling exactly which operations were expensive inside the guild process, introducing "passive sessions" so idle members stop generating constant background work, and building a relay layer that spreads the actual fan-out job across multiple machines instead of leaning on one process to do it all serially.

That relay approach — pushing hot, latency-sensitive work out of a single bottleneck and across many workers — echoes the broader trend of teams rewriting hot paths in faster, lower-level languages when a high-level runtime alone can't keep up; Discord's own engineering posts note they've used Rust in places to speed up Elixir's slowest paths. And the underlying reason any of this matters to a browser tab at all traces back to the same push toward faster client-side runtimes — a WebSocket connection is only as responsive as the slowest link in the chain, client included.

Three techniques behind one resilient guild processA decision diagram showing passive sessions, a relay system, and consistent hashing all feeding into the outcome of a large guild staying responsive.Three Techniques That Keep a Giant Server FastSummarized from Discord's Maxjourney engineering post and secondary technical coverage of itPassive SessionsIdle viewers stop trackingevery tiny state updateRelay SystemSplits fan-out work acrossmany machines, not oneConsistent HashingSpreads sessions evenlyso no node gets overloadedA guild with 1M+ membersstays fast and responsiveSource: Discord Engineering Blog ("Maxjourney")

None of this required Discord to abandon its original bet on Elixir and the BEAM. The pattern holds up across every milestone the company has published: one lightweight process per connection, one coordinating process per community, and — when a single coordinator started to strain — spreading its work sideways instead of trying to make one process do more. For any team building its own real-time product, that's the actual transferable lesson: the win wasn't picking a fashionable framework, it was matching the connection model to a runtime built to hold millions of small, independent, mostly-idle workers at once.

Comments (0)

Sort by:

No comments yet.

Be the first to share your perspective on this topic.