System Design Interview

Course Content

System Design Interview

31 sections · 71 lessons

Email Service deep dive: storage design and per-user search


The first lesson established that storage leads this design: 200 PB of bodies that are rarely read, and a thin layer of metadata that serves every query. This lesson turns that ratio into a storage layout, then builds the search system that the layout makes possible.

There are three kinds of data with three different access patterns and three different stores.

Metadata: a distributed database, partitioned by user

Every listing, search result, unread count, and flag change touches metadata. It is small, hot, and queried by mailbox.

Partition by user ID. All of one user's metadata lives on one partition, so opening a mailbox is a single-partition range scan rather than a scatter-gather across the cluster. A wide-column store fits the shape well: the partition key is the user, the clustering key is the folder or label plus a timestamp, and rows are wide and sparse.

The cost of this choice is that anything crossing users becomes expensive. A shared mailbox for support@ has many readers and one partition, so it becomes a hot key — the same problem as the global leaderboard in Section 27 (Design a Real-Time Gaming Leaderboard). Model a shared mailbox as its own pseudo-user with its own partition and grant access, rather than duplicating messages into each member's mailbox.

Bodies: blob storage, written once, read rarely

The message body — headers, text, HTML — goes into an object store as an immutable blob. The metadata row holds the blob's identifier. Bodies are never updated, only created and deleted, which makes them a perfect fit for the object storage of Section 26 (Design an S3-like Object Storage).

Small bodies have a problem worth naming: a 4 KB message in an object store with per-object overhead and a minimum billable size is inefficient. Pack many small bodies into larger container blobs and store an offset and length in the metadata. Now one read fetches a byte range from a large object, and the per-object overhead is amortised across thousands of messages.

Attachments: deduplicated by content hash

Compute a hash of each attachment's bytes. Store the blob under that hash. If the hash already exists, store only a reference.

The saving, computed. A 4 MB presentation sent to a 300-person team, stored naively, is 1.2 GB. Deduplicated, it is 4 MB plus 300 small references — a 300× reduction on that message. Across a whole system the aggregate saving depends entirely on how much mail is internal and broadcast; tens of per cent of attachment bytes is a plausible order of magnitude, and it should be presented as an estimate rather than a fact.

The caveats, both real. Reference counting must be exact, or deleting one user's copy destroys everybody's. And cross-user deduplication has a privacy edge: an attacker who can observe storage growth can test whether a specific file already exists in the system. Deduplicating within a user, or within an organisation, removes most of that risk and captures most of the saving — and that is the recommendation unless the interviewer wants the full cross-user analysis.

MetadataSharded SQL / NoSQL~1 KB per messagesender, recipientssubject, timestampsfolder, flags, thread idQueried constantly: list a folder, search, count unread.Small rows, heavy indexes.BodiesObject store~75 KB averagethe rendered textthe HTML partinline imagesRead once when the message is opened. Immutable, socache and compress freely.AttachmentsObject store + dedupeup to 25 MBfiles, addressed by hashOne file mailed to 500 colleagues is stored once.Deduplication pays for itself here more than anywhereelse.One logical message, three storage systems, chosen by access pattern rather than by what the data is called.
Metadata is 1% of the bytes and 99% of the queries — which is the entire argument for splitting them.

Search

With metadata partitioned by user, search follows the same line. Search over email is not a smaller version of web search. The constraint that changes everything is that every user must see only their own mail.

Why email search is not web searchOne shared index• Every query needs a user filter• One user's mail sits beside another's• Deletes must scrub a global structurePer-user index• Isolation is structural, not a filter• Small index, so rebuilds are cheap• A hundred million tiny indexes
Every user must see only their own mail, and enforcing that with a filter rather than a boundary is how leaks happen.

Why one shared index does not work

An inverted index maps a term to the list of documents containing it. A single global index over 100 million mailboxes would answer "invoice" with a posting list spanning every user, and the system would then filter it down to one user's messages.

Two problems, one of them fatal.

Performance. The posting list for a common term such as "invoice" would contain billions of entries, of which one user's few dozen are relevant. You would read a gigabyte to return twelve results.

Safety. One filtering bug exposes other people's mail. A design where a single missing predicate causes a privacy breach is a bad design regardless of how carefully it is written. The property you want is that a user's query is structurally incapable of reaching another user's data.

Per-user indexes

Give every mailbox its own inverted index, stored alongside that user's metadata partition. A query touches exactly one user's index, so the posting lists are small — a mailbox with 50,000 messages has posting lists in the thousands, not the billions — and cross-user access is impossible by construction rather than by predicate.

Index size. For a 2 GB mailbox of mostly text, an inverted index typically lands somewhere around 10–30% of the indexed text size. Bodies are a minority of a mailbox's bytes once attachments are counted, and attachment contents may or may not be indexed. A working estimate is a few hundred megabytes per heavy user and a few megabytes for a typical one — worth flagging as an estimate that must be measured rather than a number to quote confidently.

Incremental indexing on delivery. When a message is accepted and placed, extract its terms and append to the user's index in the same pipeline. Search freshness then equals delivery latency, which is what users expect — mail is searchable the moment it appears.

The cost of reindexing

Changing the analyser — adding stemming, changing tokenisation, supporting a new script — means rebuilding every index. 100 million users is 100 million rebuild jobs. At a throughput of, say, 1,000 mailbox rebuilds per second across a large fleet, that is roughly 28 hours of continuous work.

So build for it: version the index format, run old and new side by side, migrate mailboxes in the background, and switch each user's queries over when their new index is ready. This is a migration design, not a one-off script, and saying that is a mark of experience.

What to index, and the honest limits

Index the headers, the plain-text body, and the extracted text of common attachment formats if the product requires it — but note that attachment extraction multiplies indexing cost and is frequently descoped.

End-to-end encrypted mail cannot be indexed server-side at all, because the server never sees the plaintext. The options are client-side indexing, which does not work well across devices, or searchable encryption schemes, which are an active research area with real performance and leakage trade-offs. The honest answer in an interview is that server-side search and end-to-end encryption are fundamentally in tension, and every product picks one.