System Design Case Study
Text snippet sharing
A teammate dumps a failing stack trace into Pastebin or a Gist, hits create, and pastes a short link into Slack. Seconds later, half the channel opens it. Creates must be safe; reads must stay fast even when one paste goes viral.
Text snippet sharing is a short link plus stored text. Mint a , put bytes in an , enforce and , and scan for abuse without blocking every create.
Create → share journey
Draw a unique paste key from a pool.
Small body via API; large via presigned upload.
Expiry, language, visibility, pending scan.
Cache hot pastes; respect expiry and takedowns.
Try it: create and read pastes
Creates allocate an id and store content; reads should hit cache for popular pastes and honor expiry.
- Client
- API
- ID service
- Object/DB
- Cache
- Creates
- 0
- Reads
- 0
- Mode
- create
- Path hop
- 0
IDs
unique0 pastes created.
Reads
cache0 reads.
Expiry
TTLExpired pastes must 404 even if a CDN edge is stale.
Create pastes to allocate IDs, then read through the cache tier.
What actually happens
On create, the API rate-limits, draws a from the , and stores content. Small pastes go through the app; large ones use a so megabytes skip your servers. Metadata lands in a DB with , language, and .
runs asynchronously: spam, malware, leaked secrets. Until cleared, keep limited visibility and invalidate caches on state change. Reads check cache → metadata (auth + expiry) → object bytes, then warm the cache for a .
Syntax highlighting stays client-side on raw text so the server never stores executable HTML. That is how you avoid XSS in a paste product.
How to choose the paste design
1. How big can a paste be? Under ~1MB, API upload is fine. Near 10MB, prefer and quotas so one user cannot fill a day of storage.
2. Who can read it? Public, unlisted, and private are different models: private needs real auth, not just an obscure key.
3. What if it goes viral or toxic? Plan for a cache path and for to invalidate that cache on takedown.
Simple rule of thumb
Steal the URL-shortener key story (), then add object storage, expiry cleanup, and abuse scanning. Never render untrusted HTML from paste content on the server.
In simple terms: short link, safe storage, fast reads, and a plan for both viral and malicious pastes.
Case-study upgrade
Text Snippet Sharing Math
Text snippet sharing is a short code plus stored content. It is like link shortening, but large content, expiry, scanning, and rendering safety matter.
Assumptions and formulas
- Writes: 10M/day. Reads: 50M/day. Average paste: 10 KB. Accepted max: 10 MB.
- Metadata and content are stored separately; large content uses direct upload.
- Abuse scanning can be asynchronous unless policy requires blocking.
Concrete math
- Writes: 10M/day = 116/sec average; peak can be 500 to 2K/sec.
- Reads: 50M/day = 579/sec average; peak about 2K/sec.
- Storage: 10 KB x 10M/day = 100 GB/day; 5 years is 182.5 TB raw before replication/compression.
- Worst accepted max: 10 MB x 10M/day = 100 TB/day, so enforce quotas and direct upload.
- Cache: 1M hot snippets x 10 KB = 10 GB raw; with replicas and overhead, plan 30 to 80 GB.
Design decisions forced by numbers
- Keep metadata in a database and content in object storage.
- Use direct upload for large snippets and commit metadata only after content upload succeeds.
- Do not let syntax highlighting or scanning block reads unless policy requires it.
Scaling to millions
- At 1M snippets: one database and object store are enough.
- At 100M snippets: add cache, expiry cleanup, direct upload, and scan workers.
- At billions of snippets: shard metadata, tier old content, and isolate abuse queues.
Readable flow
Interview follow-ups to expect
- What maximum size is allowed, and why?
- What happens if object upload succeeds but metadata commit fails?
- Should scanning block creation or quarantine later?
Operational readiness checklist
After the math, explain how this capacity plan is measured, protected, launched, and evolved.
Observability
- Attach each capacity estimate to a dashboard metric: p99 latency, queue age, cache hit rate, shard skew, error rate, and freshness lag.
- Alert on rate-of-change and saturation, not only hard failures; backlogs and hot keys usually warn before an outage.
Security and abuse
- Budget for auth checks, rate limits, quota isolation, audit logging, privacy filters, and abuse-review side paths.
- Separate trusted internal events from user-controlled input so retries and backfills cannot bypass policy checks.
Degraded modes
- Define the fallback when a dependency is slow: stale cache, partial results, read-only mode, delayed async work, or admission control.
- Protect the invariant first; reduce freshness, personalization, or analytics quality before risking duplicate or corrupt state.
Testing and rollout
- Test average, peak, and hot-object traffic; include retry storms, cold caches, lagging workers, and regional dependency loss.
- Use canaries, shadow reads, dual-write validation, feature flags, and explicit rollback criteria for risky path changes.
Rollout, backfill, and runbooks
- Make migrations and backfills idempotent, resumable, rate-limited, and visible through progress and error metrics.
- Write the runbook before launch: owner, dashboards, paging thresholds, mitigation switches, replay steps, and repair commands.
10x evolution
- For text snippet sharing math, state which number forces the next move: cache memory, shard count, write amplification, queue lag, egress, or failover reserve.
- Add the next layer only when the metric forces it: sharding, regional reads, async replay, hot-key isolation, or stricter backpressure.
Scope you should nail down
Product surface
- Create paste: text content, up to 10MB
- Generate unique URL (short key)
- Read paste by URL
- Optional expiration (1h, 1d, 1w, never)
- Syntax highlighting (multiple languages)
- Visibility: public, private, unlisted
- Optional: title, language selection
Scale targets
- 10M new pastes/day
- Read:write ratio 5:1
- Low latency: <100ms read
- High availability 99.99%
- Durable storage for content
Capacity estimation
Writes: 10M new pastes/day, avg 10KB per paste = 100 GB/day storage
Reads: Read:write 5:1 → 50M reads/day ≈ 580 reads/sec (peak ~2K QPS)
Storage (5 years): 10M × 365 × 5 = 18.25B pastes ≈ 180 TB
Read bandwidth: 500 GB/day (5× writes)
Hardening paste safety and scale
Capacity formulas and SLOs
10M writes/day = 116/sec average; 50M reads/day = 579/sec average, about 2K/sec peak. Average 10KB creates 100GB/day, but 10MB max would be 100TB/day, so quotas and direct uploads are mandatory. Target public read p99 under 100ms.
Metrics and degraded modes
Track hot paste cache hit rate, create p99, object upload failures, scan queue age, expired-read attempts, takedown latency, and orphan object count. If syntax highlighting or scans lag, serve raw text only when policy allows.
Security, privacy, and abuse
Escape all rendered output, disable HTML execution, scan for malware and leaked secrets, rate-limit anonymous creates, keep private paste authorization separate from unlisted keys, and make moderation state part of cache invalidation.
Rollout, replay, and 10x growth
Replay create events through a shadow scanner, canary new policy rules, backfill moderation_state for existing public pastes, and at 10x split metadata by key hash while tiering cold objects and pre-warming viral public reads.
Hard-part drill: paste abuse scanning
A burst of anonymous large pastes contains spam and leaked tokens. Accept writes into a pending or limited-visibility state, scan asynchronously with retry budgets, quarantine high-risk matches, invalidate cached reads on moderation change, and keep a replayable scan log for policy backfills.
Create and read paths
Read the diagram definition
sequenceDiagram
autonumber
participant U as User
participant A as API
participant M as Metadata DB
participant O as Object store
participant S as Abuse scanner
U->>A: createPaste(content)
A->>A: validate size + rate limit
alt small paste
A->>M: allocate key + insert metadata pending
A->>O: store content
else large paste
A->>U: presigned upload URL
U->>O: direct upload
U->>A: commit(key)
A->>M: insert metadata pending
end
A-->>U: 201 paste URL
A-->>S: enqueue scan (async)
S->>M: update moderation_stateMetadata row size ~200 bytes; 18.25B pastes over five years ≈ 3.65 TB metadata raw before indexes. Hot read cache: 1M pastes × 10 KB ≈ 10 GB raw, plan 30-80 GB with replicas.
3. 🔌 System API
createPaste(content, title?, language?, expiration?, visibility?, api_key?)content: text (max 10MB). expiration: 1h | 24h | 7d | 30d | never. visibility: public | private | unlisted. Returns: { paste_key, url, expires_at }
getPaste(paste_key)Returns: { content, title, language, created_at }. Returns 404 if expired or private and unauthorized
deletePaste(paste_key, api_key)Soft-delete or revoke paste (owner only)
4. 🗄️ Database Schema
Pastes (Metadata)
pastes
Users
users
Content storage: Raw content stored in object storage (or object storage). Metadata in relational database. content_hash = object storage key (e.g., pastes/{key}).
5. 🔑 Key Generation
KGS (Key Generation Service)
A key-generation service can reserve unused random identifiers in a table protected by a uniqueness constraint, then lease disjoint batches to application servers. A server consumes each reserved key at most once, even after a restart. Lost batches may waste identifiers, which is preferable to reissuing a link for unrelated content. For a simpler initial system, generate keys on demand and retry a failed conditional insert after a collision; a separate key service is an operational trade-off, not a requirement.
Base62, 8 Chars
Eight Base62 characters provide 62^8, or about 218 trillion, possible keys. That is the namespace size, not a promise of collision-free random generation: collisions become possible long before every key is used, so creation still needs a uniqueness check. Nor does a large namespace turn an unlisted link into authorization. Use an appropriate cryptographic random source and longer identifiers where guessing resistance matters; require authenticated access for private pastes.
6. 🏗️ Architecture
7. 📦 Content Storage Strategy
Separate metadata from content. Metadata in relational DB (fast lookups, indexing). Raw content in object storage (object storage). Cheaper, scales independently, supports large blobs.
Use a metadata row for ownership, visibility, moderation state, expiration, and the object key; place the text bytes in object storage. A read first decides whether the caller may access the current metadata state, then retrieves or serves cached bytes. This lets storage grow independently while keeping access decisions small and indexed.
If multiple pastes share a content-addressed object, deletion becomes reference management: deleting one paste must not remove another authorized paste’s bytes. Deduplication also creates privacy considerations across owners, so begin with independent objects unless measured storage savings justify the added lifecycle complexity.
8. 📖 Read Path
9. ✍️ Write Path
10. 🧹 Cleanup Service
Expiration is enforced on the read path using expires_at, even if cleanup workers are delayed. Otherwise an hourly cleanup job can leave a supposedly expired paste readable for almost another hour. Index expiration metadata and scan bounded batches or time buckets, rather than repeatedly scanning every historical paste.
After marking metadata inaccessible, invalidate cached public responses and enqueue object deletion. Deletion workers retry safely and record completion; a separate reconciliation job finds abandoned uploads with no committed metadata. If objects are shared by deduplication, delete physical bytes only after their last valid reference is removed.
11. 🎨 Syntax Highlighting
Client-Side
Use highlight.js or Prism.js in browser. Server returns raw content + language. Client renders highlighted HTML. No server CPU for highlighting.
Language Auto-Detection
Optional: use heuristics (file extension, keywords) or ML to detect language if not specified. Store detected language in metadata.
Raw and rendered content: keeping the original text simplifies storage, but does not itself prevent cross-site scripting. The browser must insert it as text or use a trusted renderer that escapes unsafe characters; any generated HTML requires an appropriate sanitization policy. Serve raw downloads with a safe content type and response headers so pasted markup cannot execute as a page on the application’s origin.
Rate Limiting & Security
Apply creation limits to both request count and uploaded bytes. Example allowances such as ten anonymous pastes per hour or one hundred authenticated pastes are product assumptions and should account for shared IP addresses, abuse patterns, and paid quotas. Reject oversized uploads before expensive processing and isolate asynchronous scanners with bounded concurrency.
Public pastes may appear in discovery and shared caches. Unlisted pastes are omitted from discovery but readable by anyone holding the link. Private pastes require an authenticated permission check on every access path, including raw downloads; avoid placing reusable credentials in URLs because they can enter logs and browser history. Moderation changes must revoke access through caches as well as the database.
Direct object storage Upload (Large Pastes)
For a large paste, create a pending upload record and issue a short-lived signed upload URL for its specific object key. Restrict the expected size and content type where supported. The client sends bytes directly to object storage, then asks the application to finalize the paste. The server verifies that the expected object exists and matches the allowed size and checksum before changing the record to a readable state.
Object storage and the metadata database do not participate in one ordinary transaction. If upload succeeds but finalization fails, retry finalization with the same upload identifier; if upload never finishes, expire the pending record and clean up its object. This explicit lifecycle prevents broken links and unbounded orphan storage.
Multipart uploads can retry individual failed parts without restarting a large transfer. Check integrity with an explicitly supported object checksum and verify how it is calculated for multipart objects. An ETag is an opaque validator; it is not universally the MD5 digest of the object and must not be treated as a generic integrity guarantee.
Idempotency: use client-provided idempotency key to avoid duplicate pastes on retry.
12. ⚠️ Bottlenecks
KGS single point of failure
Mitigation: KGS replicas, pre-fetch large batches, in-memory key pool per app server.
Hot pastes
Viral paste overwhelms cache/DB. Mitigation: aggressive in-memory cache, Content Delivery Network (CDN) for static content if we pre-render.
Large paste upload
10MB upload can timeout. Mitigation: chunked upload, direct object storage upload (presigned URL), async processing.
Abuse / spam
Mitigation: rate limiting, CAPTCHA for anonymous, blocklist for malicious content.
Short takeaway
- Metadata in relational database, content in object storage. Key from KGS (Base62, 8 chars).
- Read path: in-memory cache store → relational database → object storage. Cache with TTL.
- Write path: KGS key → object storage upload → relational database metadata → return URL.
- Cleanup: Cron job deletes expired pastes, soft then hard delete from object storage.
- Syntax highlighting: client-side (highlight.js), language in metadata.
- Separate metadata from content for cost and scale. KGS for key generation.
💡 Key Takeaways
Text snippet sharing design mirrors URL shortener for key generation (KGS, Base62) but adds large content storage. Separating metadata (relational database) from content (object storage) keeps reads fast and storage cheap. in-memory cache for hot pastes. Cleanup service for expiration. Client-side syntax highlighting avoids server load. Consider presigned URLs for direct object storage upload for large pastes.
Beginner to Advanced Design Path(open guidance)
Beginner answer
Create a paste key, store content and metadata, support expiry, and cache hot public reads.
Advanced answer
Discuss object storage for large content, orphan cleanup, secret scanning, abuse takedown, rendered-output sanitization, unlisted visibility, and expired cache invalidation.
Deeper walkthrough
text snippet sharing deeper walkthrough
Text snippet sharing looks simple, but production concerns include large content, expiration, abuse moderation, secret scanning, rendering safety, and public hot reads.
Requirement refinements
Clarify max paste size, anonymous creation, private versus unlisted links, expiry rules, syntax highlighting, and abuse handling.
State read behavior for expired, deleted, private, and moderated pastes.
API design details
Use create paste, get paste, delete paste, update visibility, and report abuse APIs with explicit status responses.
For large pastes, use direct upload or multipart upload and commit metadata only after content is durable.
Data model and storage
Store metadata separately from content; use object storage for large content and DB storage only when size is safely bounded.
Include expires_at, visibility, owner_id, content_hash, language, and moderation_state in metadata.
Scaling strategy
Cache hot public pastes and rendered views while ensuring expiry and moderation status are respected.
Use lifecycle policies for expired objects and garbage collection for orphaned uploads.
Reliability, recovery, and operations
If object upload succeeds but metadata commit fails, clean up orphaned content later.
Monitor create p99, hot paste cache hit rate, expired read attempts, moderation backlog, and object orphan count.
Deepening reference
Text Snippet Store: realistic production notes
Beginner mental model: A text snippet store is URL shortener plus content storage, expiration, privacy, and abuse moderation.
Production scope: The core is simple, but realistic systems need large paste handling, syntax highlighting, TTL cleanup, private/unlisted visibility, rate limits, and malware/secret scanning.
Choose what the first version must guarantee
Start by deciding which text snippet store capabilities are essential. Describe the main read and write operations before choosing infrastructure. For each operation, identify the stored records it uses and the condition that makes its result correct. A fast response is useful only if it satisfies that condition.
Next, separate work the user must wait for from work that can finish later. Estimate requests per second, storage growth, and the busiest object or customer. Use those estimates to choose one bottleneck to investigate. The calculations below are example workloads with stated assumptions; they are not measured limits of a particular product.
Core flow to walk through
Create paste: validate size, generate key, store metadata, write content to object store or DB.
Read paste: resolve key, permission/expiry check, cache hot content, return content or rendered view.
Cleanup: expire metadata and delete or lifecycle-manage content objects.
Production readiness
Text Snippet Store: ship, operate, and evolve
Now examine the same request flow during failures and deployments. Tie each operational control to the user behavior it protects.
Observability
Track text snippet store p95/p99 latency, error rate, saturation, and user-visible freshness separately for read, write, and async paths.
Alert on leading indicators such as queue age, hot-key skew, cache miss storms, reconciliation mismatches, and error-budget burn.
Security and abuse
Enforce authentication, authorization, rate limits, input validation, audit trails, and privacy checks at the service boundary.
Treat abuse controls as a first-class path: throttling, quota isolation, moderation hooks, revocation, and safe handling of sensitive data.
Degraded modes
Name what still works if cache, search, ranking, stream processing, object storage, or a regional dependency is slow.
Prefer stale-but-safe reads, partial results, async retry, admission control, and feature disablement over corrupting core state.
Testing and rollout
Load test average, peak, and hot-object traffic; add failure-injection for retries, timeouts, backlog growth, and dependency outages.
Ship with canaries, shadow reads or dual writes when useful, rollback switches, schema compatibility checks, and replay validation.
Runbooks
Document dashboards, paging thresholds, owner actions, rollback steps, data repair commands, and customer-impact language.
Keep backfills and replays idempotent, resumable, rate-limited, and observable so repair work does not create a second incident.
10x evolution
Say which metric forces the next architecture change: shard count, cache footprint, write amplification, queue lag, p99 latency, or regional failover reserve.
Evolve one bottleneck at a time: partition by access pattern, isolate hot tenants, regionalize reads, add replayable async pipelines, then harden multi-region behavior.
Hard parts worth deepening
Separate metadata from large content for flexible storage and caching.
Use object storage for large pastes and DB/text store for small hot pastes if needed.
Never trust client-side syntax highlighting for security; sanitize rendered output.
Secret scanning and abuse moderation are realistic production concerns.
Failure modes and recovery
Object store write succeeds but metadata fails; garbage collect orphan objects.
Expired paste remains cached; include expiry in cache TTL and check metadata on read.
Hot paste overloads DB; cache rendered/plain content and use CDN for public pastes.
Abusive paste goes viral; support takedown and blocklist by key/account/IP.
Signals to monitor
Paste-create p99 measures publication delay, while cache hit rate for hot pastes explains how much viral read traffic reaches storage. Treat creation and reading as separate capacity paths.
Expired-read attempts show how often old links are still requested. Takedown time measures how quickly moderation reaches caches, and orphan-object count identifies uploaded bytes that lack committed metadata and may need safe cleanup.
| Choice | Best when | Cost |
|---|---|---|
| Store content in DB | Small pastes and transactional simplicity. | DB grows quickly and serves large blobs poorly. |
| Store content in object storage | Large content and cheap storage matter. | Two-phase metadata/content handling. |
| Unlisted links | Low-friction sharing is the product. | Anyone with link can access unless auth is added. |
Try this scenario
Explain how you handle a 20 MB paste upload when the user refreshes midway through the request.
Primary sources
- RFC 9110: HTTP Semantics: GET/POST and caching for snippets.
- Amazon S3: Objects: blob body storage.
- AWS DynamoDB best practices: metadata key design.
Practice and further reading
Use these exercises to connect the lesson to a design decision. State your assumptions, trace what happens to one request, and explain the rule that must remain true even during a failure. That rule is the design's correctness invariant.
Focus for this lesson: Design immutable text objects with unpredictable IDs, optional encryption, exact expiry, abuse control, large-object storage, and deletion propagation.
Explain the decision
How do you prevent enumeration of private snippets?
Read the reasoning
Use high-entropy unguessable IDs, authorization for private objects, rate limits, and avoid treating obscurity as the only access control.
Apply it to a production scenario
A snippet expires while still cached at the edge. What is correct?
Read one approach
Bound cache TTL by expiry, use signed grants or origin validation, purge asynchronously, and deny after the authoritative expiry time.
Work through the numbers
Thirty million 8 KiB snippets/day retained 90 days need how much raw storage?
Read the worked calculation
About 20.6 TiB before replicas, indexes, compression, versions, and abuse/metadata.
Reason through a failure
What must delete/expiry preserve?
Read the recovery reasoning
No read path can continue authorizing the content after its authoritative terminal time, even if physical cleanup is asynchronous.
Sources and further study
Continue with Design Maps & Navigation.