Шпаргалка по System Design

Конспект для подготовки к интервью · Обновлено 15.10.2026

Процесс проектирования

Прежде чем рисовать схему, полезно договориться о процессе. Хороший дизайн начинается с уточняющих вопросов.

О процессе дизайна хорошо написано здесь:

Там много ссылок, от которых можно плясать.


C4 model

C4 model — это способ описать систему на четырёх уровнях детализации. На интервью обычно достаточно первого и второго уровня, но полезно знать всю лестницу.

  1. Context — уровень пользователя. Кто пользуется системой, какие внешние системы с ней взаимодействуют.
  2. Containers — high-level роли: хранилища, приложения, очереди, шлюзы. То, что можно запустить отдельно.
  3. Components — низкоуровневые компоненты внутри контейнеров: REST-эндпоинты, контроллеры, сервисы.
  4. Code — код, UML, ERD-диаграммы. На интервью обычно не нужен, но именно этот уровень раскрывает детали реализации.

Базы данных

Типичные БД, которые могут пригодиться при проектировании системы. Таблица ниже — это не «рейтинг лучших», а карта: что выбрать, когда нужна транзакционность, а когда можно пожертвовать согласованностью ради доступности.

Имя Область применения Транзакционность CAP
SQLiteDB (Key-Value Store) Простая конфигурация, опции, списки… ACID CA
Postgres Ansi SQL (сюда же MySQL…) ACID CP
Aerospike NoSQL, SSD/In-memory, mix read/write Atomic execution of operations AP
Redis KVS, In-memory, complex, Dynamic caches, sessions, pub-sub, etc Atomic execution of command blocks and scripts, optimistic locking (EC, SC with Redis Raft — CP) AP
Apache Ignite KVS, in-memory and store DACID, Raft CP/AP
Couchbase NoSQL, KVS, documented, memcache protocol, MapReduce DACID CP/AP
Yandex DB OLTP, NewSQL DACID, Serializable CP/AP
Cassandra NoSQL, columnar No, atomic batches AP
Clickhouse OLAP, SQL support, columnar, analytics No, atomic batches AP
Memcached In-memory, KVS, small objects, simple No AP
Как этим пользоваться на интервью Не нужно заучивать все строки. Достаточно держать в голове три-четыре варианта под разные сценарии: реляционная БД с ACID для денег и заказов, key-value для сессий и кэшей, колоночная для аналитики, ширококолоночная для больших объёмов с доступностью в приоритете. Остальные названия — это уже вариации на тему.

Метрики

Табличка значений метрик «которые все должны знать» и ещё немного. Метрики нужны, чтобы оценить стоимость и адекватность решения. Ниже — значения, которые помогают от MAU/DAU перейти к RPS и посчитать нагрузку.

Метрика Значение
Доступ к ссылке в L1-кэш0.5 нс
Ошибка предсказания ветвления (Branch mispredict)5 нс
Доступ к ссылке в L2-кэш25 нс
Доступ к ссылке в основной памяти процессора100 нс
Сжать 1k байтов Zipy10 мкс
Отправить 1k по 1 Gbps сетке10 мкс
Случайное чтение 4kb с SSD150 мкс
Последовательное чтение 1Mb из ОП250 мкс
Стоимость сетевого обращения внутри одного ДЦ500 мкс
Последовательное чтение 1Mb из SSD1 мс
HDD seek10 мс
Последовательное чтение 1Mb по 1 Gbps сетке10 мс
Последовательное чтение 1Mb из HDD30 мс
Стоимость сетевого обращения между географически разными ДЦ150 мс
TPS (transactions per second) для Postgres100 / 1 cpu
TPS для Cassandra200 (r+w) / 1 cpu
TPS для Clickhouse?
TPS для YDB?
Time to process JSON and receive/response by HTTP1 ms + Network ping
Переход от DAU к RPS0.004–0.005 RPS/DAU
Переход от MAU к RPS0.0001–0.00017 RPS/MAU

