Design complete systems / CHAPTER 49
Design a text-sharing service (Pastebin)
Publish shareable text, check private access and expiry, and separate body downloads from the database that stores paste details.
Builds on Design a URL shortener (TinyURL), Caching fundamentals, Database indexes, Redundancy and replication.
- Publish a paste only when both its saved details and body are ready.
- Reject expired pastes even when their bytes have not yet been deleted.
- Explain why knowing an unlisted URL differs from having private access.
01Functional requirements
A paste is a saved piece of text. Its share URL contains a token, such as n7Q2, which lets the service find the paste record.
FR1: Publish and retrieve text. Accept text up to a stated size limit and return a unique share URL. Retrying the same creation request must return the same paste. Readers can view or download the text; the service never runs pasted code.
FR2: Choose expiry and visibility. Let the author set an expiry date. Public pastes are openly readable. Unlisted pastes are not shown in discovery, but anyone with the link can read them. Private pastes require sign-in and permission.
FR3: Manage owned pastes. Let authors list, inspect and delete their pastes. Published text does not change in place; an edit creates a new version with its own stored body.
FR4: Inspect processing state. Show whether a paste is pending, processing, ready or failed. Optionally support images and thumbnails, but do not publish an image before validating its bytes.
Out of scope
- Collaborative editing and executing pasted code.
- Arbitrary attachments. Images are an optional extension.
- Custom aliases. The baseline generates share tokens.
Example: Mira publishes deployment note p81 at share path n7Q2. Ben can read it once its body is ready, provided it has not expired and its visibility allows him access.
02Nonfunctional requirements
Assume 50 million new pastes per 30-day month, an average body of 10 KB and 20 reads per paste. Use a peak read rate five times the calculated average. Confirm these assumptions with the interviewer.
NFR1: Read latency. At least 95% of primary-region metadata and permission checks finish within 200 ms. Downloading the body is measured separately.
NFR2: Read availability. Public reads meet 99.9% availability per calendar month at the agreed load.
NFR3: Publish complete, durable content. Publish only after verifying the required body bytes. Published content must survive one storage-node failure under the selected storage service's guarantees. An unfinished upload is not a published paste.
NFR4: Enforce expiry and deletion. Reject expired content even if cleanup is late. Stop serving deleted public content from caches within 60 seconds. State separate schedules for deleting stored objects and backups.
NFR5: Protect private pastes. Check current permission on each application read. If the service issues a temporary download URL, it expires within 60 seconds; immediate revocation instead needs a fresh access check on every download. Never share one account's private response with another through a cache.
NFR6: Bound and safely render content. Limit text to 1 MiB and optional images to 5 MiB, with account quotas. Verify actual file type and size. Render pasted text without executing it; an extension or matching hash does not prove content is safe.
NFR7: Keep share identities unique. Generate unpredictable tokens with a cryptographically secure random source and check uniqueness when saving. The short labels in this example are for explanation, not recommended token lengths. A private paste still needs permission checks. Never reassign an expired token.
NFR8: Recover interrupted creation. A retry resumes the same paste and its current processing state. Recovery checks incomplete work and either finishes publication or records a failure that can be repaired. Finding an object in storage is insufficient to publish it.
03Budget bodies and metadata separately
**Requests:**50 million creations ÷ 2,592,000 seconds in a 30-day month ≈ 19 creations/second. At 20 reads each, one billion reads/month ≈ 386/second. A fivefold peak gives 1,930 reads/second. A viral paste can still concentrate much more traffic on one body.
**Body storage:**50 million × 10 KB = 500 GB/month, or 6 TB/year before extra copies, versions and backups. The body is the submitted text itself.
Metadata: at 300 bytes for the owner, body location, expiry, visibility and state, 50 million rows add 15 GB/month before indexes. This small record is metadata: information about the paste, rather than its text.
Limits: a 1 MiB text limit bounds one request; an account quota bounds retained bytes; a rate limit bounds repeated requests. Millions of tiny pastes can still overload the database.
Design decisions
- Begin with indexed database lookups and measured application capacity.
- Consider separate object storage because bodies grow much faster than metadata.
- Cache popular public bodies when repeated downloads justify it. The creation count alone cannot predict cache savings.
04API Design: Requests, Responses and Retries
For text, identify one creation attempt with a request key. Save the paste and the result associated with that key so a retry returns the same paste:
POST /pastes
Idempotency-Key: note-7
Content-Type: application/json
{"text":"Restart worker B after checkpoint 81.","visibility":"unlisted","expiresAt":"2026-11-01T00:00:00Z"}
{"pasteId":"p81","sharePath":"/n7Q2","state":"ready","mediaType":"text/plain","version":1}
Creation results
201 Created: the paste is published and the original response has been saved.- Retry with the same owner, request key and content: return that saved response.
409 Conflict: the key was already used with different content. An optional custom-name feature would also use this status when a name is taken.415: the content type is unsupported.413: the content is too large.
Read and manage pastes
GET /pastes/p81checks permission, then returns the text and its details or a temporary download URL.GET /pastes?cursor=...lists the next page of the owner's pastes.DELETE /pastes/p81marks the paste as deleted and returns204.- For a private paste, the author uses
PUT /pastes/p81/permissionswith the allowedaccountIds. The handler verifies the author and replaces that paste’s reader rows in one transaction. A later private read checks this current list; a temporary body URL already issued still has the lifetime explained below. - Editing published text is a new
POST /pastesoperation with a new request key. It creates a new paste ID, body and share token; the original share URL continues to identify the original immutable edition. This baseline does not silently replace text under an existing token. - A share read returns
404when the paste is absent,410when it has expired,401when sign-in is required, or403when this account lacks permission.
Upload an image
- Call
POST /uploads. The response is{"uploadId":"u81","maxBytes":5242880,"putUrl":"<scoped-url>"}. - Send the image to that temporary URL. It accepts only the reserved upload and expires; it grants no permission to publish or read a private paste.
- Call
POST /pasteswith upload IDu81. The service verifies the completed bytes and returns202 Acceptedwhile image checks or thumbnail preparation continue. - Check the paste's state until it becomes ready. Finishing the upload alone does not publish it.
05Data Model and Storage Access Patterns
The share token is the public URL identifier; pasteId identifies the database record. Keep permission and publication state in metadata even when body bytes live elsewhere.
| Record and key | What it stores and why |
|---|---|
Paste, pasteId; unique share token |
Owner, media type, body location, length, hash, expiry, visibility, state and version; find one paste |
Owner index, (ownerId,createdAt,pasteId) |
Fetch one owner's pastes in pages |
Permission, (pasteId,accountId) |
Check who may read a private paste |
Create request, (ownerId,requestKey) |
Original text/upload identity, visibility, expiry and result; compare creation fields on retry without creating another paste |
Upload/job, uploadId or (pasteId,version,task) |
Track incomplete bytes, validation work, temporary ownership and recovery |
A lease gives a worker or upload permission to keep working until a deadline. It lets recovery distinguish active work from abandoned work. Neither knowing a pasteId nor knowing its body hash grants access to a private paste.
An image-processing job also stores its current attempt ID. When a worker finishes, the database checks that ID, the expected paste version and processing state before accepting readiness. A replaced worker cannot finish a newer job or turn a deleted paste back into a ready paste.
06Build the smallest complete service
Start with an application, a database for paste details and private object storage for the submitted text. Object storage saves a body under a key, much like a file path; the database decides whether readers may see it.
Publish Mira's note
- Mira submits text with request key
note-7. The application reserves paste recordp81as pending and saves the association withnote-7. - The application writes the text to
owner-M/p81/v1/body, a location belonging to this paste and version. Published bytes are never changed in place. - It verifies the stored length and hash against the intended bytes and completes required validation. In one metadata transaction, change
p81to ready only if its expected version and pending/processing state still match, and save the final creation result fornote-7. An image worker must also match the job’s current attempt ID. A deleted or replaced paste cannot be made ready by an old completion. - It returns the share URL, such as
https://paste.example/n7Q2. Heren7Q2is the share token used to findp81. It is only a readable example; real tokens need sufficient unpredictable length. - Ben opens that URL. The application finds
p81, checks ready state, expiry and permission, then returns the text using safe rendering that cannot execute it.
Recover a crash between the two stores
- The database and object store do not share one ordinary transaction. A body can exist while
p81is still pending. - A retry of
note-7finds the samep81. The application verifies the intended existing body and validation state before finishing publication; it does not create another paste or publish bytes merely because an object exists. - A background repair program queries an index of old pending records and retries unfinished work. An upload lease gives active work a deadline during which cleanup must keep its temporary bytes.
- After the upload deadline and an additional cleanup delay, the repair program checks that no active task or retained paste still needs the body. It marks the abandoned upload failed before deleting its temporary bytes, so a late completion cannot publish the deleted upload.
When is a simpler design enough?
- For a tiny text-only service, storing text in its database row allows one publication transaction and avoids two-store recovery.
- Separate bodies when their storage, backups or download traffic begin crowding out metadata work.
- Saved job records make repair survive application restarts. A callback held only in process memory does not.
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. First show when a paste becomes readable even though its body and publication record are saved in different stores.
Stage 1: metadata gates private bodies
Follow the numbered arrows
- Author / reader → Paste applicationCreate / authorized read
- Paste application → Metadata databaseReserve / publish / check state
- Paste application → Private object storeWrite / verify / read body
- Paste application → Author / readerReady text or explicit state
Trace the diagram in words
- Reserve p81 pending.
- Write and verify its body.
- Serve only a ready, active and permitted paste.
- Reserve a pending identity before writing bytes.
- Mark ready only after verification; retry the same reservation after a crash.
The cost: Two stores let bytes grow independently but require pending-state repair; tiny text could instead stay in one database row.
Why change it? A viral paste and repeated downloads dominate the body path even when monthly creation is modest.
Stage 2: public reuse without public origin access
Follow the numbered arrows
- Author / reader → Paste applicationCreate / authorized read
- Paste application → Metadata databaseReserve / publish / check state
- Paste application → Private object storeWrite / verify / read body
- Paste application → Author / readerReady text or explicit state
- Paste application → Public response cacheLookup / fill public response
- Public response cache → Paste applicationPermitted reusable text
Trace the diagram in words
- A public hit reuses only a still-permitted response.
- A miss asks the application for a permitted response backed by private storage.
- Deletion invalidates reuse and stops new credentials.
- Add caching for eligible public ready content.
- Expire cached responses within the deletion limit and paste expiry. Private requests still check current permission.
Inspect the connection changes
- Now: Paste application → Public response cache: Lookup / fill public response
- Now: Public response cache → Paste application: Permitted reusable text
All other connections are retained. Response paths are implicit unless drawn.
The cost: The cache adds invalidation and miss surges, and cannot turn the private bucket into an unrestricted bypass.
Why change it? Repairing pending uploads and cleaning expired pastes can consume the resources needed to serve current readers.
Stage 3: repair cannot starve foreground reads
Follow the numbered arrows
- Author / reader → Paste applicationCreate / authorized read
- Paste application → Metadata databaseReserve / publish / check state
- Paste application → Private object storeWrite / verify / read body
- Paste application → Author / readerReady text or explicit state
- Paste application → Public response cacheLookup / fill public response
- Public response cache → Paste applicationPermitted reusable text
- Repair / expiry worker → Metadata databaseClaim pending / expiry work
- Repair / expiry worker → Private object storeCheck references before deleting bodies
Trace the diagram in words
- Claim an old pending reservation.
- Verify the existing body before resuming publication.
- Delete a body only when no retained paste version or active upload still needs it.
- Move repair/expiry execution into independent bounded capacity.
- Keep durable work and publication state in metadata; verify references before physical deletion.
Inspect the connection changes
- Now: Repair / expiry worker → Metadata database: Claim pending / expiry work
- Now: Repair / expiry worker → Private object store: Check references before deleting bodies
All other connections are retained. Response paths are implicit unless drawn.
The cost: The separate workers need job deadlines, limited retries and alerts for old abandoned objects. Reads still reject expired content even if cleanup is behind.
07Compare storage, deduplication and compression
Calculate storage and delivery separately
- Our workload adds 500 GB of new text each month.
- Reading each body 20 times creates 10 TB/month of downloads before overhead.
- Caching 100,000 popular 10 KB bodies needs about 1 GB before overhead. Measure whether those bodies receive enough repeat reads to justify keeping them in memory.
| Choice | Benefit | Cost |
|---|---|---|
| Database body | Publish text and details in one transaction | Large text increases database storage, backups and read traffic |
| Object body plus database details | Scale bytes and metadata separately | Recover incomplete publication across two stores |
| Compression | Represent compressible text using fewer bytes | More CPU; already compressed images may barely shrink |
| Deduplication | Store one verified body for identical text | Keep separate paste identities, permissions, expiry and references |
How identical-body sharing works
- Mira submits the same note as
p81andp82with different expiries. A content hash helps find that their bytes are identical; verify the actual length and content before reuse. - Save two paste records, each referring to the same stored body. These references mean both pastes need that object.
- When
p81expires, remove its live reference. Keep the body whilep82, a retained version or an active upload still needs it. - Before deleting an apparently unused object, allow a grace period and check work that may be publishing another reference.
- Begin sharing bodies only within one owner's storage. Cross-owner existence checks or timing can reveal that another user stored particular text.
Add capacity deliberately
- Add database memory/storage first if sufficient; later add application copies and separate image-validation workers.
- Partition metadata by
pasteIdwhen one database reaches a measured limit, keeping an owner index for listing. - A single popular body needs cached or replicated delivery; more metadata partitions do not divide that one body's traffic.
Two pastes can share bytes without sharing expiry or permission
Follow the numbered arrows
- Paste p81 → Saved body referencesReference body h1
- Paste p82 → Saved body referencesIndependent paste metadata
- Saved body references → Verified unchanged bodyReuse verified owner bytes
- Delete p81 → Saved body referencesRemove one reference only
Trace the diagram in words
- Check the requested paste’s own permission and expiry.
- Two references preserve one body after deleting p81.
- Collect the body only after no retained version or active upload needs it.
08Add caching without bypassing access
Public reads
- Cache ready public text or serve unchanged public-body responses through a content delivery network.
- Give the cached copy an expiry no later than the paste’s own expiry or 60 seconds after the application checked the main database. Preserve that timestamp when copying an entry; copying old content must not give it another minute.
- Save deletion first, then request cache removal. A fixed lifetime still limits stale copies when removal is delayed.
- Keep the object bucket private. Otherwise a direct storage URL could bypass the application's deleted-state and permission checks.
Private downloads
- Check the reader's current permission before serving the body or issuing a temporary download URL.
- A signed URL lets its holder download until its expiry. It does not recheck the audience automatically and can be forwarded.
- Keep its lifetime within the promised 60-second revocation delay. Stopping new URLs cannot cancel one already issued.
- If revocation must be immediate, send each download through a service that checks permission again. Do not share one account's cached private response with another.
Metadata and cleanup
- Use a
pasteIdpartition for one-paste lookups and a separate owner index for listing. - Use an expiry index or scheduled batches to find cleanup work.
- A normal read checks that paste's expiry. It should not scan every object.
09Trace creation, reads and expiry
Follow a shared note from creation to deletion
- Mira submits
note-7. The saved request result identifies its one paste,p81, across retries. - The application writes and verifies the body, then marks
p81ready. Losing the response does not lose that saved result. - Ben opens
https://paste.example/n7Q2. The application usesn7Q2to findp81and checks readiness, expiry and any private permission. - The response renders text without executing it. A public cache can reuse a ready response only until its fixed deadline; private responses are not shared between accounts.
- Mira deletes
p81. Save a deleted-state marker first, stop issuing new download URLs, request public-cache removal and schedule physical cleanup. - Cleanup removes the body only when no other paste, retained version or active upload needs it. Keep the used share-token record so
n7Q2never becomes someone else's paste.
Explain the limits
- Expiry is checked while reading, even if cleanup is behind.
- A previously issued download URL can work until its short expiry. Immediate revocation needs a fresh check on every download.
- Backups have a separate erasure schedule.
- Move repair/cleanup to separate limited workers if it competes with user requests. Those workers must verify publication and references rather than guessing from object existence.
10Defend the content boundary and explain cost
Unsafe preview: return downloaded text as
text/plain, and escape it before inserting it into an HTML page. A pasted<script>must appear as text rather than execute in Ben’s browser.False file claims: verify the actual content type, length and uploaded bytes; a filename extension is insufficient. A matching hash proves agreement with expected bytes, not that the text is harmless.
Abuse: apply account storage quotas, request rate limits, size checks before and after upload, and an abuse-reporting path.
Incomplete publication: repair a pending record only after checking its intended object and validation. Do not expose an unfinished upload. Finalization checks the expected paste version/state and, for a worker job, the current attempt ID in the database update; it cannot overwrite deletion with ready.
Unsafe cleanup: deleting one paste must not delete bytes referenced by another paste or ongoing publication.
Operational checks: measure failed finalizations, abandoned-object age, expired-read denials, cache-removal delay and storage/download growth. These show where recovery, access or cost differs from the plan.
OPTIONAL DESIGN EXTENSIONWhere AI Fits in This Design
Use: help the author notice secrets before sharing
- Input: only the note text Mira explicitly asks to inspect, under her current permission. Do not silently send all private pastes to a model.
- Output: a proposed preview with suspected secret spans, suggested redactions and reasons. Keep the published original unchanged.
- Integration: use a separate preview request or saved background task. A suggestion identifies its paste/version/model so it cannot overwrite a different version.
- User decision: Mira confirms a new creation operation if she accepts changes. Normal publication, audience and expiry checks still apply.
- Fallback: retain ordinary secret-pattern checks and manual review when inference fails. A clean result does not prove harmless content; pasted instructions are never executed.
- Evaluation and limits: measure missed secrets, false warnings, useful text removed and secret leakage in explanations. Set byte limits, a maximum attempt count and a daily spending limit before enabling this preview. Stop waiting after two seconds and offer manual review. Keep detected secrets out of logs, and agree where note text may be processed and how long it may be retained.
Bedrock Guardrails gives an example of sensitive-information filtering. Such filtering supplements permission checks and safe text rendering.
DRAW. CALCULATE. EXPLAIN.
Recover publication and enforce an expiring private paste
75 minutes · guided practice
Mira creates pastes p81 and p82 from the same 10 KB note. Both point to body h1, but they have different expiry times. The first text-creation response is lost, and p81 expires while p82 is still active. Separately consider the optional image-upload path: its validation job fails before the image paste becomes ready.
- Draw creation, retry, image checking, reading and cleanup states.
- Show why expiring
p81must not delete the body still needed byp82. - Calculate raw cache space for 100,000 notes of this size.
- Calculate monthly delivery for 50 million notes read 20 times each.
Compare the worked solution
- Retry
note-7and resumep81rather than creating another paste. In the separate optional image path, a failed validation job leaves the image unreadable. Retry or record failure through its saved job state; only a matching current attempt can mark the expected paste version ready. - Keep
p81andp82as separate records with separate expiry and permission. Both may refer to the same verified owner-scoped body h1. - On
p81expiry, reject its reads and remove its live reference, but keep h1 whilep82still needs it. Also protect retained versions and active uploads during cleanup. - A temporary download URL already issued may work until expiry. Immediate revocation needs a fresh permission check on each download.
- 100,000 bodies × 10 KB = 1 GB before cache overhead. 50 million × 10 KB × 20 reads = 10 TB/month delivered, separately from 500 GB of new text stored each month.
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 01How is a paste published safely across two stores?
- Reserve one pending paste and save its request key.
- Write and verify the body.
- Mark the paste ready.
- A retry resumes that same paste; cleanup protects active uploads.
CARD 02What can deduplication share?
- Identical text can share one verified body object.
- Each paste keeps its own permission and expiry.
- Delete a body only after all paste, history and active-upload references no longer need it.
CARD 03What survives delayed physical cleanup?
- Check expiry and deletion during every read, even when cleanup is late.
- Never reassign a share token.
- An issued download URL may work until expiry; immediate revocation needs a live permission check.
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.