52. Разбор типичных System Design задач
Практический практикум: 10 эталонных систем
«Практика — лучший наставник во всем».
— Публилий Сир (девиз Bell Labs)
Теоретический фреймворк из предыдущей лекции (51. System Design интервью. Как подходить к задаче) закладывает дисциплину инженерного мышления. Однако подлинное мастерство архитектора познается в горниле решения конкретных, противоречивых задач под жестким давлением ограничений реального мира.
В этой лекции мы детально разберем десять классических систем, ставших индустриальными эталонами System Design интервью мирового уровня: от сервиса сокращения ссылок до глобального мессенджера, маркетплейса уровня Черной пятницы и геолокационной аренды. Для каждой задачи мы последовательно пройдем все фазы проектирования, детально обоснуем компромиссы, опустимся на уровень моделей данных и проявим глубокую Mechanical Sympathy к рантайму языка Go и ядру Linux.
1. Сервис сокращения ссылок (URL Shortener)
Классическая «простая» задача, на которой интервьюеры мгновенно отсеивают теоретиков от практиков.
Функциональные требования:
- Пользователь отправляет длинный URL $\rightarrow$ получает короткий 7-символьный ключ.
- При переходе по короткой ссылке $\rightarrow$ мгновенный HTTP-редирект (301/302).
- Сбор аналитики переходов в реальном времени (гео-локация, User-Agent, Referrer).
Нефункциональные требования:
- 100 млн новых ссылок в месяц ($~40$ RPS на запись).
- 1 млрд переходов в день ($~12\,000$ RPS на чтение, в пике до $35\,000$ RPS).
- Соотношение чтение/запись: $300:1$ (экстремально Read-Heavy система).
- Задержка редиректа: $P99 < 20$ мс.
- Доступность: $99.95\%$.
Верхнеуровневый дизайн
Архитектура строится по принципу строгого разделения путей записи и чтения в духе CQRS (23. CQRS. Разделение чтения и записи):
flowchart LR
classDef process fill:#1e293b,stroke:#38bdf8,stroke-width:1px,color:#f8fafc
classDef success fill:#064e3b,stroke:#34d399,stroke-width:1px,color:#f8fafc
classDef error fill:#7f1d1d,stroke:#f87171,stroke-width:1px,color:#f8fafc
classDef warning fill:#78350f,stroke:#fbbf24,stroke-width:1px,color:#f8fafc
classDef entry fill:#1e3a5f,stroke:#38bdf8,stroke-width:2px,color:#f8fafc
classDef aux fill:#334155,stroke:#64748b,stroke-width:1px,color:#f8fafc
classDef system fill:#312e81,stroke:#a78bfa,stroke-width:1px,color:#f8fafc
classDef data fill:#132d29,stroke:#2dd4bf,stroke-width:1px,color:#f8fafc
classDef network fill:#881337,stroke:#fb7185,stroke-width:1px,color:#f8fafc
classDef accent fill:#1e1b4b,stroke:#818cf8,stroke-width:1px,color:#f8fafc
Client["Клиенты<br>(Web / Mobile)"]
LB["Балансировщик нагрузки<br>(L4 / L7)"]
API["API-сервис (Go)<br>Создание ссылок (Запись)"]
Redirect["Редирект-сервис (Go)<br>Сверхбыстрое чтение"]
DB[("Основная БД<br>PostgreSQL / Шарды")]:::data
Cache["Кэш<br>Redis Cluster"]:::data
Kafka["Kafka<br>События кликов"]:::process
Analytics["Аналитика<br>ClickHouse"]:::process
Client -->|"POST /shorten"| LB --> API --> DB
Client -->|"GET /abc123"| LB --> Redirect
Redirect -->|"Промах L1/L2"| Cache
Redirect -->|"Промах кэша"| DB
Cache --> Redirect
Redirect -.->|"ClickEvent"| Kafka --> Analytics
Сервис редиректов полностью stateless, не имеет транзакций и масштабируется горизонтально.
Ключевые решения
Генерация короткого ключа:
- Алфавит Base62 (
[a-zA-Z0-9]). Длина 7 символов дает $62^7 \approx 3.52$ триллиона уникальных комбинаций. - Чтобы не вызывать коллизий хэшей, используем распределенный генератор счетчиков (Snowflake-подобный ID на 64 бита) с атомарным инкрементом через
sync/atomic. Каждый Go-инстанс забирает пачку из 10 000 ID в память и конвертирует число в Base62. - В БД на поле
short_keyвешается индексUNIQUE.
Хранение данных:
- Схема:
urls(id BIGINT PRIMARY KEY, short_key VARCHAR(10) UNIQUE, original_url TEXT, created_at TIMESTAMPTZ, expires_at TIMESTAMPTZ, user_id UUID). - При объеме 100 млн записей в месяц таблица растет на 50 ГБ в год. На старте достаточно одного мощного инстанса PostgreSQL с репликацией Leader-Follower (32. Репликация. Leader Follower и Multi Leader).
- При росте до миллиардов строк шардируем по хэшу от поля
short_keyс использованием консистентного хэширования (31. Partitioning и Sharding).
Двухуровневое кэширование:
- Паттерн Cache-Aside (28. Кэширование. Cache Aside, Write Through, Write Back): Redis Cluster с политикой LRU для топ-20% горячих ссылок.
- Локальный in-memory кэш на базе библиотеки
ristrettoпрямо внутри Go-процесса редирект-сервиса. Это срезает сетевой round-trip до Redis, отдавая супергорячие ссылки (топ-1%) за 200 наносекунд.
Асинхронная аналитика:
- Редирект-сервис сбрасывает событие
ClickEventв Kafka без ожидания подтверждения (Fire-and-Forget). Воркеры считывают батчи и складывают их в колоночную СУБД ClickHouse (41. Data Pipeline и потоковая обработка).
Устойчивость и защита:
- Ограничение создания ссылок через Token Bucket (
golang.org/x/time/rate). - Circuit Breaker на обращение к Redis: если кэш перегружен, переключаемся на базу данных, снижая нагрузку на сеть (36. Circuit Breaker, Retry, Timeout и Backoff).
Mechanical Sympathy:
- Рантайм Go: редирект на Go — это чистый парсинг HTTP-заголовка, чтение слайса байт из кэша и отправка HTTP-статуса 301/302. Сервис утилизирует сетевой стек ядра Linux без единой аллокации в куче на критическом пути.
- Настройка
GOMEMLIMITпредотвращает выбивание процесса OOM Killer'ом при всплеске in-memory кэша.
2. Чат-сервер (Messaging)
Требования:
- Обмен личными (1-to-1) и групповыми сообщениями (до 1 000 участников) в реальном времени.
- Доставка офлайн-сообщений, подтверждение прочтения (Read Receipts).
- Передача медиа-файлов (изображения, голосовые сообщения).
- 100 млн активных пользователей в сутки (DAU), 10 млн одновременных онлайн-соединений (CCU), 1 млрд сообщений в день.
Архитектура
flowchart LR
classDef process fill:#1e293b,stroke:#38bdf8,stroke-width:1px,color:#f8fafc
classDef success fill:#064e3b,stroke:#34d399,stroke-width:1px,color:#f8fafc
classDef error fill:#7f1d1d,stroke:#f87171,stroke-width:1px,color:#f8fafc
classDef warning fill:#78350f,stroke:#fbbf24,stroke-width:1px,color:#f8fafc
classDef entry fill:#1e3a5f,stroke:#38bdf8,stroke-width:2px,color:#f8fafc
classDef aux fill:#334155,stroke:#64748b,stroke-width:1px,color:#f8fafc
classDef system fill:#312e81,stroke:#a78bfa,stroke-width:1px,color:#f8fafc
classDef data fill:#132d29,stroke:#2dd4bf,stroke-width:1px,color:#f8fafc
classDef network fill:#881337,stroke:#fb7185,stroke-width:1px,color:#f8fafc
classDef accent fill:#1e1b4b,stroke:#818cf8,stroke-width:1px,color:#f8fafc
Client["Клиенты<br>(Web / Mobile)"]
LB["Балансировщик нагрузки<br>(L4 TCP / Envoy TLS Termination)"]
ChatSvc["Чат-шлюз (Go)<br>Stateful WebSocket Connections"]
Broker["Брокер сообщений<br>Kafka / NATS JetStream"]:::process
History[("Хранилище истории<br>Apache Cassandra / ScyllaDB")]:::data
Media["Медиа-сервис (Go)<br>Multipart Upload"]
CDN["CDN / S3"]:::process
Push["Пуш-сервис (FCM / APNs)"]
Client <-->|"WebSocket"| LB <--> ChatSvc
ChatSvc -->|"Публикация сообщения"| Broker
Broker -->|"Подписка по chat_id"| ChatSvc
ChatSvc -->|"Асинхронная запись"| History
Client -->|"Загрузка медиа"| Media --> CDN
ChatSvc -.->|"Офлайн-уведомление"| Push
Чат-сервер stateful по сетевым TCP/WebSocket-сессиям, но stateless по отношению к долговременному состоянию данных.
Ключевые решения
Сетевой протокол и масштаб соединений:
- Полнодуплексный WebSocket поверх TLS. Для Go-сервиса используем легковесные библиотеки
nhooyr.io/websocketилиgorilla/websocket. - Каждое соединение обслуживается двумя горутинами: одна читает сокет, вторая пишет из буферизованного канала. При 10 млн соединений требуется 20 млн горутин.
- Расчет памяти: Стек горутины в 4 КБ $\times 20$ млн горутин = 80 ГБ RAM. Распределяем нагрузку на 100 подов в Kubernetes, каждый держит по 100 000 сокетов (что требует тюнинга Linux
sysctl:fs.file-maxиnet.ipv4.ip_local_port_range).
Маршрутизация сообщений (Session Registry):
- Как доставить сообщение от пользователя А пользователю Б, если они подключены к разным подам чат-сервиса?
- Redis Cluster хранит маппинг:
user_id -> pod_ip. Либо топики Kafka партиционируются по полюchat_id. Все сообщения одного диалога приходят строго в одну партицию Kafka, сохраняя абсолютную хронологию событий.
Хранилище сообщений:
- Реляционные СУБД неприменимы из-за гигантской скорости вставок (10 000 INSERT/сек).
-
Выбираем ScyllaDB / Cassandra:
SQL CREATE TABLE messages ( chat_id UUID, message_id TIMEUUID, sender_id UUID, content TEXT, media_urls LIST<TEXT>, PRIMARY KEY (chat_id, message_id) ) WITH CLUSTERING ORDER BY (message_id DESC);Это обеспечивает мгновенное чтение последних 50 сообщений чата за один дисковый seek на LSM-дереве.
Обработка медиа:
- Файлы никогда не передаются внутри WebSocket. Клиент получает от API предподписанный URL (Pre-signed URL) и загружает файл напрямую в S3, после чего сервис на Go с библиотекой
imagingгенерирует превью.
Mechanical Sympathy:
- Контроль памяти рантайма: мониторинг метрики
go_goroutinesи жесткий тюнингGOMEMLIMIT. - Защита от медленных клиентов (Slow Consumers): если клиент не вычитывает сокет, буферизованный канал заполняется. Механизм Backpressure (43. Backpressure и контроль нагрузки) принудительно обрывает зависшее соединение, предотвращая утечку памяти.
3. Новостная лента (News Feed) — аналог ленты Твиттера/Фейсбука
Требования:
- Пользователь видит агрегированную ленту публикаций людей, на которых подписан.
- Поддержка публикаций с текстом и изображениями.
- 200 млн активных пользователей (DAU), среднее число подписок — 200.
- Задержка между публикацией поста и его появлением в лентах подписчиков — менее 2 секунд.
Архитектура: Fan-out on Write с гибридом для знаменитостей
flowchart LR
classDef process fill:#1e293b,stroke:#38bdf8,stroke-width:1px,color:#f8fafc
classDef success fill:#064e3b,stroke:#34d399,stroke-width:1px,color:#f8fafc
classDef error fill:#7f1d1d,stroke:#f87171,stroke-width:1px,color:#f8fafc
classDef warning fill:#78350f,stroke:#fbbf24,stroke-width:1px,color:#f8fafc
classDef entry fill:#1e3a5f,stroke:#38bdf8,stroke-width:2px,color:#f8fafc
classDef aux fill:#334155,stroke:#64748b,stroke-width:1px,color:#f8fafc
classDef system fill:#312e81,stroke:#a78bfa,stroke-width:1px,color:#f8fafc
classDef data fill:#132d29,stroke:#2dd4bf,stroke-width:1px,color:#f8fafc
classDef network fill:#881337,stroke:#fb7185,stroke-width:1px,color:#f8fafc
classDef accent fill:#1e1b4b,stroke:#818cf8,stroke-width:1px,color:#f8fafc
PostSvc["Сервис постов (Go)"]
Kafka["Kafka Топик<br>PostCreated"]:::process
Worker["Fan-out воркеры (Go)"]
TimelineCache["Лента пользователя<br>Redis Sorted Set"]:::entry
FeedSvc["Сервис формирования ленты (Go)"]
GraphDB[("Граф подписок<br>Cassandra / Neo4j")]:::process
PostSvc -->|"Создание поста"| Kafka
Kafka --> Worker
Worker -->|"Чтение подписчиков"| GraphDB
Worker -->|"ZADD post_id"| TimelineCache
FeedSvc -->|"ZREVRANGEBYSCORE"| TimelineCache
FeedSvc -->|"Гибрид: мердж постов звезд"| PostSvc
Ключевые решения
Модель Fan-out (Раздувание лент):
-
Fan-out on Write (Push-модель):
Когда обычный пользователь (до 10 000 подписчиков) публикует пост, фоновый воркер берет список его фолловеров и добавляет идентификатор поста в персональный список каждого подписчика в Redis:REDIS ZADD timeline:userID timestamp postIDЧтение ленты невероятно быстрое:
ZREVRANGEBYSCORE timeline:userID +inf -inf LIMIT 0 20. -
Проблема знаменитостей (Celebrity Problem):
Если у Илона Маска 150 млн подписчиков, Push-модель парализует систему: один твит потребует 150 млн записей в Redis.
Гибридное решение: Знаменитости помечаются флагомis_celebrity. Их посты не проталкиваются в ленты подписчиков при записи. Когда подписчик запрашивает свою ленту, сервисFeedSvcна лету запрашивает последние посты знаменитостей, на которых он подписан, и мерджит их со своей кэшированной лентой за 5 мс.
Ограничение длины ленты:
- Человек редко скроллит глубже 800 постов. Команда
ZREMRANGEBYRANK timeline:userID 0 -1001держит размер кэша строго в пределах 1 000 записей.
Консистентность:
- Модель Eventual Consistency (29. Consistency модели. Strong, Eventual, Causal): появление поста в ленте друга через 1–2 секунды абсолютно приемлемо.
Mechanical Sympathy:
- Воркеры Fan-out на Go объединяют запись в Redis через
go-redis pipeline. Вместо 10 000 отдельных сетевых RTT отправляется один TCP-пакет с батчем команд, что снижает загрузку ЦПУ в десятки раз.
4. Система отложенных задач (Job Queue / Scheduler)
Требования:
- Отложенный запуск произвольных задач: «отправить push-уведомление через 3 часа», «сгенерировать выписку 1-го числа месяца».
- Гарантия выполнения At-least-once.
- Десятки миллионов задач в сутки, пиковая пропускная способность до 50 000 задач в секунду.
Архитектура
flowchart LR
classDef process fill:#1e293b,stroke:#38bdf8,stroke-width:1px,color:#f8fafc
classDef success fill:#064e3b,stroke:#34d399,stroke-width:1px,color:#f8fafc
classDef error fill:#7f1d1d,stroke:#f87171,stroke-width:1px,color:#f8fafc
classDef warning fill:#78350f,stroke:#fbbf24,stroke-width:1px,color:#f8fafc
classDef entry fill:#1e3a5f,stroke:#38bdf8,stroke-width:2px,color:#f8fafc
classDef aux fill:#334155,stroke:#64748b,stroke-width:1px,color:#f8fafc
classDef system fill:#312e81,stroke:#a78bfa,stroke-width:1px,color:#f8fafc
classDef data fill:#132d29,stroke:#2dd4bf,stroke-width:1px,color:#f8fafc
classDef network fill:#881337,stroke:#fb7185,stroke-width:1px,color:#f8fafc
classDef accent fill:#1e1b4b,stroke:#818cf8,stroke-width:1px,color:#f8fafc
API["API-сервис (Go)<br>Регистрация задач"]
Store[("Персистентное хранилище<br>PostgreSQL / Шарды")]:::data
FastQueue["Redis Sorted Set<br>Ближайшие задачи (1 час)"]
Scheduler["Планировщик (Go)<br>Выборка задач"]
Worker["Пул воркеров (Go)<br>Исполнение бизнес-логики"]
DLQ["Dead Letter Queue<br>Kafka"]:::data
API -->|"INSERT job"| Store
API -->|"ZADD timestamp job_id"| FastQueue
Scheduler -->|"ZPOPMIN"| FastQueue
Scheduler -->|"SELECT FOR UPDATE SKIP LOCKED"| Store
Scheduler -->|"Отправка в канал"| Worker
Worker -->|"Ошибка после N ретраев"| DLQ
Модель данных и алгоритм выборки:
-
В PostgreSQL создается таблица:
SQL CREATE TABLE jobs ( id UUID PRIMARY KEY, payload JSONB, scheduled_at TIMESTAMPTZ NOT NULL, status VARCHAR(20) NOT NULL, retry_count INT DEFAULT 0, idempotency_key VARCHAR(64) UNIQUE ); CREATE INDEX idx_jobs_pending ON jobs (status, scheduled_at); -
Для предотвращения гонок между несколькими инстансами планировщика используется неблокирующий опрос с синтаксисом
SKIP LOCKED:SQL UPDATE jobs SET status = 'processing' WHERE id IN ( SELECT id FROM jobs WHERE status = 'pending' AND scheduled_at <= NOW() ORDER BY scheduled_at FOR UPDATE SKIP LOCKED LIMIT 500 ) RETURNING *; -
Задачи с близким дедлайном (ближайшие минуты) дублируются в Redis Sorted Set, откуда вычитываются атомарной командой
ZPOPMIN.
Отказоустойчивость и идемпотентность:
- Каждая задача имеет уникальный
idempotency_key, предотвращающий дублирование (27. Idempotency и exactly once семантика). - Исполнение задач оборачивается в контекст с дедлайном (
context.WithTimeout). При сбое счетчикretry_countувеличивается, задача возвращается в очередь с экспоненциальным backoff. При исчерпании лимита задача уходит в DLQ.
5. Аналог Твиттера
Требования:
- Публикация коротких заметок, лайки, ретвиты, глобальный поиск и тренды хэштегов.
- 300 млн DAU, 500 млн твитов в день (6 000 твитов/сек средняя, 50 000 твитов/сек пиковая).
Архитектура
flowchart LR
classDef process fill:#1e293b,stroke:#38bdf8,stroke-width:1px,color:#f8fafc
classDef success fill:#064e3b,stroke:#34d399,stroke-width:1px,color:#f8fafc
classDef error fill:#7f1d1d,stroke:#f87171,stroke-width:1px,color:#f8fafc
classDef warning fill:#78350f,stroke:#fbbf24,stroke-width:1px,color:#f8fafc
classDef entry fill:#1e3a5f,stroke:#38bdf8,stroke-width:2px,color:#f8fafc
classDef aux fill:#334155,stroke:#64748b,stroke-width:1px,color:#f8fafc
classDef system fill:#312e81,stroke:#a78bfa,stroke-width:1px,color:#f8fafc
classDef data fill:#132d29,stroke:#2dd4bf,stroke-width:1px,color:#f8fafc
classDef network fill:#881337,stroke:#fb7185,stroke-width:1px,color:#f8fafc
classDef accent fill:#1e1b4b,stroke:#818cf8,stroke-width:1px,color:#f8fafc
TweetSvc["Сервис твитов (Go)"]
Kafka["Kafka Топик<br>TweetEvents"]:::process
Timeline["Сервис ленты<br>(Fan-out)"]
Trend["Стрим-аналитика трендов<br>(Go Worker / Sliding Window)"]
Search["Поисковый движок<br>Elasticsearch"]:::process
DB[("Cassandra / Postgres")]:::data
Cache["Redis Cluster"]:::data
TweetSvc -->|"TweetCreated"| Kafka
Kafka --> Timeline --> Cache
Kafka --> Trend --> Cache
Kafka --> Search --> Elasticsearch
TweetSvc --> DB
Ключевые решения
Шардирование:
- Твиты шардируются по первичному ключу
tweet_id(Snowflake ID с меткой времени). - Ленты пользователей шардируются строго по
user_id.
Полнотекстовый поиск и тренды:
- Поисковый индекс Elasticsearch наполняется асинхронно из Kafka батчами по 1 000 документов.
- Подсчет трендов: стриминговый воркер на Go агрегирует хэштеги в скользящих временных окнах (Sliding Window: 5 минут, 1 час) с использованием вероятностного алгоритма Count-Min Sketch.
6. Аналог Инстаграма
Требования:
- Загрузка высококачественных фото и видео, сторис с 24-часовым TTL, медиа-лента.
- 1 млрд пользователей, 100 млн загрузок медиа в сутки.
Архитектура
flowchart LR
classDef process fill:#1e293b,stroke:#38bdf8,stroke-width:1px,color:#f8fafc
classDef success fill:#064e3b,stroke:#34d399,stroke-width:1px,color:#f8fafc
classDef error fill:#7f1d1d,stroke:#f87171,stroke-width:1px,color:#f8fafc
classDef warning fill:#78350f,stroke:#fbbf24,stroke-width:1px,color:#f8fafc
classDef entry fill:#1e3a5f,stroke:#38bdf8,stroke-width:2px,color:#f8fafc
classDef aux fill:#334155,stroke:#64748b,stroke-width:1px,color:#f8fafc
classDef system fill:#312e81,stroke:#a78bfa,stroke-width:1px,color:#f8fafc
classDef data fill:#132d29,stroke:#2dd4bf,stroke-width:1px,color:#f8fafc
classDef network fill:#881337,stroke:#fb7185,stroke-width:1px,color:#f8fafc
classDef accent fill:#1e1b4b,stroke:#818cf8,stroke-width:1px,color:#f8fafc
Client["Мобильный клиент"]:::entry
UploadSvc["Сервис загрузки (Go)"]
MediaProc["Воркер конвертации<br>(Go + ffmpeg)"]
S3[("Объектное хранилище<br>MinIO / S3")]:::data
CDN["Глобальный CDN"]:::process
StorySvc["Сервис сторис (Go)"]
Redis["Redis (TTL 24h)"]
Client -->|"Загрузка чанка"| UploadSvc --> S3
UploadSvc -->|"MediaUploaded"| Kafka
Kafka --> MediaProc --> S3 --> CDN
Kafka --> StorySvc --> Redis
Потоковая обработка медиа:
- Прием файлов через Multipart Streaming: байты сбрасываются напрямую в хранилище через SDK
minio-go, не накапливаясь целиком в оперативной памяти процесса. - Для видео запускается утилита
ffmpegчерез пакетos/exec. Временные чанки обрабатываются в каталоге/tmp, смонтированном в оперативную память черезtmpfs.
7. Аналог Авито (Доска объявлений)
Требования:
- Публикация объявлений с гео-привязкой, фильтрация по категориям, ценам и радиусу (например, «в радиусе 15 км от меня»).
- 10 млн активных объявлений, 100 млн поисковых запросов в день.
Архитектура
flowchart LR
classDef process fill:#1e293b,stroke:#38bdf8,stroke-width:1px,color:#f8fafc
classDef success fill:#064e3b,stroke:#34d399,stroke-width:1px,color:#f8fafc
classDef error fill:#7f1d1d,stroke:#f87171,stroke-width:1px,color:#f8fafc
classDef warning fill:#78350f,stroke:#fbbf24,stroke-width:1px,color:#f8fafc
classDef entry fill:#1e3a5f,stroke:#38bdf8,stroke-width:2px,color:#f8fafc
classDef aux fill:#334155,stroke:#64748b,stroke-width:1px,color:#f8fafc
classDef system fill:#312e81,stroke:#a78bfa,stroke-width:1px,color:#f8fafc
classDef data fill:#132d29,stroke:#2dd4bf,stroke-width:1px,color:#f8fafc
classDef network fill:#881337,stroke:#fb7185,stroke-width:1px,color:#f8fafc
classDef accent fill:#1e1b4b,stroke:#818cf8,stroke-width:1px,color:#f8fafc
ListingSvc["Сервис объявлений (Go)"]
SearchSvc["Поисковый сервис (Go)"]
Elastic["Elasticsearch Cluster<br>(Geo-Distance фильтры)"]
DB[("PostgreSQL<br>PostGIS")]:::process
Cache["Redis Cluster"]:::data
ListingSvc -->|"CRUD"| DB
ListingSvc -->|"События изменений"| Kafka --> SearchSvc --> Elastic
SearchSvc --> Cache
Гео-поиск:
-
Реляционные базы не выдерживают сложных полигональных поисков при 10 000 RPS. Поисковый индекс выносится в Elasticsearch:
JSON { "query": { "bool": { "filter": [ { "term": { "category_id": 42 } }, { "range": { "price": { "gte": 1000, "lte": 5000 } } }, { "geo_distance": { "distance": "15km", "location": { "lat": 55.75, "lon": 37.61 } } } ] } } }
8. Аналог Озона (Маркетплейс)
Требования:
- Каталог с миллионами SKU, корзина, мгновенное резервирование остатков и проведение заказов.
- Выдерживать наплыв покупателей в Черную пятницу (рост трафика в 10–20 раз).
Архитектура
flowchart LR
classDef process fill:#1e293b,stroke:#38bdf8,stroke-width:1px,color:#f8fafc
classDef success fill:#064e3b,stroke:#34d399,stroke-width:1px,color:#f8fafc
classDef error fill:#7f1d1d,stroke:#f87171,stroke-width:1px,color:#f8fafc
classDef warning fill:#78350f,stroke:#fbbf24,stroke-width:1px,color:#f8fafc
classDef entry fill:#1e3a5f,stroke:#38bdf8,stroke-width:2px,color:#f8fafc
classDef aux fill:#334155,stroke:#64748b,stroke-width:1px,color:#f8fafc
classDef system fill:#312e81,stroke:#a78bfa,stroke-width:1px,color:#f8fafc
classDef data fill:#132d29,stroke:#2dd4bf,stroke-width:1px,color:#f8fafc
classDef network fill:#881337,stroke:#fb7185,stroke-width:1px,color:#f8fafc
classDef accent fill:#1e1b4b,stroke:#818cf8,stroke-width:1px,color:#f8fafc
CartSvc["Сервис корзины<br>(Redis)"]
OrderSvc["Оркестратор заказов<br>(Go Saga)"]
Inventory["Сервис остатков<br>(Redis Lua / Postgres)"]
Payment["Платежный шлюз"]:::process
Warehouse["Складская логистика"]:::process
CartSvc --> OrderSvc
OrderSvc -->|"1. Резерв товара"| Inventory
OrderSvc -->|"2. Списание средств"| Payment
OrderSvc -->|"3. Заказ на сборку"| Warehouse
Управление заказами и распределенные транзакции:
- Применение паттерна Saga с оркестрацией (26. Saga Pattern. Оркестрация и хореография). Оркестратор на Go последовательно координирует шаги с компенсационными действиями при отказе любого узла.
- Резервирование остатков: Использование атомарных Lua-скриптов в Redis исключает возможность продажи одного и того же последнего товара двум пользователям одновременно (Race Condition).
9. Аренда квартир/машин (Booking-like)
Требования:
- Поиск доступных объектов по календарю дат, предотвращение двойных бронирований (Double Booking).
Архитектура
flowchart LR
classDef process fill:#1e293b,stroke:#38bdf8,stroke-width:1px,color:#f8fafc
classDef success fill:#064e3b,stroke:#34d399,stroke-width:1px,color:#f8fafc
classDef error fill:#7f1d1d,stroke:#f87171,stroke-width:1px,color:#f8fafc
classDef warning fill:#78350f,stroke:#fbbf24,stroke-width:1px,color:#f8fafc
classDef entry fill:#1e3a5f,stroke:#38bdf8,stroke-width:2px,color:#f8fafc
classDef aux fill:#334155,stroke:#64748b,stroke-width:1px,color:#f8fafc
classDef system fill:#312e81,stroke:#a78bfa,stroke-width:1px,color:#f8fafc
classDef data fill:#132d29,stroke:#2dd4bf,stroke-width:1px,color:#f8fafc
classDef network fill:#881337,stroke:#fb7185,stroke-width:1px,color:#f8fafc
classDef accent fill:#1e1b4b,stroke:#818cf8,stroke-width:1px,color:#f8fafc
Search["Поисковый сервис (Go)"]
Calendar["Сервис доступности<br>(Redis Bitmaps)"]
Booking["Сервис бронирования (Go)"]
DB[("PostgreSQL<br>B-Tree Exclusion Constraints")]:::process
Search --> Calendar
Booking --> Calendar
Booking --> DB
Календарь дат и исключающие блокировки:
- Для каждого объекта в Redis заводится битовая карта (Bitmap) на 365 дней вперед. Установка флага занятости выполняется атомарной операцией
SETBIT. -
На уровне PostgreSQL надежность гарантируется оператором диапазонов дат:
SQL ALTER TABLE bookings ADD CONSTRAINT no_overlap EXCLUDE USING gist (room_id WITH =, booking_dates WITH &&);Это математически исключает наложение бронирований даже при сбое Redis.
10. Интернет-магазин (High-Load E-commerce)
Требования:
- Каталог, поиск, оформление заказов, защита от краха в Черную пятницу.
Архитектура
flowchart LR
classDef process fill:#1e293b,stroke:#38bdf8,stroke-width:1px,color:#f8fafc
classDef success fill:#064e3b,stroke:#34d399,stroke-width:1px,color:#f8fafc
classDef error fill:#7f1d1d,stroke:#f87171,stroke-width:1px,color:#f8fafc
classDef warning fill:#78350f,stroke:#fbbf24,stroke-width:1px,color:#f8fafc
classDef entry fill:#1e3a5f,stroke:#38bdf8,stroke-width:2px,color:#f8fafc
classDef aux fill:#334155,stroke:#64748b,stroke-width:1px,color:#f8fafc
classDef system fill:#312e81,stroke:#a78bfa,stroke-width:1px,color:#f8fafc
classDef data fill:#132d29,stroke:#2dd4bf,stroke-width:1px,color:#f8fafc
classDef network fill:#881337,stroke:#fb7185,stroke-width:1px,color:#f8fafc
classDef accent fill:#1e1b4b,stroke:#818cf8,stroke-width:1px,color:#f8fafc
CDN["Cloudflare CDN"]:::process
Gateway["API Gateway (Go)"]
Catalog["Сервис каталога (Go)"]
SingleFlight["singleflight cache"]:::data
DB[("Кластер БД")]:::data
CDN --> Gateway --> Catalog --> SingleFlight --> DB
Защита от кэш-лавины (Cache Stampede):
- Когда истекает TTL горячего товара в кэше, тысячи параллельных запросов могут одномоментно ударить в базу данных, положив её.
- В Go-сервисе каталога применяется утилита
singleflight(golang.org/x/sync/singleflight). Она гарантирует, что при одновременном промахе кэша только одна горутина пойдет в базу данных за товаром, а остальные сотни горутин будут ждать этого ответа в памяти, защищая СУБД.
Общие выводы
Разбор десяти фундаментальных систем демонстрирует незыблемые инженерные законы:
- Асимметрия нагрузок: Подавляющее большинство систем в реальном мире — экстремально Read-Heavy. Разделяйте пути чтения и записи (CQRS).
- Асинхронность — ключ к выживанию: Никогда не выполняйте тяжелые, аналитические или ненадежные операции в синхронном цикле HTTP-запроса. Сбрасывайте события в очереди сообщений.
- Многоуровневый кэш: Комбинируйте CDN, распределенный Redis и локальный in-memory кэш процесса.
- Mechanical Sympathy с Go: Рантайм Go великолепен для высоконагруженного бэкенда, но требует бережного отношения к памяти (
sync/atomic,singleflight,GOMEMLIMIT, пулы буферов и неблокирующий I/O).
Теперь, когда мы изучили правильные паттерны, пора взглянуть на обратную сторону медали: роковые архитектурные ошибки, разрушающие даже самые амбициозные проекты: 53. Антипаттерны архитектуры.