Измерения

Измерение Описание
нснаносекунды
мксмикросекунды
мсмиллисекунды
/1cpuна один CPU в ~~вакууме~~ облаке

Откуда взялись некоторые метрики

Для PostgreSQL я взял бенчмарк lithium-postgres-batch (postgres, C++, Linux, Full ORM), который на первом месте в cloud по состоянию на 06.05.2022. Вводные: 20 updates, 7917 RPS, Azure D3v2 instances. Смотрим доку по инстансам. И примерно прикидываем (8000 RPS / 20 / 4 ядра) — получается 100 TPS на одно ядро для Postgres.

По Cassandra взял несколько примеров кластеров и посчитал среднее на виртуальный CPU thread.

Как пользоваться этими числами Не воспринимайте их как точные. Это порядок величины, который помогает быстро прикинуть: «если у меня 10 000 RPS, сколько нужно инстансов Postgres». Точные числа всегда зависят от железа, версии, конфигурации и профиля нагрузки. На интервью достаточно уверенно назвать порядок: нс — это память, мкс — сеть и SSD, мс — диск и межконтинентальные запросы.

Оценка нагрузки: back-of-the-envelope

Прежде чем рисовать архитектуру, нужно понять порядок величин. Back-of-the-envelope (BOTE) — это быстрые приблизительные расчёты, которые помогают отбросить заведомо неподходящие варианты и увидеть, где будет узкое место.

От пользователей к RPS

Основная формула: QPS = (DAU × действий на пользователя) / 86 400. Для MAU коэффициент перехода к DAU обычно 2–3×, но проще считать от DAU, если он известен.

ПараметрФормула / ориентир
Средний QPS(DAU × actions_per_user) / 86 400
Пиковый QPSСредний QPS × peak_multiplier (обычно 3–5×)
Пиковый множитель3–5× для большинства систем; выше для событийных пиков
Конкурентные пользователиDAU × %_online × session_length_min / 60

Пример: 500M DAU, 10 действий на пользователя в день. Средний QPS = (500M × 10) / 86 400 ≈ 57 870. Пиковый при множителе 4× ≈ 231 500 QPS. При 1K QPS ещё может хватить одной БД; при 100K QPS нужны кэш и read replicas; при 1M QPS — шардирование и CDN.

Оценка хранилища и трафика

Хранилище = DAU × действий × размер_записи × срок_хранения. Не забывайте про репликацию: каждая реплика умножает объём.

Трафик = QPS × средний_размер_ответа. Для медиа-сервисов это часто становится узким местом раньше, чем CPU или БД.

Правило большого пальца Считайте в порядке величин, а не в точных числах. На интервью достаточно уверенно назвать: «примерно 200K QPS на пике, значит нужно 20–30 инстансов stateless-сервиса по 10K RPS каждый». Если ошибётесь в 2 раза — это нормально, если ошибётесь в 100 раз — нет.

Балансировка нагрузки

Балансировщик распределяет входящий трафик между серверами. Ключевой вопрос на интервью: L4 или L7.

УровеньЧто видитКогда использовать
L4 (TCP)IP, порт, TCP-флагиВысокая пропускная способность, минимальная задержка, нетерминированные протоколы
L7 (HTTP)URL, заголовки, cookies, содержимоеМаршрутизация по пути, sticky sessions, SSL-терминация, rate limiting

Алгоритмы балансировки

АлгоритмКогда подходит
Round RobinСерверы одинаковой мощности, запросы однородные
Weighted Round RobinСерверы разной мощности
Least ConnectionsДлительность запросов сильно варьируется
IP HashНужны sticky sessions без cookies
Consistent HashingРаспределённый кэш, минимизация перераспределения при добавлении узла
Sticky sessions — это компромисс Привязка пользователя к конкретному серверу упрощает работу с сессиями, но усложняет балансировку при отказе узла и мешает горизонтальному масштабированию stateless-сервисов. Предпочтительнее хранить состояние вне сервера (Redis, БД), а sticky sessions использовать только там, где это действительно необходимо.

