#Design Pastebin / GitHub Gist
#1. Problem Statement & Clarifications
Pastebin / GitHub Gist is a content-sharing service that allows users to store and share plain text or code snippets via short, unique URLs. Users paste content, receive a shareable link, and anyone with the link can view the content. It is a deceptively simple system with interesting challenges around URL generation, storage optimization, and read-heavy traffic at scale.
#Functional Requirements
- Create Paste β Users submit text/code content (up to 10MB) and receive a unique short URL (e.g.,
paste.example.com/aB3xK9) - Read Paste β Anyone with the URL can view the paste content with syntax highlighting
- Paste Expiration β Support configurable TTL: 10 minutes, 1 hour, 1 day, 1 week, 1 month, never (default: never)
- User Accounts (Optional) β Registered users can view their paste history, edit, and delete pastes
- Visibility Controls β Public (searchable), unlisted (link-only), private (owner-only)
- Syntax Highlighting β Auto-detect or manually select language for code rendering
#Non-Functional Requirements
| Requirement | Target |
|---|---|
| Read Latency | P99 < 100ms for paste retrieval |
| Write Latency | P99 < 200ms for paste creation |
| Availability | 99.99% β shared links must always work |
| Scale | 5M new pastes/day, 50M reads/day, 10:1 read:write ratio |
| Durability | No data loss for non-expired pastes |
| URL Length | 6β8 characters (base62 encoded) |
#Out of Scope
- Real-time collaborative editing (Google Docs)
- Version control / branching / PRs (full GitHub)
- File uploads beyond text (images, binaries)
- Comments and social features (stars, forks)
#Assumptions
- Average paste size: 10KB; max: 10MB
- 80% of pastes are < 50KB
- 90% of reads are for pastes created in the last 24 hours (hot content)
- Paste content is immutable after creation (edits create new versions)
- Read traffic follows power-law distribution (few pastes get most views)
#2. Back-of-Envelope Estimation
#Traffic Estimates
New pastes/day: 5M
Reads/day: 50M (10:1 read:write)
Write QPS (avg): 5M / 86400 β 58 QPS
Write QPS (peak 3x): ~175 QPS
Read QPS (avg): 50M / 86400 β 580 QPS
Read QPS (peak 3x): ~1,750 QPS#Storage Estimates
Avg paste size: 10KB
New storage/day: 5M Γ 10KB = 50GB/day
New storage/year: 50GB Γ 365 = 18.25TB/year
5-year total: ~91TB (before expiration cleanup)
Active pastes (no-expire): ~60% of 5M/day Γ 365 Γ 5 = ~5.5B pastes
Metadata per paste: ~500B (URL, timestamps, user_id, settings)
Metadata storage: 5.5B Γ 500B = 2.75TB#URL Space Calculation
URL characters: Base62 (a-z, A-Z, 0-9)
6-char URL: 62^6 = 56.8 Billion unique URLs
7-char URL: 62^7 = 3.5 Trillion unique URLs
8-char URL: 62^8 = 218 Trillion unique URLs
At 5M pastes/day: 6 chars sufficient for 31+ years
β Use 6-char URLs, upgrade to 7 when needed#Bandwidth
Read bandwidth (avg): 580 Γ 10KB = 5.8 MB/s
Read bandwidth (peak): 1,750 Γ 10KB = 17.5 MB/s
Write bandwidth (avg): 58 Γ 10KB = 0.58 MB/s
Total: ~18 MB/s peak (very manageable)#Cache Estimates
Hot pastes (24h): 5M pastes Γ 10KB = 50GB
Top 20% (Pareto): 1M Γ 10KB = 10GB
Cache size: 10β50GB Redis cluster (easily fits in memory)
Cache hit ratio: 90%+ (power-law distribution)#3. API Design
#Create Paste
POST /api/v1/pastes
Authorization: Bearer <token> // optional β anonymous pastes allowed
{
"content": "def hello():\n print('Hello, World!')",
"title": "My Python Script", // optional
"language": "python", // optional, auto-detected if omitted
"expiration": "1d", // 10m | 1h | 1d | 1w | 1M | never
"visibility": "unlisted" // public | unlisted | private
}
Response 201:
{
"paste_id": "aB3xK9",
"url": "https://paste.example.com/aB3xK9",
"raw_url": "https://paste.example.com/raw/aB3xK9",
"created_at": "2025-05-29T12:00:00Z",
"expires_at": "2025-05-30T12:00:00Z",
"visibility": "unlisted",
"language": "python"
}#Read Paste
GET /api/v1/pastes/{paste_id}
Response 200:
{
"paste_id": "aB3xK9",
"title": "My Python Script",
"content": "def hello():\n print('Hello, World!')",
"language": "python",
"created_at": "2025-05-29T12:00:00Z",
"expires_at": "2025-05-30T12:00:00Z",
"visibility": "unlisted",
"view_count": 342,
"author": { // null for anonymous
"username": "john_doe",
"user_id": "U123"
}
}
Response 404: { "error": "paste_not_found" } // expired or invalid
Response 403: { "error": "private_paste" } // private + not owner#Get Raw Content
GET /api/v1/pastes/{paste_id}/raw
Content-Type: text/plain
def hello():
print('Hello, World!')#Delete Paste (Owner only)
DELETE /api/v1/pastes/{paste_id}
Authorization: Bearer <token>
Response 204: No Content#List User's Pastes
GET /api/v1/users/{user_id}/pastes?page=1&limit=20&sort=created_at_desc
Response 200:
{
"pastes": [
{ "paste_id": "aB3xK9", "title": "My Python Script", "language": "python",
"created_at": "...", "visibility": "unlisted", "view_count": 342 }
],
"total": 47,
"page": 1
}#4. Data Model
#Paste Metadata Store (PostgreSQL β sharded by paste_id)
CREATE TABLE pastes (
paste_id VARCHAR(8) PRIMARY KEY, -- base62 encoded short URL
user_id BIGINT NULL, -- NULL for anonymous pastes
title VARCHAR(255) NULL,
language VARCHAR(50) NULL,
content_size INT, -- bytes
content_hash VARCHAR(64), -- SHA-256 for dedup
visibility ENUM('public','unlisted','private') DEFAULT 'unlisted',
expires_at TIMESTAMP NULL, -- NULL = never expires
created_at TIMESTAMP DEFAULT NOW(),
view_count BIGINT DEFAULT 0,
is_deleted BOOLEAN DEFAULT false
);
CREATE INDEX idx_pastes_user ON pastes(user_id, created_at DESC)
WHERE user_id IS NOT NULL;
CREATE INDEX idx_pastes_expiry ON pastes(expires_at)
WHERE expires_at IS NOT NULL AND is_deleted = false;
CREATE INDEX idx_pastes_public ON pastes(created_at DESC)
WHERE visibility = 'public' AND is_deleted = false;#Paste Content Store (Object Storage β S3 / MinIO)
Bucket: paste-content
Key: {paste_id} -- e.g., "aB3xK9"
Body: <raw paste content, compressed with zstd if > 1KB>
Metadata:
content-type: text/plain
content-encoding: zstd // if compressed
original-size: 10240Why S3 over database?
- Paste content can be up to 10MB β storing large blobs in PostgreSQL hurts query performance
- S3 offers 11 nines durability at low cost (~$0.023/GB/month)
- Content is immutable β perfect fit for object storage
- CDN integration is straightforward
#Content Deduplication (Optional β for popular content)
-- If same content is pasted multiple times, share the storage
CREATE TABLE content_store (
content_hash VARCHAR(64) PRIMARY KEY, -- SHA-256
s3_key VARCHAR(100), -- actual S3 object key
ref_count INT DEFAULT 1,
content_size INT,
created_at TIMESTAMP
);#URL Key Store (Redis β cache layer)
Key: paste:{paste_id}
Value: {
"title": "My Python Script",
"language": "python",
"content_size": 10240,
"visibility": "unlisted",
"user_id": "U123",
"s3_key": "aB3xK9",
"expires_at": 1716984000,
"created_at": 1716897600
}
TTL: matches paste expiration, or 24h for non-expiring pastes (LRU eviction)#Access Patterns
| Query | Store | Index/Key |
|---|---|---|
| Get paste by ID | Redis cache β PostgreSQL | PK: paste_id |
| Get paste content | S3 (via CDN/CloudFront) | Key: paste_id |
| List user's pastes | PostgreSQL | IX: user_id + created_at DESC |
| Browse public pastes | PostgreSQL | IX: visibility + created_at DESC |
| Cleanup expired pastes | PostgreSQL | IX: expires_at |
| Check content dedup | PostgreSQL | PK: content_hash |
#5. High-Level Design (HLD) & Scale Evolution
#Stage 1: MVP / Single Server (1K pastes/day)
- Architecture: Single monolith (Node.js / Django). Paste content stored directly in PostgreSQL
TEXTcolumn. No cache. - URL Generation: Auto-increment ID β base62 encode.
- Bottleneck: Large pastes degrade DB performance. Single server = single point of failure.
#Stage 2: Growth Scale (500K pastes/day)
- Key Changes:
- Move content to S3 β separate metadata (PostgreSQL) from content (object storage).
- Introduce Redis for paste metadata caching (90%+ cache hit).
- CDN for raw paste content delivery.
- Pre-generate unique IDs to avoid auto-increment bottleneck.
- Bottleneck: Single PostgreSQL instance becomes write bottleneck. Need sharding or read replicas.
#Stage 3: Full Scale (5M pastes/day, 50M reads/day)
Target architecture with separated concerns and horizontal scalability.
#Architecture Diagram
ββββββββββββββββ
β CDN β β Raw paste content + static assets
β (CloudFront) β
ββββββββ¬ββββββββ
β
ββββββββββββΌβββββββββββ
β API Gateway / β
β Load Balancer β
β (Rate limit, Auth) β
ββββββββββββ¬βββββββββββ
β
ββββββββββββββΌβββββββββββββ
βΌ βΌ βΌ
ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ
β Read Service β β Write Serviceβ β Cleanup β
β β β β β Service β
β β’ Get paste β β β’ Create β β (Cron) β
β β’ List user β β β’ Delete β β β
β β’ Raw content β β β’ Validate β β β’ Expire β
β β β β’ Generate IDβ β β’ Purge S3 β
ββββββββ¬ββββββββ ββββββββ¬ββββββββ ββββββββ¬ββββββββ
β β β
ββββββΌβββββ βββββΌβββββ ββββββΌβββββ
β Redis β β Key β βPostgreSQLβ
β Cache β β Gen β β (query β
β β β Serviceβ β expired)β
ββββββ¬βββββ βββββ¬βββββ βββββββββββ
β β
ββββββΌββββββββββββββββΌβββββ
β PostgreSQL β
β (Metadata, sharded) β
ββββββββββββββ¬βββββββββββββ
β
ββββββββββββββΌβββββββββββββ
β S3 / MinIO β
β (Paste content blobs) β
βββββββββββββββββββββββββββ#Component Breakdown
| Component | Responsibility | Tech Choice |
|---|---|---|
| API Gateway / LB | Rate limiting, auth, routing, SSL | Nginx / Kong |
| Read Service | Fetch paste metadata + content; cache-first reads | Go / Node.js microservice |
| Write Service | Validate content, generate ID, store metadata + content | Go microservice |
| Key Generation Service | Pre-generate unique base62 keys | Standalone service with ZooKeeper ranges |
| Cleanup Service | Delete expired pastes (metadata + S3 objects) | Cron job / scheduled worker |
| Redis Cache | Paste metadata cache; hot paste content for small pastes | Redis cluster |
| PostgreSQL | Paste metadata, user data, dedup table | Sharded by paste_id hash |
| S3 | Paste content blobs | AWS S3 / MinIO |
| CDN | Serve raw paste content globally with low latency | CloudFront |
#Data Flow
Write Path (Create Paste):
Client β API GW β Write Service
β 1. Validate content (size < 10MB, rate limit check)
β 2. Fetch pre-generated unique paste_id from Key Generation Service
β 3. Compute SHA-256 hash of content
β 4. Upload content to S3 (key = paste_id, compress if > 1KB)
β 5. Insert metadata into PostgreSQL
β 6. Optionally cache in Redis
β 7. Return paste_id + URL to clientRead Path (Get Paste):
Client β CDN (cache hit for raw content? β return)
β API GW β Read Service
β 1. Check Redis cache for metadata (hit β skip DB)
β 2. Cache miss β query PostgreSQL
β 3. Check if expired or deleted β 404
β 4. Check visibility (private β verify owner)
β 5. Fetch content from S3 (or CDN cache)
β 6. Populate Redis cache
β 7. Increment view_count (async, batched)
β 8. Return paste with metadata + contentCleanup Path (Expiration):
Cron (every 5 min) β Cleanup Service
β 1. Query PostgreSQL: SELECT paste_id, s3_key FROM pastes
WHERE expires_at < NOW() AND is_deleted = false LIMIT 1000
β 2. Batch delete S3 objects
β 3. Mark as is_deleted = true in PostgreSQL
β 4. Delete from Redis cache#6. Deep Dive β Core Components
#Key Generation Service β Detailed Design
The Problem: We need globally unique, short, URL-safe IDs at 175 QPS peak. Options:
| Approach | Pros | Cons |
|---|---|---|
| Auto-increment + Base62 | Simple, sequential | Predictable URLs (security risk), single DB bottleneck |
| Random Base62 | Non-predictable | Collision risk; must check DB each time |
| UUID β Base62 | Globally unique | 22+ chars (too long for short URLs) |
| Pre-generated Key Ranges | Fast, no collision, short | Extra service to manage |
Chosen: Pre-generated Key Range Service
βββββββββββββββββββββββββββββββββββββββββββββββββββ
β Key Generation Service β
β β
β 1. On startup: generate 1M random 6-char base62 β
β keys and store in a KV table β
β β
β 2. Each app server requests a batch of 1000 keysβ
β β marks them as "assigned" in the table β
β β
β 3. App server uses keys from local pool β
β β no DB lookup needed per paste creation β
β β
β 4. When pool < 200 keys β request new batch β
β β
β ZooKeeper coordinates which ranges are assigned β
β to which server to prevent overlap β
βββββββββββββββββββββββββββββββββββββββββββββββββββKey table:
CREATE TABLE pre_generated_keys (
key_value VARCHAR(8) PRIMARY KEY,
status ENUM('available', 'assigned', 'used') DEFAULT 'available',
assigned_to VARCHAR(100) NULL,
used_at TIMESTAMP NULL
);Why not hash-based? MD5/SHA of content β base62 truncation has collision risk at 6 chars. Pre-generation guarantees uniqueness with zero runtime collision checks.
#Read Service β Detailed Design
Cache-First Strategy:
Read Request β Redis GET paste:{id}
β HIT: Check expiration β return metadata
Fetch content from CDN/S3
β MISS: PostgreSQL SELECT β check exists + not expired
Fetch content from S3
SET in Redis with appropriate TTL
Return to clientView Count Optimization:
View counts at 1,750 QPS would create hot rows in PostgreSQL if updated synchronously.
Strategy: Buffer in Redis β batch flush to PostgreSQL
1. INCR paste:views:{paste_id} in Redis (atomic, < 0.1ms)
2. Every 60 seconds, cron reads all paste:views:* keys
3. Batch UPDATE pastes SET view_count = view_count + N
4. Delete Redis counter keys after flush#Content Compression β Detailed Design
Upload Pipeline:
Content (raw) β Size check
< 1KB β Store as-is (overhead of compression not worth it)
1KBβ1MB β Compress with zstd (level 3) β ~60% size reduction
> 1MB β Compress with zstd (level 1) β fast compression for large content
S3 object metadata: content-encoding = "zstd" or "identity"
Read Pipeline:
S3 GET β Check content-encoding header
"zstd" β decompress before serving
"identity" β serve as-is
CDN caches the decompressed versionStorage savings: With 60% compression on 80% of content:
Before: 50GB/day
After: 50 Γ 0.2 (uncompressed) + 50 Γ 0.8 Γ 0.4 (compressed) = 10 + 16 = 26GB/day
Savings: ~48% β $$ at 18TB/year scale#Scaling Strategy
| Component | Strategy |
|---|---|
| Read Service | Stateless; horizontal scale behind LB; scale based on read QPS |
| Write Service | Stateless; scale independently from reads (10x fewer writes) |
| PostgreSQL | Hash-shard by paste_id across 4β8 shards; read replicas per shard |
| Redis | Cluster mode, 50GB across 10 nodes; LRU eviction for paste cache |
| S3 | Infinite scale; no sharding needed; lifecycle policies for cost optimization |
| CDN | CloudFront edge caches for raw content; 24h TTL for popular pastes |
| Key Gen Service | Single leader with standby; pre-generates keys in background |
#Caching Strategy
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Layer 1: CDN (CloudFront) β
β β’ Raw paste content (TTL: 24h for public/unlisted) β
β β’ Static assets (syntax highlighting JS/CSS) β
β β’ ~80% of read traffic served from edge β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β Layer 2: Redis (Metadata + Small Content) β
β β’ Paste metadata (TTL: matches expiration or 24h) β
β β’ Small paste content < 50KB inline in cache β
β β’ 90%+ hit rate due to power-law access pattern β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β Layer 3: Application-Level β
β β’ Key pool (pre-generated IDs in local memory) β
β β’ Language detection model weights (in-process) β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ#Consistency Models
| Data | Model | Rationale |
|---|---|---|
| Paste content | Strongly consistent (write-once, immutable) | Content never changes after creation; S3 provides read-after-write consistency |
| Paste metadata | Eventually consistent (< 5s) | Redis cache may briefly serve stale metadata; acceptable |
| View counts | Eventually consistent (< 60s) | Buffered in Redis, flushed every minute; exact count not critical |
| Expiration | Eventually consistent (< 5 min) | Cron runs every 5 min; paste may be accessible briefly past expiry |
#7. Low-Level Design β Core Functionality
#Key Algorithms
1. Base62 Encoding
CHARSET = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
def base62_encode(num):
"""Convert integer to base62 string."""
if num == 0:
return CHARSET[0]
result = []
while num > 0:
result.append(CHARSET[num % 62])
num //= 62
return ''.join(reversed(result))
def base62_decode(s):
"""Convert base62 string back to integer."""
num = 0
for char in s:
num = num * 62 + CHARSET.index(char)
return num
# Example: base62_encode(1000000) β "4c92"
# 6-char range: 0 to 56,800,235,5832. Paste Creation Flow
def create_paste(content, title, language, expiration, visibility, user_id):
# 1. Validate
if len(content) > 10 * 1024 * 1024: # 10MB
raise PayloadTooLarge()
# 2. Get unique ID
paste_id = key_pool.get_next() # from pre-generated pool
# 3. Content processing
content_hash = sha256(content)
compressed = zstd_compress(content) if len(content) > 1024 else content
encoding = "zstd" if len(content) > 1024 else "identity"
# 4. Detect language if not specified
if not language:
language = detect_language(content) # heuristic / ML
# 5. Upload to S3
s3.put_object(
bucket="paste-content",
key=paste_id,
body=compressed,
metadata={"content-encoding": encoding, "original-size": len(content)}
)
# 6. Store metadata
expires_at = compute_expiry(expiration)
db.insert("pastes", {
paste_id, user_id, title, language,
content_size=len(content), content_hash,
visibility, expires_at
})
# 7. Cache metadata
redis.setex(f"paste:{paste_id}", ttl=86400, value=metadata_json)
return paste_id3. Language Auto-Detection (Heuristic)
LANGUAGE_PATTERNS = {
"python": [r"def \w+\(", r"import \w+", r"print\(", r"class \w+:"],
"javascript": [r"function\s+\w+", r"const \w+", r"=>\s*{", r"require\("],
"java": [r"public\s+class", r"System\.out", r"void\s+main"],
"sql": [r"SELECT\s+", r"FROM\s+", r"CREATE\s+TABLE", r"INSERT\s+INTO"],
"html": [r"<html", r"<div", r"<head>", r"<!DOCTYPE"],
"bash": [r"#!/bin/bash", r"\$\{?\w+\}?", r"echo\s+"],
}
def detect_language(content):
scores = {}
for lang, patterns in LANGUAGE_PATTERNS.items():
scores[lang] = sum(1 for p in patterns if re.search(p, content))
best = max(scores, key=scores.get)
return best if scores[best] >= 2 else "plaintext"#Design Patterns Used
| Pattern | Where | Why |
|---|---|---|
| Cache-Aside | Redis for paste metadata | Read-heavy workload; 90%+ cache hit; simple invalidation on delete |
| Pre-computation | Key Generation Service | Avoid runtime collision checks; zero-latency ID assignment |
| Write-Behind | View count buffering in Redis | Decouple hot-path reads from PostgreSQL writes |
| Content-Addressable Storage | SHA-256 dedup for paste content | Save storage when identical content is pasted multiple times |
| CQRS | Separate Read and Write services | 10:1 read:write ratio; scale independently |
| TTL-Based Eviction | Redis cache + S3 lifecycle + DB cleanup | Multi-layer expiration handling |
#8. Failure Handling & Edge Cases
#What Happens When X Fails?
| Failure | Impact | Mitigation |
|---|---|---|
| Redis down | Reads hit PostgreSQL directly (latency spike) | Read replicas absorb load; Redis is cache-only, not source of truth |
| PostgreSQL primary down | Writes fail; reads served from replicas | Auto-failover to standby; writes queued for < 30s |
| S3 down | Content unavailable | Multi-AZ S3 (99.999999999% durability); CloudFront serves cached content |
| Key Gen Service down | Can't create new pastes | Each app server has local pool of ~1000 keys; survives hours without key gen |
| CDN down | Latency increase for global users | Fallback to direct S3 reads; origin always available |
| Cleanup cron fails | Expired pastes remain accessible longer | Pastes checked at read time too (expires_at < NOW β 404); cron is best-effort |
#Edge Cases
- Paste bombs β User submits 10MB of repeated characters β Content-length validation + rate limit (10 pastes/min per IP for anon, 100/min for auth users)
- Malicious content β Pastes containing malware/phishing links β Content scanning (async); abuse reporting endpoint; automated takedown via keyword/URL blocklist
- Hot paste (viral link) β Single paste gets millions of reads β CDN absorbs traffic; Redis caches metadata; S3 handles the rest. No single point of failure
- Paste content as executable β User tries to upload binary disguised as text β Content-Type validation; reject non-UTF8 content
- URL enumeration β Attacker iterates base62 to find private pastes β Random (not sequential) key generation;
unlistedpastes not discoverable via enumeration (62^6 keyspace too large to brute-force) - Concurrent deletes β Owner deletes while reads in progress β Soft delete (is_deleted flag); reads check flag; S3 cleanup async
#9. Monitoring & Observability
#Key Metrics
| Metric | Target | Alert Threshold |
|---|---|---|
| Read P99 latency | < 100ms | > 250ms |
| Write P99 latency | < 200ms | > 500ms |
| Cache hit ratio (Redis) | > 90% | < 75% |
| CDN hit ratio | > 80% | < 60% |
| S3 upload error rate | < 0.01% | > 0.1% |
| Key pool remaining | > 500 per server | < 100 |
| Expired paste cleanup lag | < 10 min | > 30 min |
| Storage growth rate | ~50GB/day | > 100GB/day (abuse?) |
#Alerting Strategy
- P0 (Page immediately): Write service down, Key Gen Service depleted, S3 unreachable
- P1 (15 min response): Cache hit ratio collapse, read latency spike, PostgreSQL replication lag > 30s
- P2 (Next business day): Storage growth anomaly, cleanup cron stalled, CDN cache ratio declining
#SLAs / SLOs
Read API: 99.99% availability, P99 < 100ms
Write API: 99.95% availability, P99 < 200ms
Content Durability: 99.999999999% (11 nines, S3 guarantee)
Expiration Accuracy: Pastes removed within 10 min of expiry#10. Trade-off Summary
| Decision | Chose | Over | Because |
|---|---|---|---|
| Content storage | S3 (object storage) | PostgreSQL BYTEA / TEXT | 10MB pastes degrade DB; S3 is cheaper, more durable, CDN-friendly |
| URL generation | Pre-generated key pool | Hash-based / auto-increment | No collision risk, no sequential guessing, zero runtime overhead |
| URL length | 6 characters (base62) | 8+ characters | 56.8B keyspace is sufficient for 31+ years at 5M/day; shorter URLs are better UX |
| Metadata DB | PostgreSQL (sharded) | MongoDB, DynamoDB | Relational queries for user paste lists, public browse; partial indexes for efficiency |
| Cache strategy | Cache-aside (Redis) | Write-through | Read-heavy (10:1); cache misses are rare and tolerable; simpler invalidation |
| View counting | Buffered in Redis, batch flush | Synchronous DB update | Avoids hot row contention at 1,750 QPS; exact count not critical |
| Compression | Zstandard (zstd) | Gzip, LZ4 | Best compression ratio for text at acceptable CPU cost; 48% storage savings |
| Expiration | Cron-based cleanup + read-time check | DB TTL / scheduled deletes | Lazy + active cleanup covers all cases; no expired paste served |
#11. Extensions & Follow-ups
#What Would You Add With More Time?
- Paste Forking β Create a new paste based on an existing one (like GitHub Gist forks), tracking lineage
- Diff View β Compare two pastes or paste versions side-by-side
- Collaborative Pastes β Real-time collaborative editing using OT/CRDT (moves toward Google Docs territory)
- API Keys & Programmatic Access β CLI tool (
paste-cli create < file.py) with API key auth - Paste Collections β Group related pastes into a collection (like multi-file Gists)
- Full-Text Search β Elasticsearch index for public pastes; search by content, title, or language
- Burn After Reading β One-time view pastes that auto-delete after first read (encrypted at rest)
#How Would This Change at 100x Scale?
- 500M pastes/day: Shard PostgreSQL across 64+ nodes; move metadata to Cassandra for linear write scaling
- 5B reads/day: Multi-region CDN + regional Redis clusters; edge compute for metadata resolution
- Storage (9.1PB/5yr): S3 Intelligent-Tiering for automatic cost optimization; aggressive compression + dedup
- Key generation: Distributed Snowflake-style IDs replacing centralized key service
#12. Cross-References
#Related Topics in This Repo
| Topic | Connection |
|---|---|
| URL Shortener (#1) | Nearly identical URL generation and key management problem |
| Unique ID Generator (#4) | Key Generation Service is a specialized unique ID system |
| Distributed Cache (#15) | Redis cluster design underpins paste metadata caching |
| Web Crawler (#12) | Paste content indexing for search shares parsing patterns |
#Building Blocks Used
- Load Balancer β L7 routing for read/write service separation
- PostgreSQL β Paste metadata, user accounts, dedup table (sharded)
- Redis β Metadata cache, view count buffering, key pool tracking
- CDN β Raw paste content delivery at edge; static asset serving
- S3 β Paste content blob storage with 11 nines durability
#Concepts Used
- CQRS β Separate Read and Write services for 10:1 read:write ratio
- Consistency Models β Strongly consistent writes, eventually consistent cache
- Sharding β PostgreSQL hash-sharded by paste_id