System Design Cases
Design Pastebin
Pastebin system design case study. Client posts text snippets via HTTPS through a CDN and Load Balancer to the paste-svc. paste-svc generates a base62 short ID (8 chars), stores the body in S3 under sharded prefixes, and persists metadata (lang, ttl, s3_key, visibility) in Postgres. Hot pastes are cached in Redis. Reads are served via CDN for /raw views and rendered HTML pages bypass the CDN to keep view counts accurate. A TTL cleanup cron sweeps expired pastes from S3, Postgres and Redis. ADRs: KV store choice (S3 + Postgres metadata), ID generation strategy (base62 hash with collision retry), CDN caching for raw views. Capacity: 10M DAU, 1M new pastes/day, 10:1 read:write, viral pastes 100x burst. Six scenarios: create paste, view with cache miss, raw CDN hit, viral burst absorbed by CDN+Redis, TTL cron cleanup, abuse rate limiting.
Pastebin-like text sharing without hidden consistency bugs
Requirements and assumptions
The service creates an immutable text paste, returns a short URL, serves rendered HTML and raw text, supports optional expiration and user deletion, and rejects abuse. Private pastes require authorization; an unlisted URL is not an authorization mechanism.
Exercise workload: 10M creates/month and 100M reads/month. For a 30-day month this is about 3.86 average writes/s and 38.6 average reads/s, not hundreds of requests per second. Peaks are separate assumptions: for example 20× create peak and a viral raw object at 10K reads/s. At 10KB average body size, raw new data is about 100GB/month and 6TB over five years before replication, versions, metadata and logs.
Correct short-ID math
Eight base62 characters give M = 62^8 = 218,340,105,584,896 possible values. With n = 600,000,000 created pastes, the expected number of colliding pairs is approximately n(n-1)/(2M) ≈ 824. The probability of at least one collision is therefore effectively 100%, not negligible.
The design uses 12 uniform base62 characters from an operating-system CSPRNG in every Paste Service replica. Then M = 62^12 ≈ 3.226 × 10^21 and the same five-year horizon gives expected colliding pairs about 5.58 × 10^-5, or about 0.0056% probability of at least one collision under the uniform model. This is small, not zero. A UNIQUE constraint is still the authority; on conflict the replica generates a fresh independent ID and retries a bounded number of times.
Hash(content + timestamp) is avoided: clocks and predictable inputs are not a substitute for entropy, identical content should not create accidental coupling, and local CSPRNG generation removes the central ID service SPOF. RFC 4086 explains why clock/limited-range sources are guessable.
Create state machine across SQL and object storage
SQL and S3-compatible storage do not share a transaction. The service therefore uses visible states:
- INSERT metadata with status PENDING and a unique random ID.
- PUT immutable body under that reserved ID and record checksum/version.
- Compare-and-set PENDING → ACTIVE.
- Readers require ACTIVE and expires_at > now before consulting origin cache or object storage.
If the process dies, a reconciler claims old PENDING rows. A missing object leads to ABORTED; a matching object can be activated only when all metadata is complete; an unexpected orphan is deleted. Operations are idempotent and keyed by paste ID plus object version.
S3 documents strong read-after-write behavior for a single object key, but no atomic update across keys. The application state machine is still required.
Read path and XSS boundary
On an origin read, metadata status, expiration and visibility are checked first. A cache key includes immutable object version so an old body cannot be confused with a recreated ID. Rendered content is escaped before highlighting, returned with a restrictive CSP, and raw content uses text/plain plus X-Content-Type-Options: nosniff.
The public raw path can be cached at the CDN. Its max-age is no greater than min(5 minutes, remaining paste lifetime). This is an explicit bounded stale-read trade-off, not an instant-delete guarantee.
Expiration and deletion include the CDN
The sweeper atomically changes ACTIVE → DELETING and inserts an outbox event. From that commit onward origin reads fail closed. An idempotent worker then invalidates every raw CDN path/cache variant, evicts Redis, deletes the exact object version and finalizes a DELETED tombstone. CDN invalidation is asynchronous and retried until its provider reports completion.
CloudFront documents that an object must be invalidated to remove it from edge caches before TTL, and that invalidations cannot be cancelled after submission. Browser or intermediary copies can outlive a purge. Therefore a product promising immediate hard delete must either perform an edge authorization/status check for each request or disable public caching for that class of content. This design promises a bounded public-cache window and exposes purge backlog as an SLO.
If object-store versioning is enabled, a simple DELETE can create only a delete marker; permanent erasure requires deleting the specified version and applying noncurrent-version lifecycle policy. The worker records version IDs and distinguishes logical unavailability from physical erasure.
Abuse and failure policy
- The load balancer rejects bodies above the configured maximum before buffering them fully.
- Anonymous create is limited by a composite risk key; account quotas and CAPTCHA supplement IP limits.
- Private content is never public-CDN cached. Malware/phishing scanning can quarantine ACTIVE transition or publication.
- A failed CDN purge keeps the row DELETING and alerts; it never marks cleanup complete optimistically.
- A failed object DELETE is retried idempotently. Reads remain unavailable because metadata was tombstoned first.
- Logs contain paste ID and state transition, never body text or secret access tokens.
Trade-offs
Putting bodies in Postgres may be simplest at MVP scale. Object storage reduces database/WAL pressure later but adds a cross-system state machine. Syntax highlighting can be lazy because many pastes are never viewed. Exact view counts conflict with CDN hits; use edge logs/analytics or accept approximate counters rather than bypassing CDN solely for a vanity number.
Связанные темы
Смотрите [CONCEPT]back-of-envelope, [CONCEPT]caching-patterns, [CONCEPT]rate-limiting-algorithms, [CONCEPT]sharding-strategies, [CASE]object-storage-s3 и [CASE]cdn-design. Практический cache-aside: Cache-Aside.
Первичные источники
- RFC 4086, randomness and problems with clocks: https://www.rfc-editor.org/rfc/rfc4086.html
- Amazon S3 data consistency model and lack of cross-key atomicity: https://docs.aws.amazon.com/console/s3/UsingObjects.html
- Amazon S3 DeleteObject versioning behavior: https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteObject.html
- Amazon S3 lifecycle and noncurrent-version expiration: https://docs.aws.amazon.com/AmazonS3/latest/userguide/intro-lifecycle-rules.html
- Amazon CloudFront invalidating cached content: https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Invalidation.html
- PostgreSQL INSERT and ON CONFLICT: https://www.postgresql.org/docs/current/sql-insert.html