Кэширование

Кэш — самый быстрый способ снизить latency и нагрузку на БД. Но у него есть цена: инвалидация, консистентность, дополнительная инфраструктура.

Стратегии записи в кэш

СтратегияКак работаетКогда подходит
Cache-asideПриложение сначала читает кэш, при промахе идёт в БД и кладёт результат в кэшУниверсальный вариант по умолчанию
Write-throughЗапись идёт одновременно в кэш и в БДКогда важна консистентность кэша и БД
Write-behindЗапись идёт в кэш, в БД попадает асинхронноWrite-heavy нагрузка, допустима eventual consistency
Read-throughКэш сам загружает данные из БД при промахеАбстракция в библиотеке/прокси

Типичные проблемы кэша

  • Cache penetration — запросы к несуществующим ключам проходят мимо кэша и бьют в БД. Решение: кэшировать «пустой» результат или использовать bloom filter.
  • Thundering herd — множество запросов одновременно промахиваются и идут в БД. Решение: lock/single-flight, jitter в TTL.
  • Инвалидация — самый сложный аспект. TTL, версионирование ключей, событийная инвалидация через pub/sub.
  • Eviction policies — LRU, LFU, FIFO. Выбор зависит от профиля доступа: LRU хорош для временной локальности, LFU — для стабильно популярных ключей.

Очереди сообщений

Очереди отделяют производителя от потребителя, сглаживают пики и позволяют обрабатывать задачи асинхронно.

Гарантии доставки

ГарантияДубликаты?Потери?Где применяется
At-most-onceНетДаМетрики, логи — потеря допустима
At-least-onceДаНетБольшинство брокеров (Kafka, SQS) — требует идемпотентности потребителя
Exactly-onceНетНетСложно и дорого; Kafka transactions, Flink

На практике «exactly-once» почти всегда означает «at-least-once + идемпотентная обработка». Это важный тезис для интервью.

Ключевые паттерны

  • Backpressure — если потребитель не успевает, нужно замедлить производителя или отбрасывать/буферизовать запросы.
  • Dead Letter Queue (DLQ) — сообщения, которые не удалось обработать после N попыток, отправляются в отдельную очередь для разбора.
  • Retry с exponential backoff — повторные попытки с увеличивающейся задержкой и jitter, чтобы не долбить упавший сервис.

Шардирование и репликация

Это две ортогональные оси масштабирования БД: шардирование увеличивает пропускную способность записи, репликация повышает доступность и масштабирует чтение.

Стратегии шардирования

СтратегияКак работаетПлюсы / минусы
Hash-basedshard = hash(key) % NРавномерно, но при изменении N перераспределяются почти все данные
Range-basedДиапазоны значений (например, по дате или user_id)Простота, но возможны hot shards
Consistent HashingКлючи и узлы отображаются на «кольцо»При добавлении узла перераспределяется только ~1/N данных
Directory-basedОтдельный сервис хранит маппинг ключ → шардГибко, но directory становится SPOF и дополнительным узким местом

Паттерны репликации

ПаттернОписаниеПримеры
Single-leaderОдин узел принимает записи, остальные реплицируют и обслуживают чтенияPostgreSQL streaming replication, MySQL
Multi-leaderНесколько узлов принимают записи, конфликты разрешаются по правиламMulti-region active-active, некоторые конфигурации Cassandra
LeaderlessЛюбой узел принимает и чтение, и запись; кворумы (R + W > N) обеспечивают консистентностьCassandra, DynamoDB, Riak

Replication lag — отставание реплик от лидера. При read-heavy нагрузке это нормально, но если приложение пишет и сразу читает с реплики, пользователь может не увидеть свои же данные. Решения: read-your-writes через привязку сессии к лидеру, quorum reads, или синхронная репликация (ценой latency записи).

