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 кажется маленьким сервисом: пользователь вставляет текст, получает короткую ссылку, другие открывают ее в браузере или через curl. Именно поэтому кейс полезен для тренировки системного дизайна: в нем почти нет сложной бизнес-логики, зато хорошо видны базовые инженерные решения. Нужно выбрать, где хранить тело paste, как генерировать короткий идентификатор, как не сломать чтение при вирусной ссылке, как удалять истекшие записи и как защититься от спама.
В интервью этот кейс проверяет не знание конкретного Pastebin, а способность не переусложнять. При масштабе порядка миллионов новых записей в месяц не нужен распределенный граф или десятки микросервисов. Достаточно stateless application tier, Postgres для метаданных, object storage для тела paste, Redis для горячих чтений и CDN для raw-представления. Сложность возникает в деталях: HTML-страницы могут требовать точного счетчика просмотров, а /raw/<id> должен быть быстрым и кэшируемым; истечение срока должно удалять и метаданные, и объект; большой flood из 10 MB paste должен быть отрезан раньше, чем заполнит storage.
Этот дизайн напрямую связан с темами ::concept{slug="back-of-envelope"}, ::concept{slug="cdn-design"}, ::concept{slug="object-storage-s3"}, ::concept{slug="caching-strategies"}, ::concept{slug="rate-limiting"} и ::concept{slug="sharding"}. Он также хорошо показывает границу между relational metadata и blob storage.
Мысленная модель сервиса: paste immutable после создания, а чтений больше, чем записей. Тело paste является blob-объектом, метаданные являются строкой с индексами. Запрос создания проходит через балансировщик к paste-svc: сервис валидирует размер, язык и TTL, генерирует короткий base62 ID, записывает body в S3-подобное хранилище по шардированному prefix и фиксирует строку в Postgres. Возврат клиенту возможен только после того, как metadata и body согласованы.
Запрос чтения делится на два класса. Raw view (/raw/abc123) отдается как immutable bytes и хорошо ложится в CDN: если paste публичный и не истек, edge может обслуживать его 24 часа без обращения к origin. Rendered view (/p/abc123) часто содержит HTML, подсветку синтаксиса, заголовок, metadata, возможно счетчик просмотров и кнопки. Для него полезнее cache-aside через Redis на origin: при miss сервис читает metadata из Postgres, body из S3, рендерит HTML или подсвеченный текст и кладет результат в Redis с ограниченным TTL.
Важный принцип: ID не должен раскрывать количество paste и не должен становиться single point of failure. Простая стратегия для такого масштаба: 8 символов base62 из hash(content + user_salt + timestamp) плюс retry на конфликте при вставке metadata. Пространство ключей достаточно большое, а ON CONFLICT делает collision handling явным.
Диаграмма показывает клиентский путь через CDN и Load Balancer к paste-svc, а затем три разные зависимости: Redis для hot cache, Postgres для metadata и S3 для тела paste. Отдельно вынесен ID Generator, потому что генерация коротких ссылок является самостоятельным решением, и Highlight worker, потому что подсветка синтаксиса может быть CPU-bound и не должна тормозить простые raw-чтения.
В data tier тело paste не хранится в Postgres. Это осознанный trade-off: Postgres отлично подходит для id, language, visibility, expires_at, user_id, s3_key, size_bytes, но большие и многочисленные text blobs раздувают WAL, backup, vacuum и read amplification. Object storage дешевле, масштабируется проще и позволяет lifecycle policy для старых unlisted paste.
TTL cleanup cron показывает, что expiration не является магическим свойством. Нужно найти истекшие строки, удалить объекты из S3, удалить metadata и очистить cache. Для надежности это делают батчами и идемпотентно: если объект уже удален, повторная попытка не должна ломать job.
Create paste учит разделять metadata и body. Сначала сервис валидирует input и генерирует ID, затем кладет body в S3, затем вставляет metadata в Postgres. Если вставка metadata не прошла из-за collision, можно сгенерировать новый ID и повторить. Если S3 write прошел, а Postgres write упал, нужен cleanup orphan object или асинхронный janitor.
View with cache miss показывает классический cache-aside. Redis miss не является ошибкой: сервис достает metadata, проверяет TTL и visibility, забирает body, делает syntax highlight и заполняет cache. Этот путь медленнее, но он должен оставаться корректным.
Raw CDN hit учит видеть immutable URL как идеальный CDN-кандидат. Edge обслуживает текст без origin, что защищает backend от viral paste. Но purge при delete или expiration должен инвалидировать соответствующий surrogate key, иначе пользователь сможет получить истекший paste с edge.
Viral burst показывает, почему raw и HTML paths лучше разделять. Raw traffic может дать 99 процентов hit ratio на CDN, HTML traffic остается на origin, но Redis снижает нагрузку на S3 и renderer.
TTL cleanup учит проектировать background maintenance. Нужны индексы по expires_at, ограниченный batch size, retry и метрики backlog.
Abuse scenario учит ставить guardrails: max body size, per-IP and per-account rate limit, captcha для анонимных пользователей, content scanning для malware и phishing, quota на never-expiring pastes.
S3 плюс Postgres проще и дешевле, чем хранить все в SQL. Цена решения: появляется dual write и необходимость cleanup orphan objects. Для маленького проекта допустимо хранить тело в Postgres, но при росте стоимости backup и vacuum быстро становятся заметными.
CDN для raw views снижает latency и egress с origin, но усложняет консистентность delete/expire. Если продукт обещает hard delete, нужен purge API и короткий max-age для спорных объектов. Если достаточно eventual expiration, можно жить с TTL на edge.
Base62 hash ID не требует центрального счетчика, но collision handling должен быть в коде, а не в надежде. Snowflake дает сортируемость и меньше retry, но вводит инфраструктуру и раскрывает время создания. Последовательный counter прост, но делает enumeration тривиальным и создает hot dependency.
Syntax highlighting можно делать on read, on write или lazy precompute. On read дешевле для редко открываемых paste, но первый viewer платит latency. On write сглаживает чтение, но тратит CPU на paste, которые никто не откроет. Lazy precompute через Redis или background worker часто является лучшим компромиссом.
Pastebin, GitHub Gist, GitLab snippets, hastebin-подобные сервисы и internal log sharing tools используют похожую модель: metadata отдельно, body как immutable object, короткая ссылка, optional expiration. В больших компаниях этот паттерн встречается в debug dump sharing, support attachments и временных export-ссылках.
S3, Google Cloud Storage и Azure Blob Storage подходят для body. PostgreSQL или MySQL подходят для metadata и ownership. Redis используется для hot objects и rate limiting. Cloudflare, Fastly или CloudFront обслуживают raw/static traffic. Для malware scanning можно подключить async worker, который помечает paste как suspicious и снимает его с публичного доступа.
Хранить мегабайтные paste прямо в основной Postgres-таблице без лимитов и lifecycle. Это кажется простым, пока backup, replication lag и vacuum не начинают влиять на весь сервис.
Делать reverse edges для response path в диаграмме. Физически клиент подключается к CDN, CDN к origin, сервис к storage; ответы идут по тем же соединениям в обратную сторону.
Кэшировать HTML-view в CDN без учета privacy, view count и expiration. Raw immutable bytes подходят для edge лучше, чем пользовательская HTML-страница.
Не проектировать deletion. Если есть TTL, нужно понимать, что удаляется: Redis key, metadata row, S3 object, CDN entry и search index, если он появится.
Использовать короткие sequential IDs для public unlisted links. Это превращает unlisted в почти public, потому что ссылки можно перебрать.
Такую архитектуру не стоит применять для collaborative editor, где документ постоянно меняется и важны conflict resolution, presence и real-time merge. Там нужен другой mental model: mutable document, operational transform или CRDT, websocket transport и versioned history.
Она также не подходит для секретного vault. Unlisted URL не равен authorization. Для secrets нужны encryption at rest with customer keys, strict access control, audit log, short TTL by default и запрет на CDN-кэширование чувствительного содержимого.
Если продукт должен искать по содержимому всех paste, понадобится отдельный indexing pipeline, например OpenSearch, и политика удаления из индекса. Базовая Pastebin-архитектура intentionally не решает full-text search.
Начните с ::concept{slug="back-of-envelope"}, чтобы быстро проверять порядок величин. Затем разберите ::concept{slug="object-storage-s3"} и ::concept{slug="cdn-design"}: они объясняют, почему blob storage и edge cache являются естественной парой. Для hot path полезны ::concept{slug="caching-strategies"} и ::concept{slug="rate-limiting"}. Для ID и partitioning посмотрите ::concept{slug="sharding"}. После этого сравните кейс с URL shortener: там тело объекта отсутствует, зато сильнее акцент на redirect latency, analytics и anti-abuse.