Design complete systems / CHAPTER 50
Design a photo-sharing service (Instagram)
Let authors publish complete photos and followers browse them, then separate image delivery from the work of building feeds.
Builds on Design a text-sharing service (Pastebin), Synchronous versus asynchronous communication, Caching fundamentals, Read-heavy versus write-heavy workloads.
- Publish a post only after all required images are ready.
- Calculate image bytes separately from the database work for a feed.
- Combine prepared home lists with read-time author queries, while checking current access.
01Functional requirements
The product stores photos and shows posts from followed authors. The original is the uploaded image; display variants are smaller prepared copies for different screens.
FR1: Upload and manage photos. Let an author upload an original photo, check processing progress, publish it and remove it. Prepare the required display sizes before publishing; viewers must not receive a half-prepared photo.
FR2: View and download. Check the viewer’s permission, then return photo details and a prepared image large enough for the requested display dimensions. Avoid sending the full original for a small feed preview. Support public photos and photos restricted to an approved audience.
FR3: Search titles. Find published photos by title. Check current visibility before returning a title, snippet or image reference.
FR4: Follow accounts. Support following, unfollowing and approval for private accounts. Removing approval must remove access even if an old feed entry still mentions the photo.
FR5: Generate home feeds. Show posts from followed authors, with pages that do not shuffle unpredictably while the viewer moves through them. Begin with newest-first ordering and use it if optional ranking fails.
Out of scope
- Video, photo tags or tag search, user tagging and comments.
- Suggestions about whom to follow and posts from outside the follow graph.
Example: Mira uploads p81, “Garden opening.” Ben follows her, searches its title and sees it in his home feed.
02Nonfunctional requirements
Assume ten million daily active users. Five percent upload two photos each day; each user views 100 photos. These are planning assumptions to confirm and test.
NFR1: Feed latency. At least 95% of primary-region feed requests finish within 300 ms at the planned peak. Downloading images is measured separately.
NFR2: Feed availability. Feed requests meet 99.9% availability per calendar month. If optional ranking fails, return posts ordered by creation time, newest first.
NFR3: Media durability. Published originals and required display sizes survive one storage-node failure under the selected storage service's guarantees. Unfinished temporary uploads are excluded; this is not a promise that data can never be lost.
NFR4: Publication readiness. Verify every required image before publishing its reference. At normal load, at least 95% of valid uploads publish within 30 seconds. Show failures and allow retries.
NFR5: Audience revocation. Check current approval before issuing private-image download credentials. Previously issued credentials expire within 60 seconds. Already downloaded images cannot be recalled.
NFR6: Search, feed and pagination behavior. A photo may become searchable or appear in feeds shortly after publication rather than instantly. For ranked pages, save the ordered post IDs and continue from the last returned position; use post ID to order equal scores. Unrelated photos do not need one globally agreed creation order.
NFR7: Isolate expensive work. Limit simultaneous image validation, resizing, feed updates and retries separately. A slow transform or an author with millions of followers must not consume every resource needed to serve feeds.
03Calculate the media budget
- Uploads: 5% of ten million users = 500,000 uploaders. Two photos each gives one million/day, about 12 uploads/second on average.
- Stored images: one million × 4 MB originals = 4 TB/day. Display variants adding 30% bring the total to 5.2 TB before replicas and backups.
- Displayed bytes: ten million viewers × 100 photos = one billion image displays/day, about 11,600/second. At 50 KB per display, transfer 50 TB/day. Serving smaller prepared sizes and nearby cached copies matters far more than enlarging caption rows.
- Follower updates: a post by an author with ten million followers may need ten million home-list insertions. Measure active followers and posting rates; average upload rate cannot size that burst.
Decisions: store originals durably, prepare copies for the display dimensions the application supports and deliver repeated images through a content delivery network. Put follower-list work in limited background workers, and compare prepared lists with merging large-author posts when viewers read.
04API Design: Requests, Responses and Retries
Reserve bytes separately from publication:
POST /uploads
Idempotency-Key: upload-81
{"mediaType":"image/jpeg","byteLength":4000000}
{"uploadId":"u81","putUrl":"<scoped-url>","expiresInSeconds":900}
Publish the uploaded photo
- Send the image to the upload URL within 900 seconds. The URL permits only this reserved upload.
- Call
POST /postswith request keypublish-81and{"uploadId":"u81","title":"Garden opening","caption":"Come Saturday","visibility":"followers"}. - The service returns
202 Acceptedand{"postId":"p81","state":"processing"}while it prepares the required display sizes. - Check
GET /posts/p81. When all required images are ready, it reports published state and their versioned locations.
Retries and errors
- The same key and content return the same post ID; the retry does not publish a second post.
- Changed content under that key returns
409 Conflict. - An oversized upload returns
413; an unsupported media type returns415. - Reading a private post without permission returns
403. Reading a deleted post returns410.
Browse and change access
GET /authors/M/posts?cursor=...lists Mira's posts; M is her account ID.GET /home?cursor=...lists a home-feed page, for example{"items":[{"postId":"p81","mediaVersion":1}],"cursor":"<ranking-view-cursor>"}.GET /photos/search?q=garden&cursor=...searches titles. Each cursor identifies where the next page continues.- Ben calls
PUT /authors/M/followto follow Mira andDELETE /authors/M/followto unfollow. A private account records the follow as pending until Mira approves it. - Mira calls
PUT /followers/Ben/approvalto approve Ben orDELETE /followers/Ben/approvalto revoke approval. The handler checks that the signed-in account owns the private account before changing that relationship. Repeating either operation leaves the same final relationship state. - Mira calls
DELETE /posts/p81. The handler checks ownership and saves the deleted state before returning success; repeating deletion succeeds without creating another operation. Supplying another ownerId cannot grant permission.
05Data Model and Storage Access Patterns
A published post is the original record. Search hits and feed entries are copies used to find posts; they do not independently decide whether a viewer may see one.
| Record and index | What it stores or answers |
|---|---|
Post, postId |
Author, title, caption, visibility, publication state, creation time and version |
Upload, uploadId; create request, (authorId,requestKey) |
Reserved object, length/type and expiry; original creation fields and the resulting post ID for retry comparison |
Image task, (postId,mediaVersion,taskName) |
Required output, current attempt ID and completion state |
Pending publication work, (postId,version) |
Feed/search update still needing delivery after publication |
Author index, (authorId,createdAt,postId) |
Find one author's recent posts in order |
Image variant, (postId,mediaVersion,size) |
Object location, dimensions and checksum for one prepared image size |
Follow, (followerId,authorId); reverse author index |
Check current approval and find an author's followers |
Home entry, (viewerId,postId) |
A candidate post for one viewer, with ranking hints and a unique insertion key |
Search document, postId |
Searchable title terms and the version copied from the original post |
The author index begins with authorId because the query asks for one author's posts. A globally unique postId alone cannot efficiently answer that query.
06Complete upload before optimizing feeds
Start with an application, a database for post details and private object storage for image bytes. A variant is a prepared size of the photo, such as a thumbnail or display image.
Upload and publish
- Mira requests upload
u81and sends the original image.u81identifies this upload;publish-81identifies her later request to create a post from it. - The application checks actual length, type and checksum. An image-processing worker decodes it outside the feed-serving process, with memory and time limits for malformed files. For this example, remove embedded GPS coordinates from display copies, then create the required image sizes.
- Save each processing task under its post, version and task name. After a crash, another worker checks completed outputs and retries only missing work. Do not rely on an in-memory callback.
- After verifying every required output, the worker asks the database to publish
p81only if its saved post version, processing state and task attempt still match. In that same transaction, save the feed/search work to deliver. A delayed worker cannot republish a post that Mira deleted or a newer task replaced. Readers cannot see a partially prepared photo. - Clean abandoned uploads only after their deadline and grace period, while protecting active tasks.
Serve the first feed and search
- Ben follows Mira. The feed handler reads recent posts from followed authors, removes posts Ben may not see and merges the rest newest first.
- The response names a prepared image that meets the requested display dimensions, rather than always sending the large original.
- Title search can initially use a database text index. Before showing a title, snippet or image, check that the original post is still published and permitted.
At small load, the application may receive upload bytes itself. Later let clients upload directly through restricted storage URLs and run image processors separately. Reserving, verifying and publishing remain distinct steps.
BUILD THE DESIGN, ONE CHANGE AT A TIME
Watch the architecture evolve.
Advance through the stages in this same diagram. Existing components keep their places; additions fill the available space, then add rows. Highlights show what changed from the previous stage, and numbered arrows match the connection key below.
Stage 1 of 3
Start here. Verify that upload, required variants, publication and an authorized feed work before adding distributed views.
Stage 1: read-time feed with verified media
Follow the numbered arrows
- Photo client → Photo API + workerUpload / feed request
- Photo API + worker → Metadata databaseSave posts; query authors; check access
- Photo API + worker → Private media storeProxy bytes / prepare variants
- Photo API + worker → Photo clientAuthorized metadata / display bytes
Trace the diagram in words
- The image worker verifies upload u81 and prepares the required sizes.
- The database publishes p81 only if the current processing version is complete.
- For Ben’s feed, the API merges a limited number of followed-author posts and checks current approval.
- Keep the original post and follow records together in one metadata database.
- Run durable media jobs alongside the API and merge followed authors on reads.
The cost: The baseline is easy to repair but heavy image work competes with feed requests and every feed repeats author queries.
Why change it? The calculated 50 TB/day of displays and slow image transforms should not occupy feed-serving capacity.
Stage 2: media bytes bypass the feed API
Follow the numbered arrows
- Photo client → Photo APIReserve / finalize / feed request
- Photo API → Metadata databaseRead current posts, tasks and permission
- Photo client → Private media storeScoped original upload
- Media workers → Metadata databaseClaim job / publish ready version
- Media workers → Private media storeVerify original / write variants
- Private media store → Media delivery edgeControlled origin fill
- Media delivery edge → Photo clientDisplay-size image
- Photo API → Photo clientMetadata + scoped media access
Trace the diagram in words
- Mira uploads bytes directly to her reserved storage location using a temporary credential.
- An image worker verifies the full required set and conditionally updates the current post’s publication state.
- The API checks Ben’s current approval before returning media access; the delivery edge then serves the image bytes.
- Move image processing into separate workers that claim saved jobs for a limited time.
- Let clients upload only to their reserved storage locations, and serve ready variants through nearby delivery caches.
Inspect the connection changes
- Now: Photo client → Photo API: Reserve / finalize / feed request
- Now: Photo API → Metadata database: Read current posts, tasks and permission
- Now: Photo client → Private media store: Scoped original upload
- Now: Media workers → Metadata database: Claim job / publish ready version
- Now: Media workers → Private media store: Verify original / write variants
- Now: Private media store → Media delivery edge: Controlled origin fill
- Now: Media delivery edge → Photo client: Display-size image
- Now: Photo API → Photo client: Metadata + scoped media access
- Previously: Photo client → Photo API: Upload / feed request
- Previously: Photo API → Metadata database: Save posts; query authors; check access
- Previously: Photo API → Private media store: Proxy bytes / prepare variants
- Previously: Photo API → Photo client: Authorized metadata / display bytes
All other connections are retained. Response paths are implicit unless drawn.
The cost: Separate workers and delivery caches need job recovery, protection against too many origin fetches, and expiry rules for media credentials.
Why change it? Repeated followed-author queries cost too much per feed request. But preparing ten million home-list entries for every large-author post would overwhelm the background-write budget.
Stage 3: candidate index plus large-author merge
Follow the numbered arrows
- Photo client → Photo APIReserve / finalize / feed request
- Photo API → Metadata databaseRead current posts, tasks and permission
- Photo client → Private media storeScoped original upload
- Media workers → Metadata databaseClaim job / publish ready version
- Media workers → Private media storeVerify original / write variants
- Private media store → Media delivery edgeControlled origin fill
- Media delivery edge → Photo clientDisplay-size image
- Photo API → Photo clientMetadata + scoped media access
- Metadata database → Feed workersDurable publication event
- Feed workers → Recipient home indexInsert each viewer/post pair once
- Photo API → Recipient home indexRead ordinary-author candidates
Trace the diagram in words
- Feed workers insert p81 into each active follower’s list; the unique viewer/post pair makes retries harmless.
- The API reads ordinary-author post IDs from Ben’s home index.
- It merges large-author IDs, loads current post records and checks approval before returning media access.
- Add recipient candidate lists and resumable feed workers for ordinary authors.
- For exceptionally large authors, merge recent author-index IDs on read; then fetch current posts and check visibility.
Inspect the connection changes
- Now: Metadata database → Feed workers: Durable publication event
- Now: Feed workers → Recipient home index: Insert each viewer/post pair once
- Now: Photo API → Recipient home index: Read ordinary-author candidates
All other connections are retained. Response paths are implicit unless drawn.
The cost: Feed reads save work at the cost of amplified background writes, lag, checkpoints and cleanup of stale candidate IDs.
07Evolve media delivery and feed generation
Deliver image bytes efficiently
- Prepare the sizes needed for thumbnails, small screens and large screens. Serve these through a content delivery network, which keeps reusable copies near viewers.
- Unchanged public images can have long cache lifetimes, with a stated deletion/purge policy.
- Private downloads require a fresh permission gate or temporary credentials within the revocation limit. A stale feed entry is not permission to download.
Prepare feeds for ordinary authors
- After publication, a relay reads the saved feed-update record and passes it to a background program that finds Mira’s active followers. If the relay crashes before saving its delivery confirmation, it may send the same update again.
- It inserts
p81into each follower's recent home list. Use a unique(viewerId,postId)pair so retrying an insertion cannot add the post twice. - Ben's next request reads that prepared list, loads current post details and checks permission before returning images.
- This is fan-out-on-write: one publication triggers many background feed writes, reducing later read work. Keep only a limited recent window, grouped by viewer.
Handle very large audiences differently
- One author with ten million followers can require ten million feed insertions for one photo.
- Keep that author's recent posts in an author index instead. When Ben requests his feed, merge a limited number of those posts into his prepared list.
- The hybrid combines prepared lists for ordinary authors with read-time merging for large audiences. Estimate feed insertions/second as the author’s posting rate × active followers. Use read-time merging when those insertions would exceed the workers’ tested capacity or make posts appear later than the feed’s freshness target; include the added author reads in the comparison.
08Choose IDs and metadata partitions
Choose capacity before partitioning
- Add database memory if that solves the current limit. Replicas can serve public metadata when a short delay is acceptable.
- Mira's immediate “published” confirmation must read a copy known to include her saved publication.
- Scale request handlers separately from image workers so resizing does not consume all feed-serving resources.
| Place records by | Benefit | Cost |
|---|---|---|
| authorId | One author's recent posts stay together | A popular author can overload its partition |
Random postId |
Spreads individual post rows | Need an author/time index to find one author's posts |
| Creation-time range | Efficient archive ranges | New traffic concentrates on the latest partition |
Retrieve records after finding IDs
- Mira's posts
p81andp82may live on partitions 2 and 7. An author/time index lists their IDs in order. - The feed handler groups those IDs by partition and fetches their current titles, visibility and image references in batches. This is sometimes called hydration: loading the details behind an ID.
- Map logical partitions to machines so selected groups can move during growth. A modulo directly tied to machine count can move many records on every resize.
Choose IDs separately from storage placement
- A database sequence issues distinct integers; assigned ranges can reduce calls to the issuer.
- Random IDs need duplicate handling and do not imply creation order.
- Time/worker/sequence IDs and
UUIDv7carry time information. For a time/worker/sequence generator, assign each worker a distinct worker ID, wait when its sequence for one clock tick is full, and stop or wait if its clock moves backward. These rules prevent reusing an earlier combination. Such IDs still do not establish one agreed publication order across all authors; use the index required by the listing query.
Find Mira’s recent photos even when their records are spread out
Follow the numbered arrows
- Mira author index → Post p81Resolve recent post ID
- Mira author index → Post p82Same author, different shard
- Post p81 → Load current post detailsCurrent metadata
- Post p82 → Load current post detailsCurrent metadata
Trace the diagram in words
- Read the author-time index to identify recent photos.
- Group the resulting IDs by their metadata partition.
- Load current post details and check who may see it before ranking or issuing downloads.
09Rank feeds and search without confusing their indexes
Rank eligible posts
- Ben follows Mira and Lee. Their posts
p81andp90are candidates: possible feed results, subject to current permission. - Start newest first. A later rule may combine age with Ben's prior interactions, so the slightly older
p90can rank abovep81. - Save the ordered post IDs for this browsing session, using post ID to order equal scores. The page cursor identifies that saved list and the last returned position. Later pages continue the same order instead of changing when new scores arrive.
Search titles separately
- A title index maps words such as garden to post IDs. This word-to-ID structure is an inverted index.
- Load the current post behind each matching ID and check permission before returning its title, snippet or media. An old index may still contain a deleted or newly private post.
Budget each cache for its own work
- One million 4 MB originals/day add 4 TB; display images adding 30% need another 1.2 TB.
- One billion 50 KB displays/day transfer 50 TB. Metadata caching cannot provide that media bandwidth.
- One million 1 KB metadata rows need 1 GB before overhead; 100,000 50 KB images need 5 GB. Measure repeated use separately for records and images.
Why stage 3 separates feed workers
- The source database stores posts and follow relationships. A separate home index holds prepared candidate IDs for each viewer.
- Feed workers save progress and use unique recipient/post insertions, allowing a crash to resume without duplicates.
- Reads combine prepared IDs with large-author IDs, load current records, check visibility and rank a limited set. These extra recovery and merging rules are the cost of reducing repeated author queries.
10Follow a photo to a viewer
- Upload: Mira sends her original under upload
u81. Workers verify it and create all required display sizes. - Publish: check the current processing version and task attempt, then save
p81as published with version 1 and its pending feed/search update in one database transaction. Both records survive an application crash; a late worker cannot restore a deleted post. - Index the title: a background program adds “Garden opening” to title search. Search still checks the current post and audience before showing a result.
- Prepare Ben's feed: insert
(Ben,p81)only if that pair is absent. A repeated publication event cannot create another entry. - Read the feed: combine Ben's prepared list with recent posts from exceptional large authors, fetch current details and check permission. Return the page with a cursor naming its saved post order and last returned position.
- Download: Ben fetches a prepared image large enough for his display dimensions through the delivery network. A small feed preview need not download the 4 MB original.
- Delete or revoke: Mira removes
p81or Ben's approval. Stop issuing new private-media credentials immediately according to the stated permission check. Previously issued private-image credentials expire within 60 seconds. A cached image must still require a valid credential; deleting a feed/search entry alone does not block its download. An image already downloaded to Ben’s device cannot be recalled.
Keep delays explicit
- Publication can finish before title search catches up.
- An old feed can still contain a deleted post's ID, so the read must check current state.
- Previously issued download credentials may work until expiry; already downloaded bytes cannot be recalled.
11Explain operation, abuse and trade-offs
- A required image fails: leave the post processing or failed, save which output is missing and retry within a limit. Do not publish broken required images.
- The prepared feed fails: a limited author-query merge can fall back, but cap source queries. Hundreds of new database reads per failed feed request could spread the outage.
- Deletion cleanup lags: save current deleted/private state first and block fresh credentials. Remove old feed/search entries in the background while filtering them during reads.
- Cleanup races processing: keep any object needed by active image tasks or retained versions. An unfinished upload is not automatically abandoned.
- Abuse: verify actual media type, enforce quotas, handle embedded location data deliberately and limit rapid follow changes.
- Measure: track request latency, publication time, queued feed-update age, permission denials, cache reuse and bytes delivered. Compare image-processing cost with repeated downloads, and prepared-feed writes with repeated author reads.
OPTIONAL DESIGN EXTENSIONWhere AI Fits in This Design
Use: draft an accessible photo description
- Input: one prepared image that passed the upload checks, after Mira agrees to send it to the caption service. Sending it for captioning does not make the photo public.
- Output: a suggestion such as “raised garden beds beside a path,” saved separately with post, media and model versions.
- Integration: run after media preparation, outside the 300 ms feed request. Optional caption failure must not block required display images from publishing.
- Author decision: Mira can accept or edit the description. The model cannot identify people without evidence, alter her title or expand the audience. An old-version result cannot replace a newer approved caption.
- Fallback: use creator-written text or honestly indicate that no description is available.
- Evaluation and limits: test whether descriptions name visible objects, omit useful details or invent identities, emotions or events that the image cannot establish. Limit image size, retries, queue delay and cost. Obtain permission before sending private images to a model, restrict who can access that processing service, and delete caption copies when the post’s retention rule requires it. A caption must not remain publicly readable after its source photo becomes private.
Microsoft's image-caption documentation illustrates possible outputs. Confidence is a review signal, not proof of correctness.
DRAW. CALCULATE. EXPLAIN.
Survive image-worker failure and a celebrity post
75 minutes · guided practice
Mira starts publishing posts p81 and p82, whose metadata is placed on database partitions 2 and 7. Ben follows her, searches for garden and requests a ranked home page. Only two of p82’s three required image sizes are ready. Ben then loses approval to view her private account, while old feed and search entries remain. Another author, Lee, posts to ten million followers.
- Draw how a complete post is published and indexed; explain why
p82must wait. - Show how the feed finds posts across partitions and checks Ben’s current permission.
- Choose which authors use prepared follower feeds and which are merged at read time; explain how this handles Lee’s burst.
- Calculate cache space for 1 million 1 KB metadata rows and 100,000 50 KB display images.
Compare the worked solution
- For each photo, save restartable tasks to prepare the required sizes.
p81can publish when all its outputs are verified and its processing version is still current.p82remains processing while its third size is missing. Save the publication and feed/search work in the same database transaction. - An author/time index lists Mira’s recent post IDs even when the posts live on different partitions. The feed groups IDs by partition to fetch current records in batches.
- Search finds possible IDs. Each result checks the original post’s current state and audience before returning its title or image.
- Ben’s prepared feed may retain
p81after approval is removed. Filter it during reads and stop fresh private-media credentials; issued credentials follow their short expiry. - Give ordinary authors unique viewer/post list entries so retries add no duplicates. Merge exceptional large authors at read time, preserving the page’s ordered ranking view.
- One million 4 MB originals add 4 TB/day; 30% extra display sizes add 1.2 TB. One billion 50 KB displays transfer 50 TB/day. One million 1 KB metadata rows need 1 GB raw cache; 100,000 50 KB display images need 5 GB.
The checkboxes record your own assessment. The worked solution gives you a way to check the reasoning.
RETRIEVE BEFORE YOU REVEAL
Keep the important ideas.
Say the answer aloud, then turn the card over.
CARD 01What connects a photo to the complete product?
- Verify the original and required display sizes.
- Publish one post with saved feed/search work.
- Copy post IDs into discovery lists.
- Load current post details and permission before returning media access.
CARD 02Why does random post sharding need another index?
Random post IDs spread rows but scatter an author’s photos.
- An author/time index finds recent IDs.
- Batch-fetch their current records by partition.
- Check permission before display.
CARD 03Why retain both feed and search checks after publication?
- A feed or search entry only identifies a possible result.
- Load the original post and check its current state and audience.
- Optional AI captions do not decide image readiness or permission.
CHECK THE REASONING
Can you explain the choice?
Answer every question, then finish for an immediate score and an explanation for each answer. Aim for 80%, then revisit any gaps.
CLOSE THE LOOP
Explain it without the page.
Summarise the mechanism, one trade-off, and one failure mode. Then mark this chapter complete.