Шардирование и репликация не исключают друг друга Типичная схема: данные разбиты на шарды, каждый шард имеет лидера и несколько реплик. Лидер принимает записи, реплики обслуживают чтения. При отказе лидера одна из реплик повышается до лидера (failover).

Модели консистентности

CAP-теорема говорит, что при сетевом разделении нужно выбирать между консистентностью (C) и доступностью (A). На практике выбор сложнее, и на помощь приходит PACELC: если партиций нет (E), нужно выбирать между latency (EL) и consistency (EC). Если есть - между консистентностью (PC) и доступностью (PA).

МодельГарантияЗадержкаПримеры
StrongВсегда читается последняя записьВышеPostgreSQL, Spanner, etcd
EventualРеплики сходятся со временемНижеCassandra, DynamoDB (по умолчанию)
CausalУважается причинно-следственная связьСредняяНекоторые конфигурации MongoDB, CockroachDB
Read-your-writesПользователь видит свои записиЗависит от реализацииСессионная привязка, quorum reads

На интервью важно не просто назвать модель, а объяснить, какой выбор вы делаете и почему. Например: «Для баланса пользователя выбираем strong consistency, потому что показать старый баланс — это баг, а не trade-off. Для ленты новостей — eventual, потому что задержка в пару секунд не критична».


Latency vs Throughput

Latency — время обработки одного запроса. Throughput — сколько запросов система обрабатывает в единицу времени. Это не одно и то же, и оптимизация одного часто ухудшает другое.

Little's Law: N = λ × W, где N — количество запросов «в полёте», λ — интенсивность поступления (RPS), W — время обработки (latency). Если latency выросла, а RPS остался прежним, количество одновременных запросов растёт, и пулы потоков/соединений могут исчерпаться.

Средняя latency обманчива. На больших системах важнее tail latency: p95 и p99. Именно хвост определяет пользовательский опыт и каскадные отказы. Ориентиры: p99 для кэшированных ответов < 100 мс, для некэшированных < 500 мс, для indexed-запросов к БД 1–10 мс.


Паттерны отказоустойчивости

На интервью смотрят не только на happy path, но и на то, как система ведёт себя при отказах.

ПаттернЧто делает
Circuit BreakerПрекращает вызовы к упавшему сервису на время, давая ему восстановиться. "закрытым" (CLOSE) сервисам сообщения отправляются, в "открытом" (OPEN) состоянии - блокируются (и это не опечатка). В "полуоткрытом" (HALF OPEN) проходит часть запросов для проверки состояния сервиса.
Retry с backoffПовторяет запрос с увеличивающейся задержкой, чтобы не усугублять перегрузку.
BulkheadИзолирует пулы ресурсов для разных типов запросов, чтобы отказ одного не уронил всё.
Graceful degradationПри перегрузке отключает второстепенные функции, сохраняя основные.
Single Point of Failure (SPOF)Устраняется дублированием компонентов и health-checks.
Что спросят почти наверняка «Что произойдёт, если узел упадёт?», «Что если дата-центр целиком недоступен?», «Как вы поймёте, что что-то пошло не так?». На каждый из этих вопросов нужен заранее продуманный ответ: failover, multi-AZ, метрики и алерты.

API Design

Когда доходит до deep dive, часто просят «покажите API». Вот минимальный набор, который стоит держать в голове.

  • Идемпотентность. POST с idempotency-key — повторный запрос не создаёт дубликат. Критично для платежей и заказов.
  • Пагинация. Cursor-based предпочтительнее offset-based для больших таблиц: не «плывёт» при вставках и не требует OFFSET N.
  • Версионирование. /v1/orders vs /v2/orders или заголовок Accept-Version. Позволяет менять контракт без ломающих изменений.
  • Rate limiting. Token bucket или sliding window; заголовки X-RateLimit-Limit, X-RateLimit-Remaining, Retry-After.
  • Форматы ошибок. Единый JSON: {"error": {"code": "...", "message": "...", "details": ...}}.

