SDSystem design
Start learning free

LESSON 93 OF 140 · PRACTICE PROBLEMS

SYSTEM DESIGN · INTERVIEW WALKTHROUGH

Design Slack: System Design Interview Practice

The problem

"Design Slack." Coworkers talk in channels inside their company's workspace, reply in threads, see which channels have something new, and search messages from years ago, often with Slack open on a laptop and a phone at once.

This practice problem reuses the pattern of Design Chat (WhatsApp). Five things are new: workspaces as tenants, history kept and searchable forever, unread counts per channel, threads, and several devices per person. Park voice calls and screen sharing: live sound and video is a system of its own.

The numbers

From the interviewer: 10 million daily users, each sending about 50 messages a day. Round a day to 100,000 seconds (86,400 really); busy hours run about 5× the average.

  • Messages: 10 million × 50 = 500 million a day, 5,000 a second on average (about 5,800 with 86,400), so about 25,000 a second at busy hours.
  • Storage: say a message takes 500 bytes with its ids: 250 GB a day, about 90 TB a year, never deleted (about 270 TB with 3 copies).
  • Members: say a message lands in a channel of about 100 members, so anything written per member per message costs 2.5 million writes a second at busy hours.

Workspaces: split the data by company

90 TB a year can't sit on one database server, so the messages are split into shards. The question is the key. Nearly every read stays inside one company: a channel's history, its member list, a search. Splitting by workspace puts each company's channels, members and messages on one shard, so each of those reads goes to one place. It also keeps tenants apart: one company's data never mixes with another's, so it can be exported, deleted or kept in one region on its own.

Splitting by sender scatters a channel's history across shards; hashing message ids spreads the writes but makes every history page and search ask all the shards. The catch with workspaces is size: a very big company can outgrow one shard, and then its workspace is split further, by channel.

Unread counts: a read mark, not a counter

channel    newest   Ana read   unread
#launch    108      104        4
#general   990      990        0
#design    312      309        3

As in Chat, each message gets a sequence number in its channel: 1, 2, 3… Then a person needs one small row per channel, a read mark: the number of the last message they've read. The unread count is newest − read mark, with nothing to count. Sending a message writes nothing per member, reading writes one mark, and the open app adds 1 itself for each new message it receives.

The alternatives write per member. A counter per person per channel, bumped on every message, is 25,000 × 100 = 2.5 million writes a second at busy hours, 100× the messages. A read flag per message per member is 500 million × 100 = 50 billion new rows a day, kept forever.

Threads: replies are messages

A reply is a message in the same channel with a parent id, the message it answers. A thread is one query, the messages whose parent is 104, and replies are stored, searched and permitted like any other message. The first message keeps a reply count and the last reply's time, a deliberate copy so the channel can show "3 replies" without counting. Replies are numbered within their thread, so they don't make the channel unread; the people in the thread are told instead.

A list of replies inside the first message's row would be rewritten with every reply; a new channel per thread would copy the channel's members and permissions for no gain.

Search: one index per workspace

Search works as in Search Engine: an inverted index maps each word to message ids. Here it is split like the messages, one index per workspace, so a search reads only its own company's index. New messages reach it through a queue, a few seconds behind the chat. Before answering, the server keeps only hits from channels the searcher is in, so private channels stay private.

An index per person would store a message from a 100-member channel 100 times, and hiding hits in the app is too late: the server has already sent them.

Many devices: every app gets the news

When a WebSocket opens, its socket server adds that app to the person's entry in the session store, which then reads "Ana → laptop on server 3, phone on server 9". A message goes to every open app, and so does a read mark:

  1. The laptop sends "read #launch up to 108".
  2. The chat service saves Ana's read mark.
  3. It looks up every open app of Ana's in the session store.
  4. It sends the mark to the socket server holding her phone, and the phone clears the bold.

A closed app catches up when it reconnects: it asks for everything after its cursor, as in Dropbox, and gets the new messages and read marks in order.

Same pattern as Chat (WhatsApp)

The delivery path carries over unchanged. Each open app keeps a WebSocket to a socket server, a session store says which server holds whom, and the chat service saves a message first, then sends it to the members' open apps: a channel message is a Chat group message with more members.

Summary

  • Scope: workspaces, history kept and searchable, unread counts, threads, several devices.
  • Data: a workspace id on every row; a reply is a message with a parent id.
  • API: a WebSocket for messages and read marks; GET /search?q=….
  • Numbers: about 25,000 messages a second at busy hours, 90 TB a year.
  • Services: a search index per workspace, filtered by channel.
  • Scale: messages split by workspace.
  • Unread: a read mark per person per channel; newest − mark.
  • Devices: every open app in the session store; read marks sent to all.
Keep going with system designInteractive lessons from the building blocks to Twitter, Uber and payments, the way interviews ask. Free to start.
Start learning free