Resource Estimation
In one line: after five chapters of capacity models that divided users by requests-per-second, this one divides users by users-per-server. That is a real improvement, and it still leaves the most important number uncomputed.
Assumptions
More than 2 billion daily active users sending more than 100 billion messages per day. Average message size 100 bytes. Servers retain messages for 30 days; if a user does not reconnect within that window, messages are permanently deleted.
Storage
100,000,000,000 messages/day x 100 bytes = 10 TB/day 10 TB x 30 days = 300 TB
Real-world requirements exceed this due to media files, user information, message metadata, and encryption keys. For simplicity we use 300 TB as our baseline.
300 TB is small, and the reason is that storage is a queue
Put it beside the rest of the module:
| System | Storage |
|---|---|
| ~1,982 PB/year | |
| 93 PB/year | |
| Web crawler | 10.35 PB/crawl |
| 300 TB |
Three orders of magnitude below the media platforms, for a service with more users than any of them.
The reason is Lesson 2's observation: the server stores only what is undelivered. Most messages occupy it for seconds. The 300 TB figure is the 30-day tail of messages whose recipients never came back — an upper bound on a buffer, not an archive.
And the chapter is honest that media is excluded. That matters, because media would dominate: Lesson 8 notes limits of 16 MB per media file and 100 MB per document. A single video is 160,000 times a text message. If even 1% of messages carried media at 1 MB average, that alone would be 1 PB/day — three thousand times the text figure.
When storage is a delivery buffer rather than an archive, its size is set by the delivery gap. That is why a service with two billion users needs less storage than one with a hundred million photos.
Bandwidth
10 TB / 86,400 s = 115.7 MB/s = 926 Mb/s incoming Outgoing must match, since every message received is delivered
Under a gigabit, and the symmetry claim needs qualifying
926 Mb/s — a single network interface. Even granting the excluded media, the text path is trivially small.
The symmetry argument is "outgoing bandwidth must match incoming, as every message received by the system is delivered to a recipient." True for one-on-one chat and false for groups.
A message to a group of 50 is received once and delivered fifty times:
One-on-one: 1 in -> 1 out symmetric Group of 50: 1 in -> 50 out 50x amplification
That is Lesson 7's fan-out, and it means outgoing bandwidth is incoming × average delivery multiplier, not incoming. With group chat being a stated functional requirement, the multiplier is well above one.
It also compounds with Lesson 2's acknowledgements — each delivery generates a return event.
In any system with fan-out, outgoing traffic is incoming times the amplification factor, and claiming symmetry only holds where the fan-out is one.
The number that is never computed
1.16 million messages per second — everything is derived from it except itself
The chapter uses 100 billion messages a day to compute storage and bandwidth. It never divides by 86,400:
100,000,000,000 / 86,400 = 1,157,000 messages per second
Over a million per second, and it is the figure that sizes the components Lessons 6 and 7 describe:
| Per message, the system does | At 1.16M/s |
|---|---|
| A WebSocket manager lookup for the recipient's server | 1.16M lookups/s |
| A write to Mnesia before delivery | 1.16M writes/s |
| A forward between two WebSocket servers | 1.16M forwards/s |
| A delete on acknowledgement | 1.16M deletes/s |
| Plus Lesson 2's three acknowledgement events | ~3.5M more events/s |
So the real event rate through the message path is closer to four million per second, against a bandwidth figure of 926 Mb/s that suggests a single machine.
This is precisely the trap that building block set: 750,000 location updates per second hiding inside 114 Mb/s. Same shape here — the bytes are trivial because a message is 100 bytes; the operations are not.
A small bandwidth figure conceals an enormous rate when the objects are tiny. Always divide by 86,400 and look at the quotient, even when the byte count looks comfortable.
The practical consequence: the 200 servers in the next section are sized for connections, and nothing in the chapter sizes for message throughput. Those are different tiers with different bottlenecks.
The sizing mistake here is treating this like a request-response service. Connection count sets the fleet size, and it is driven by how many people have the app open, not by how many messages they send.
Servers
WhatsApp handles approximately 10 million connections per server, achieved through extensive performance engineering — optimizing the server kernel, networking libraries, and infrastructure configuration.
Number of servers = 2 billion / 10 million = 200 servers
This is the right model, and it is the first time in the module
Lesson 1 laid out the comparison. The key point is dimensional coherence: users divided by users-per-server is a valid operation, where users divided by requests-per-second was never one.
And the input — 10 million connections per server — is a real number from real engineering. The design is right that it comes from "optimizing the server kernel, networking libraries, and infrastructure configuration," and it is worth knowing what that actually involves:
File descriptor limits. Every socket is a descriptor; default limits are thousands, not millions.
Per-connection memory. Default socket buffers of a few hundred kilobytes times 10 million is impossible; you tune them down to a few kilobytes for mostly-idle connections.
An event-driven server. Thread-per-connection dies long before this. You need epoll or kqueue and a small number of threads — which is the C10K answer the YouTube, Maps, and Uber chapters all gestured at.
Erlang's process model, which is what WhatsApp actually used and which Lesson 6's Mnesia database is a hint of — Mnesia is Erlang's built-in distributed store.
A general-purpose server tuned for one workload can outperform a general-purpose server by orders of magnitude — and that is genuinely this chapter's stated lesson, in its own summary.
But the input is daily-active users, not concurrent connections
The formula says "Total connections per day / connections per server." A daily count divided by a concurrent capacity.
2 billion users are not online at once. They are spread across time zones, asleep, or have the app closed:
100% concurrent -> 200 servers <- published 20% concurrent -> 40 servers 10% concurrent -> 20 servers
At a realistic 10–20% simultaneity the answer is 20 to 40 servers.
So 200 is a conservative upper bound, which is a legitimate thing to compute — provision for everyone online and you are never short. But it should be labelled as one, because a 5–10× over-provision is a real cost at this scale.
To be fair, this is a far better error than the module's earlier ones. It is off by a factor of five to ten in the safe direction, using the right model — where DAU-as-RPS was off by four orders of magnitude using the wrong one.
| Quantity | Published | Assessment |
|---|---|---|
| Storage/day | 10 TB | Correct for text; media excluded and acknowledged |
| Storage 30-day | 300 TB | Correct — and it is a queue depth, not an archive |
| Bandwidth | 926 Mb/s in and out | Correct in; outgoing needs a fan-out multiplier |
| Message rate | not computed | ~1.16M/s, rising to ~4M events/s with acknowledgements |
| Servers | 200 | Right model, conservative input — 20–40 at realistic concurrency |
Key takeaway
300 TB is three orders of magnitude below the media platforms despite more users, because storage is a delivery buffer sized by the gap, not an archive. The bandwidth symmetry claim holds only where fan-out is one — a group of 50 turns one inbound message into fifty outbound. The chapter derives everything from 100 billion messages a day except the rate itself: 1.16 million per second, closer to four million events once acknowledgements are counted, hidden inside a comfortable 926 Mb/s because the objects are tiny. And the server model is the first structurally correct one in the module — connections divided by connections-per-server — with a conservative input, since 2 billion daily-active is not 2 billion concurrent.
Next: the high-level flow and the acknowledgement chain.