Паттерны работы с данными и транзакциями

Это тема, на которой чаще всего «плывут» кандидаты. Все знают, что такое БД и очередь, но не все понимают, как гарантировать, что запись в БД и отправка события произойдут атомарно. А именно это и есть суть распределённых транзакций.

Проблема dual write

Представьте: сервис заказов принимает запрос на создание заказа. Он должен сохранить заказ в БД и отправить событие OrderCreated в очередь, чтобы сервисы инвентаря и биллинга узнали о новом заказе.

Наивный код выглядит так:

function createOrder(order) {
      db.save(order);                    // шаг 1: сохранили в БД
      messageQueue.publish('OrderCreated', order);  // шаг 2: отправили событие
      return ok;
    }

Проблема в том, что между шагом 1 и шагом 2 может произойти сбой: сетевая ошибка, падение сервиса очереди, таймаут. Тогда заказ в БД есть, а событие потеряно. Инвентарь не знает о заказе, биллинг не выставит счёт. Данные расходятся.

Это называется dual write problem — проблема двух записей. Её нельзя решить простым try/catch: даже если вы откатите БД, событие уже могло уйти в очередь, и отменить его нельзя.

Почему нельзя просто использовать транзакцию БД? Транзакция БД атомарна только внутри одной БД. Она не покрывает отправку в очередь. Если вы отправите событие до коммита, а потом транзакция откатится — событие уйдёт «в пустоту». Если после коммита — при сбое между коммитом и отправкой событие потеряется.

Transactional Outbox

Решение: записать событие в ту же БД, в той же транзакции, что и бизнес-данные. А потом отдельный процесс читает эти события и публикует их в очередь.

Схема:

  1. Сервис начинает транзакцию.
  2. Сохраняет бизнес-объект (заказ) в основную таблицу.
  3. Сохраняет событие в таблицу outbox.
  4. Коммитит транзакцию. Теперь и заказ, и событие гарантированно записаны вместе.
  5. Отдельный воркер (или CDC-коннектор) читает outbox и публикует события в очередь.
  6. После успешной публикации помечает событие как обработанное (или удаляет).
function createOrder(order) {
      db.transaction(() => {
        db.orders.insert(order);
        db.outbox.insert({
          id: uuid(),
          type: 'OrderCreated',
          payload: JSON.stringify(order),
          created_at: now()
        });
      });  // всё или ничего
      return ok;
    }
    
    // отдельный воркер
    function publishOutboxEvents() {
      const events = db.outbox.findUnprocessed();
      for (const event of events) {
        messageQueue.publish(event.type, event.payload);
        db.outbox.markProcessed(event.id);
      }
    }

Гарантия: at-least-once. Событие может быть опубликовано повторно, если воркер упал между публикацией и пометкой. Поэтому потребители должны быть идемпотентными (см. Inbox).

Почему это работает Ключевая идея: мы не пытаемся сделать атомарными БД и очередь. Мы делаем атомарной запись в БД, а очередь становится вторичной — она может отставать, но не может потерять данные, потому что источник истины — это таблица outbox.

Inbox / Idempotent Consumer

Если продюсер гарантирует at-least-once, потребитель должен уметь обрабатывать дубликаты. Паттерн Inbox — зеркальное отражение Outbox на стороне потребителя.

Идея: потребитель, получив сообщение, сначала проверяет, не обрабатывал ли он его раньше. Для этого он ведёт таблицу inbox с идентификаторами обработанных сообщений.

function handleMessage(message) {
      db.transaction(() => {
        const alreadyProcessed = db.inbox.exists(message.id);
        if (alreadyProcessed) {
          return; // дубликат, пропускаем
        }
        
        // обрабатываем сообщение
        processOrder(message.payload);
        
        // записываем факт обработки
        db.inbox.insert({ id: message.id, processed_at: now() });
      });
      messageQueue.ack(message);
    }

