Free preview

Group Messages and the Encryption Gap

In one line: the group flow and the encryption requirement are both stated confidently, and they are incompatible as written. That contradiction is the most substantial thing in the chapter.

The group flow

WebSocket servers track active users, not groups. Since group members may have mixed online statuses, the system uses a group message handler, a group message service, and Kafka.

  1. User A sends the message to the message service via their WebSocket server.
  2. The message service publishes the message and group details to Kafka. The group acts as a topic, senders as producers, receivers as consumers.
  3. The group service manages group metadata — members, ID, status, icon — using a MySQL cluster with geographically distributed replicas and a Redis cache.
  4. The group message handler retrieves member data from the group service.
  5. The handler delivers the message to each member following the standard WebSocket delivery process.

A group as a Kafka topic is a reasonable model, with one caveat

"The group acts as a topic, with senders as producers and receivers as consumers" is a clean mapping, and it buys the properties pub-sub established: ordering within a topic, durability, and decoupling of send from delivery.

Ordering matters especially here. Lesson 2's consistency requirement is "deliver messages in order" — and within a Kafka partition, order is guaranteed. So a group conversation stays coherent, which is the requirement's actual purpose.

The caveat is cardinality. Kafka topics are not free — each carries partition metadata, files on disk, and cluster state. A messaging service has an enormous number of groups, most of them tiny and inactive:

A topic per group -> potentially hundreds of millions of topics

Real systems partition by something coarser — hash the group ID into a fixed set of topics — so ordering is preserved per group (all its messages land in one partition) without creating a topic per group.

"One topic per entity" is a clean model that stops working when the entity count is large. Same conclusion pub-sub reached about partitions.

Fan-out is here too, and the chapter does not name it

Step 5 — "delivers the message to each member" — is fan-out, the mechanism three chapters in this module have wrestled with.

The good news is that group chat bounds it in a way social feeds do not:

Social feed fan-outGroup message fan-out
RecipientsUnbounded — 400M followersBounded — group size limits
Push vs pullGenuinely hardPush is fine
WhyPower-law follower countsA cap makes the worst case known

Because group sizes are capped — real products limit them to hundreds or a low thousand — that building block's celebrity problem does not arise. There is no group with 400 million members, so pushing to every member is always affordable and the hybrid strategies are unnecessary.

A hard cap on fan-out converts an unbounded distribution problem into a bounded one, which is why messaging apps impose group size limits and why those limits are a systems decision as much as a product one.

Worth also connecting to Lesson 3: the bandwidth section claimed incoming and outgoing must match. Group fan-out is exactly where that breaks — one inbound message to a group of 200 is 200 outbound deliveries, plus their acknowledgements.

Fan-out is where group chat gets expensive, and the write amplification is linear in group size: one message becomes one content row plus N inbox entries. Batching those writes matters.

The channel choice flips with group size. Per-recipient channels suit one-to-one chats, which dominate by volume. Per-chat channels suit large groups, where publishing once and letting every subscribed server forward locally is far cheaper than publishing a hundred times.

Where encryption breaks it

The group flow as drawn is incompatible with end-to-end encryption

Lesson 2 established that E2E is a stated requirement, listed in the evaluation as satisfied, and included in the summary's claim that the design covers "group chat, and encryption."

Now read step 2 again: "the message service publishes the message and group details to Kafka," and step 5: the handler "delivers the message to each member."

One message, published once, delivered to many. That works only if there is a single representation every recipient can read.

Under end-to-end encryption there is not. The whole point is that the server holds ciphertext it cannot decrypt — and ciphertext is encrypted to a specific key. A message encrypted for Bob cannot be decrypted by Carol.

So the design has three options, and it picks none:

Sender-side fan-out. A encrypts the message separately for each member and sends N ciphertexts. Correct, simple, and it makes the sender's upload cost proportional to group size — painful on mobile for a 200-member group.

A shared group key. All members hold one key; one ciphertext serves everyone. Efficient, and it makes membership changes expensive — when someone leaves, the key must be rotated and redistributed to everyone remaining, or they can still read future messages.

Sender keys. What real products use: each sender has a key distributed once to the group via pairwise encrypted channels, then messages are encrypted once with it. Combines the efficiency of a shared key with per-sender isolation, at the cost of real key-management machinery.

The point is not which one to pick. It is that all three change what step 2 means, and the design's "publish the message" assumes a plaintext-shaped object that E2E forbids.

A cryptographic requirement is not a layer added at the end — it determines what the server is permitted to handle. A design that claims E2E and then has a server read, fan out, and route message content has specified two different systems.

And it breaks media deduplication, concretely

Lesson 8 covers the asset service, which "hashes files to prevent duplication; if the content already exists, the service returns the existing ID."

Now combine with the media flow's own first step: "the device compresses and encrypts the media file" — encryption happens on the device, before upload.

Two users sending the same photo encrypt it with different keys, producing different ciphertexts, which hash to different values.

Same photo, User A -> encrypt(photo, key_A) -> hash_A
Same photo, User B -> encrypt(photo, key_B) -> hash_B    hash_A != hash_B

Deduplication finds nothing. Every copy is stored separately.

This is a checkable contradiction between two things the chapter states three paragraphs apart, and it is the kind of detail worth having ready — it demonstrates the general point concretely rather than abstractly.

The workaround real systems use is convergent encryption: derive the key from a hash of the content itself, so identical files produce identical ciphertexts. It restores dedup and it leaks information — an adversary who suspects a file exists can confirm it by encrypting their copy and checking whether the hash is already known.

Deduplication and encryption are in fundamental tension, because dedup requires recognizing identical content and encryption is designed to prevent exactly that.

The group service is a straightforward read-heavy lookup, and its choices are right

Amid the harder problems, this component is simple and correctly specified: MySQL with geographically distributed replicas plus a Redis cache.

The access pattern justifies both. Group membership is read on every group message — at Lesson 3's rate, a very high read volume — and written only when membership changes, which is rare. So:

Read replicas handle the read volume, and geographic distribution puts membership data near the handlers that need it.

Redis absorbs the hot lookups, and group membership is highly cacheable because active groups are read repeatedly and change rarely.

Relational because group membership is genuinely relational — users, groups, roles, join dates — and Lesson 3 established the volume is modest. This is the same conclusion four chapters in this module have reached: when volume is not the constraint, choose the store for its query model.

Key takeaway

A group as a Kafka topic buys ordering, durability, and decoupling — with a cardinality caveat, since one topic per group does not scale to hundreds of millions. Group fan-out is bounded by group size limits, which is why the celebrity problem does not arise: a hard cap converts an unbounded distribution problem into a bounded one. But the flow as drawn — publish one message, deliver it to many — cannot coexist with end-to-end encryption, because ciphertext is encrypted to a specific key and there is no single representation all members can read. The three resolutions (sender-side fan-out, a shared group key, or sender keys) all change what "publish the message" means. And E2E breaks media deduplication concretely: identical files encrypted with different keys hash differently, so dedup and encryption are in fundamental tension.

Next: media, and why it gets its own path.

Enjoying the preview?

Create a free account to unlock the rest of this course, the in-browser judge, and live AI mock interviews.

Sign up free to continue