Важно: запись в inbox и бизнес-логика должны быть в одной транзакции. Иначе можно обработать сообщение, упасть до записи в inbox, и при повторной доставке обработать снова.

Ключ дедупликации должен быть стабильным: либо producer-assigned message ID, либо бизнес-ключ (например, order_id + event_type).

Вместе Outbox и Inbox дают effectively-once обработку: at-least-once доставка + идемпотентный потребитель = каждое сообщение обрабатывается ровно один раз с точки зрения бизнес-эффекта.

Saga

Transactional Outbox решает проблему атомарности внутри одного сервиса. Но что если бизнес-транзакция охватывает несколько сервисов? Например: создать заказ → зарезервировать товар → списать деньги → отправить уведомление.

Классический ACID здесь не работает: нет единой БД, а 2PC (см. ниже) слишком дорог и хрупок. Вместо этого используется Saga — последовательность локальных транзакций, где каждая следующая запускается после успеха предыдущей. Если шаг падает — выполняются компенсирующие транзакции, которые отменяют эффекты предыдущих шагов.

Пример саги «оформление заказа»:

  1. Order Service: создать заказ (статус PENDING).
  2. Inventory Service: зарезервировать товар.
  3. Payment Service: списать деньги.
  4. Order Service: перевести заказ в статус CONFIRMED.

Если шаг 3 падает — компенсации: вернуть товар на склад (шаг 2), отменить заказ (шаг 1).

ПодходКак работаетПлюсыМинусы
Хореография Каждый сервис публикует событие и слушает события других сервисов. Нет центрального координатора. Слабая связанность, простота добавления новых участников Сложно отследить весь flow, трудно отлаживать
Оркестрация Центральный оркестратор вызывает сервисы по очереди и управляет компенсациями. Централизованный контроль, видимость всего процесса, проще отладка Оркестратор — потенциальный SPOF, дополнительная связанность
Компенсации — это не rollback В распределённой системе нельзя «откатить» уже совершённое действие так же, как в БД. Компенсация — это новое действие, которое семантически отменяет предыдущее. Например, «вернуть деньги» — это не откат списания, а новая транзакция. Она может сама упасть, и тогда нужен retry или ручной разбор.

Two-phase commit (2PC)

Классический протокол атомарного коммита в распределённых системах. Работает через координатора, который сначала спрашивает всех участников «готовы ли вы?» (фаза 1 — prepare), а затем принимает решение «коммит или откат» и рассылает его (фаза 2 — commit/abort).

// Фаза 1: голосование
    coordinator -> participant A: PREPARE
    participant A: блокирует ресурсы, пишет в WAL, отвечает VOTE_YES / VOTE_NO
    
    coordinator -> participant B: PREPARE
    participant B: ... VOTE_YES
    
    // Фаза 2: решение
    if (все VOTE_YES) {
      coordinator -> A: COMMIT
      coordinator -> B: COMMIT
    } else {
      coordinator -> A: ABORT
      coordinator -> B: ABORT
    }

Проблемы 2PC:

  • Блокировки. Участники держат ресурсы заблокированными до конца фазы 2. При медленном координаторе это убивает пропускную способность.
  • Single point of failure. Если координатор падает после фазы 1, участники остаются в неопределённом состоянии.
  • Latency. Два раунда сетевых вызовов, плюс синхронное ожидание.

Поэтому в микросервисах 2PC используют редко — предпочитают Saga и Outbox. Но на интервью важно знать, что это такое, и уметь объяснить, почему не стоит его использовать в большинстве случаев.

Change Data Capture (CDC)

CDC — это способ читать изменения из БД, не меняя код приложения. Вместо того чтобы писать в таблицу outbox, вы подключаетесь к журналу транзакций БД (WAL в PostgreSQL, binlog в MySQL) и превращаете каждое изменение в событие.

Схема:

  1. Приложение пишет только в основную таблицу (без outbox).
  2. CDC-коннектор (Debezium, Maxwell, Oracle GoldenGate) читает WAL/binlog.
  3. Каждое изменение транслируется в событие и публикуется в Kafka или другую очередь.
ПодходПлюсыМинусы
Outbox-таблица Проще понять и реализовать, полный контроль над форматом событий, не зависит от конкретной БД Дополнительная таблица, нужно чистить, воркер — ещё один компонент
CDC Нет изменений в коде приложения, меньше нагрузки на БД (только WAL), события генерируются автоматически Зависимость от внутреннего формата БД, сложнее эволюция схемы, нужен отдельный коннектор
CDC и транзакции CDC читает закоммиченные изменения. Если транзакция откатится, её изменения не попадут в WAL (или будут помечены как откат). Это гарантирует, что события соответствуют только успешно применённым изменениям. Однако при чтении WAL в реальном времени есть нюанс: нужно читать только после коммита, иначе можно увидеть незакоммиченные данные.

Шпаргалка по выбору паттерна

СитуацияЧто использовать
Запись в БД + отправка события в одном сервисеTransactional Outbox
Потребитель получает сообщения с at-least-onceInbox / Idempotent Consumer
Транзакция охватывает 2–5 сервисов, есть чёткие шаги и компенсацииSaga (оркестрация для сложных flow, хореография для простых)
Нужно синхронизировать данные с другой системой без изменения кодаCDC
Один сервис, одна БД, короткая транзакцияОбычная ACID-транзакция
Нужна атомарность между несколькими БД, но нет возможности менять архитектуру2PC (редко, только если действительно необходимо)

Источники и ссылки

Основные материалы

  1. Интервью в Яндекс.Облако — процесс дизайна системы на интервью.
  2. Подготовка к собеседованиям в Tinkoff — разбор процесса и типичных вопросов.
  3. C4 model — официальный сайт подхода.
  4. TechEmpower Benchmarks — источник данных по производительности Postgres.
  5. Azure Dv2/Dsv2 series — спецификации инстансов, использованных в бенчмарке.
  6. Обсуждение статьи в Telegram — задать вопросы и предложить правки.

Паттерны работы с данными

  1. Transactional Outbox — Chris Richardson, microservices.io. Классическое описание паттерна: как атомарно сохранить бизнес-данные и событие для очереди.
  2. Saga — Chris Richardson, microservices.io. Паттерн распределённых транзакций через последовательность локальных шагов и компенсаций.
  3. Transaction Log Tailing — Chris Richardson, microservices.io. Основа CDC: чтение журнала транзакций БД для публикации событий.
  4. Idempotent Consumer — Azure Architecture Center. Как гарантировать, что повторная доставка сообщения не создаст дубликат.
  5. Debezium Documentation — CDC-коннектор для PostgreSQL, MySQL, MongoDB, Oracle и других СУБД. Практическая реализация Transaction Log Tailing.
  6. Transactions in Apache Kafka — Confluent. Как устроены exactly-once транзакции в Kafka и зачем нужны transactional producers.
  7. AWS Prescriptive Guidance: Transactional Outbox — практическое руководство с примерами реализации и сравнением с CDC.

GitHub-репозитории и подборки

  1. The System Design Primer — самый известный репозиторий: организованная коллекция ресурсов, примеры дизайнов, Anki-карточки. Идеальная точка входа.
  2. Awesome System Design Resources — курированная подборка бесплатных ресурсов для изучения концепций и подготовки к интервью. Более 35 000 звёзд.
  3. System-Design-Fundamentals — русскоязычная база знаний перед интервью: план собеседования, высокоуровневый дизайн, погружение в детали.
  4. Awesome System Design (madd86) — ещё одна курированная подборка с акцентом на распределённые системы и микросервисы.
  5. Eventuate Tram — фреймворк Криса Ричардсона для реализации Transactional Outbox и Saga в Java-микросервисах.