Job 2026 md

title: Статья 09 — Согласованность и распределённые транзакции source: подготовлено 2026-09-17, TASK-45.49; каркас — brief.md §09; развёртка knowledge-base.md §7; источники — DDIA 2-е изд. гл. 8, 10 (TASK-45.39); отсылки — статьи 05, 07, 08, 10, 11 серии, классические разборы classic-designs.md


Статья 09: согласованность и распределённые транзакции

Раздел о том, на каком языке мы договариваемся о гарантиях данных. Статья 05 дала механику: реплики, лаг, кворумы, failover. Статья 06 — копии в кэше. Здесь появляется то, что над этими механизмами: модели согласованности (какие аномалии пользователь может увидеть), транзакции в распределённой системе (как несколько систем пишут вместе) и консенсус (как система вообще может одинаково договориться о чём-то). Критерий «умею, если» из брифа: объясняю, какую согласованность выбираю для конкретного кейса и чем плачу.

Верхний уровень глубины для интервью — именно он заявлен: «уметь упомянуть на правильном уровне», а не прочитать мини-курс по CAP. Что это значит для нас: не сдавать раздел лекцией, а продавать выбором. Ответ на «какую согласованность выбираете?» строится по трём шагам: (1) назвать нужную гарантию, (2) назвать цену, (3) сказать, где явно от неё отказываемся и чем компенсируем. Формула раздела: «кейс → гарантия → цена». Глубина живёт в ../knowledge-base.md §7; полные конспекты глав — ../materials/ddia-2ed/ch-08-transactions.md и ../materials/ddia-2ed/ch-10-consistency-consensus.md; эталоны, где выбор звучит целиком, — ../classic-designs.md.

Место раздела в секции: на этапе 4 (детали и БД) мы выбираем модель данных и хранилище — и обязаны сказать, какую согласованность даёт выбранная схема; на этапе 6 (эксплуатация) отвечаем, что происходит при отказах нод с данными. Раздел 09 — язык для обоих. Он же пропитывает весь дизайн: мульти-ДЦ из раздела 08 почти всегда означает «между ДЦ — AP, внутри — C»; очереди из 07 — «at-least-once + идемпотентность» вместо распределённых транзакций.

1. Словарь: три оси, на которых стоят все гарантии

Прежде чем говорить о конкретных механизмах, три рамки, в которые укладывается всё, что дальше.

Ось 1 — модели согласованности (что видит читающий). От самой строгой к самой слабой: linearizability (система выглядит как одна копия: после завершения записи все читают новое) → sequential consistency (порядок операций один и тот же для всех наблюдателей, но не обязан совпадать с реальным временем) → causal consistency (причинно связанные операции видят все в одном порядке, параллельные — как получится) → eventual consistency (когда-нибудь догонит). Сессионные гарантии (read-your-writes, monotonic reads, consistent prefix) — не отдельная модель, а ослабление, которое приложение может обеспечить само; статья 05 §3 уже это разобрала — здесь хватает ссылки.

Ось 2 — изоляция транзакций (что делает БД внутри одной системы): read committed → snapshot isolation → serializable. Это про честность конкурентных транзакций, разбираем в §4. Часто путают с осью 1 — и это ловушка (§3): linearizability про свежесть, serializability про порядок.

Ось 3 — атомарность многих систем (как несколько сервисов/БД пишут «вместе»): атомарный коммит (2PC), сага, outbox (§5).

Каждая ось отвечает свой вопрос; неумение их разделять — главный источник «каши» на секции. Простой способ держать их в голове: ось 1 про чтение, ось 2 про одну БД, ось 3 про несколько систем.

2. CAP и PACELC: формулировки без мифов

CAP знают все, но на секции он часто звучит как заклинание. Правильная подача — две фразы.

CAP буквально: при сетевом разделении (partition — P случается всегда, это не опция, это сеть) система должна выбрать: либо Consistency — то есть linearizable чтения/записи (все ноды видят одну свежую копию), либо Availability — каждая нода продолжает принимать запросы (даже если отвечает устаревшим состоянием). Вне разделения CAP молчит — и поэтому бесполезен в обычной жизни: разделения редки (в исследованиях сетевые партиции — это малая доля инцидентов), а плата за согласованность идёт всегда.

PACELC честнее: Partition → Availability vs Consistency; в оставшееся время (Else) Latency vs Consistency. То есть даже когда сеть цела, мы платим латентностью за строгую согласованность: линейзуемость требует синхронного кворума или round-trip к одной точке, а это дополнительные ходы по сети. Причём это не инженерный выбор, а теоретический предел (Attiya–Welch): линейзуемость по своей природе стоит минимум RTT — строгая согласованность принципиально медленнее слабой, предел существует даже для RAM многоядерного CPU.

Формулировки для доски (из KB §7), которые звучат в разы большим пониманием, чем «возьмём AP»:

  • «В одном ДЦ почти всегда C»: партиции внутри ДЦ редки и кратковременны, а за линейзуемость внутри ДЦ мы платим десятки миллисекунд — приемлемо. Цена в проде: мульти-ДЦ «active-active» с синхронной репликацией между континентами — каждая запись ждёт RTT между ДЦ (Москва–NY ~120 мс). Внутри ДЦ C дёшев, между ДЦ — дорог.
  • «Между ДЦ — вынужденно AP с eventual»: тот же сюжет из статьи 08 §9 — между ДЦ допускаем отставание реплик (split brain лечим fencing'ом, а не синхронным кворумом). Отсюда же классика: глобальный сервис с локальными копиями данных (CDN, кэш по регионам) — это осознанное AP.
  • «Консистентность — это обычно компромисс двух осей сразу»: внутри ДЦ выбрали C и платим латентностью; между ДЦ выбрали доступность и платим свежестью и сложностью конфликтов.

Лакмус понимания CAP/PACELC на секции: если интервьюер спрашивает «а что выберете — C или A?», ответ не должен быть «C!» — должен быть «при разделении я выбираю…, а в остальное время…; вот мой кейс». Готовая реплика: «для денежных операций — в одном ДЦ C с полусинхронной репликацией и внятным failover; для лайков — eventual + идемпотентные счётчики, пользователь простит лаг в секунды, если счётчик не капризничает каждый раз».

3. Linearizability vs eventual: лестница и что на ней случается

Самый частый сюжет раздела — выбор между «свежо всегда» и «свежо когда-нибудь». Чтобы выбор был осознанным, надо видеть не два конца, а лестницу с промежуточными ступенями:

Модель Что гарантирует Цена Где живёт на нашей схеме
Linearizability Система как одна копия: запись завершилась → все последующие чтения видят новое значение Синхронный кворум/одна точка записи; латентность ≥ RTT; единственная точка при шардировании Деньги, инвентарь, лидерство, uniqueness (замок, ключ)
Sequential Порядок операций одинаков для всех, время может врать Дёшевле линейзуемости, но семантика «порядок есть, свежести нет» почти нигде не нужна сама по себе Теоретическая ступень; на практике упоминается как «знаю, что есть»
Causal Причинно связанные записи видят все в одном порядке; параллельные — в произвольном Лаги, упорядочение по причинности (ключам/партициям) Лента друзей: комментарий-ответ после комментария-вопроса; «что я писал, вижу в правильном порядке»
Eventual Когда-нибудь догонит; никакой гарантии момента Дешёвая репликация, высокая доступность; нужны сессионные гарантии отдельно Лайки, просмотры, профили, поисковые проекции

Важные оговорки, которые превращают перечисление в инженерное знание:

  • Eventual не значит «вечно рассинхронно»: eventual подразумевает, что при остановке новых записей все реплики в итоге сойдутся. Это свойство, которое надо обеспечивать (read repair, anti-entropy из статьи 05 §4), а не подразумевать. Фраза: «eventual — это контракт „когда-нибудь“, но „когда“ я делаю предсказуемым: лаг в секунды, иначе пользователь видит аномалии».
  • Каждая более слабая ступень — это более сильные требования к приложению: eventual означает, что приложение готово видеть аномалии (потерянный лайк, старая версия, откат комментария). Если на этом чтении бизнес делает решающее действие (списать, зарезервировать) — eventual недостаточен.
  • Сессионные гарантии — дешёвый способ съесть разницу между ступенями: статья 05 §3 показывает, что read-your-writes и monotonic reads обеспечиваются маршрутизацией, а не консенсусом. Именно поэтому «лента» (из статьи 11) может честно говорить: «лента eventual, но свои посты читаю с лидера, а порядок ленты прибит к партиции пользователя — аномалии, которые пользователь может поймать, сводятся к почти нулю».
  • Версии и часы (кратко, на уровне «упомянуть»): сравнение «кто свежее» по wall-clock — это last-write-wins, который тихо теряет данные (часы врут, NTP прыгает — статья 08 §2); параллельные версии детектируют vector clocks (счётчики по узлам — дороже по размеру), total order дают Lamport/HLC. Достаточно на секции сказать: «LWW по времени машины — знаю, что теряет данные; параллельность детектируют векторные часы; конфликт отдаём пользователю или мерджим». Это уже «правильный уровень», дальше — только если интервьюер сам полезет.

3.1 Вопрос-ловушка: linearizability ≠ serializability

Вопрос, который ставят кандидату, чтобы проверить, не механически ли выучены термины: «линеаризуемость и сериализуемость — это одно и то же?» Правильный ответ — нет:

  • Linearizability — гарантия свежести одной операции: как только запись завершилась (или чтение началось после него), все последующие чтения видят новое значение. Про транзакции и их внутренний порядок ничего не говорит.
  • Serializability — гарантия изоляции транзакций: результат эквивалентен какому-то последовательному (одна за другой) исполнению — какой именно порядок, не важно. Про время и свежесть не говорит: транзакции могут быть «свежими» и при этом давними.
  • Вместе — strict serializability (Spanner, FoundationDB): и изоляция, и свежесть — транзакции имеют общий порядок, согласованный с реальным временем.

Следствие, которое любит интервьюер: кворумы W+R>N НЕ дают linearizability (обсуждено в статье 05 §4): они дают свежесть после завершённой записи, но параллельные записи и read repair не сериализуют. А single-leader репликация даёт линейзуемость только пока лидер настоящий (и жив) — как только включается failover со split brain, гарантия исчезает. Из этого следует, где linearizability реально нужна: лок/выборы лидера, uniqueness-ограничения, «cross-channel timing» (запись в одном канале, проверка в другом). А где не нужна: лайки, ленты, кэши — там важны дешевизна и доступность, а не общий порядок, и платить RTT за свежесть каждой записи нечем.

4. Изоляция транзакций: второй эшелон глубины

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

  • Read committed — минимум, обещают почти все СУБД: нет dirty read (не видим незакоммиченное) и dirty write (не перезаписываем чужое незакоммиченное). Реализация: MVCC (читатели видят последнюю закоммиченную версию) + row-lock на запись до коммита.
  • Snapshot isolation (в Postgres фактически это repeatable read) — транзакция читает согласованный снапшот на момент старта: все чтения внутри транзакции видят одну версию мира, даже если другие транзакции параллельно коммитят. Дёшево, потому что ничего не блокирует читателей.
  • Serializable — самый строгий: результат как при каком-то последовательном исполнении. Двумя путями: 2PL (блокировки до конца транзакции — просто, но дедлоки и низкая конкурентность) или SSI (serializable snapshot isolation — MVCC + отслеживание опасных rw-антизависимостей, abort вместо блокировок; современный стандарт — Postgres 9.1+, CockroachDB, TiDB, FoundationDB).

Три гонки — называть по имени и с лечением:

Гонка Что происходит Лечение
Lost update Две транзакции делают read-modify-write одного счётчика, обе записывают, одна перезаписывает другую Атомарная запись (UPDATE ... SET x = x + 1), SELECT ... FOR UPDATE, CAS (UPDATE ... WHERE old_value), автоматический конфликт-детект в MVCC
Write skew Две транзакции читают пересекающийся набор строк и пишут разные строки — инвариант сломан (оба врача сняли себя с дежурства, думая, что второй останется) Serializable, материализация конфликта (общая строка-счётчик), constraint (как правило — только serializable честно решает)
Phantom Предикатный запрос возвращает разные строки внутри транзакции (кто-то вставил подходящую строку) Snapshot закрывает фантомы чтений; для serializable — предикатные/range-lock или SSI

Формулировка для секции: «дефолт — snapshot isolation: дёшево и честно для чтения; гонки счётчиков — атомарной записью/CAS; там, где инвариант бизнеса (деньги, лимиты, дежурства), а не просто арифметика — serializable через SSI или материализованный конфликт». Уровень изоляции — только про одну БД: как только операция затрагивает несколько систем, мы переходим в раздел 5.

5. Распределённые транзакции: 2PC, сага, outbox

Вопрос «как атомарно записать в несколько систем?» имеет три ответа — и порядок выбора на секции примерно такой: сначала outbox (двух систем не атомарно — делаем одну), потом сага (атомарность не нужна — нужна согласованность шагов), 2PC — избегаем умышленно и говорим почему.

5.1 2PC: механизм существует, но в high-load мы его избегаем

Two-phase commit: координатор спрашивает у всех участников prepare (заморозились, гарантируем коммит), потом commit всем. Красиво в теории — атомарность в распределённой системе достигнута. На практике три причины, почему на секции правильнее сказать «избегаю»:

  1. In-doubt транзакции: участник, получивший prepare и упавший до commit, держит лока (а вместе с ними читатели и конкурентные писатели) — «зависшие» транзакции разбирают вручную, когда координатор придёт в себя. Это реальность XA в проде.
  2. Координатор — SPOF и точка синхронизации: упал координатор — вся транзакция стоит; «heuristic decisions» (участник сам решил, коммитить или откатывать) ломают атомарность тихо — ремонт дороже, чем отказ.
  3. Цена блокировок на весь срок транзакции — в распределённой транзакции он непредсказуем (сеть, очередь, паузы GC — статья 08 §2), а блокировки держатся до конца.

Об этом есть честная, готовая фраза: «2PC — это атомарность ценой точки отказа и блокировок; в высоконагруженных системах я избегаю 2PC и применяю саги» — и сразу перевернуть в конструктив: «вместо атомарности многих систем — одна атомарность (БД + outbox), остальное — компенсации».

5.2 Сага: не атомарно, но согласованно

Saga — последовательность локальных транзакций (каждая в своей системе), где на каждый шаг есть компенсирующее действие: order → reserve → charge → ship; откат — cancel → refund → restock. Ключевое отличие от 2PC: атомарности нет — на короткое время состояние «частично выполнено» видно наружу, но благодаря компенсациям в итоге система приходит в согласованное состояние. Цена — компенсации надо проектировать с первого дня (не бывает «отменить резерв» задним числом) и идемпотентность повторных шагов (ретраи в саге — норма).

Два способа управлять сагой — называть оба и выбирать по кейсу:

  • Оркестрация: центральный координатор (сага-менеджер) вызывает шаги по очереди и компенсирует при отказе. Плюс — всё видно в одном месте, простое мышление о бизнес-процессе. Минус — координатор сам становится узким местом/SPOF (но это stateless-оркестратор с перезапуском процесса — дешевле, чем 2PC-координатор, потому что не держит блокировки).
  • Хореография: шаги связаны событиями через шину: «OrderCreated → PaymentService резервирует → ReservedEvent → ShipmentService…». Плюс — нет центральной точки, сервисы слабо связаны. Минус — поток процесса размазан по событиям, читать и отлаживать его сложнее, компенсации тоже события. Для простых цепочек (2–3 шага) хореография избыточна.

Уровень для интервью — не «как реализовать сагу», а «когда нужна сага и чем плачу»: «мульти-сервисная запись без атомарности: шаги локально транзакционны, откат — компенсации, повтор — идемпотентность; оркестрация для управляемого процесса, хореография для слабосвязанных сервисов».

5.3 Transactional outbox: стандартный ответ вместо двойной записи

Механизм из статьи 07 §10 — здесь он превращается в ответ на 2PC-соблазн: «БД записала, событие потерялось» решается не распределённой транзакцией, а композицией. Событие пишется в ту же транзакцию, что и данные (outbox-таблица), публикатор (воркер или CDC — Debezium) доставляет в Kafka и помечает прочитанным. Атомарность не между системами, а в одной БД — чего достаточно, потому что точно-once между системами не существует: есть at-least-once + дедупликация.

Фраза, которой закрывается любой вопрос «а как две системы пишут вместе?»: «двойную запись не делаю: данные и событие — в одной транзакции через outbox; доставка — at-least-once + idempotency key; если нужен согласованный бизнес-процесс из нескольких шагов — сага с компенсациями; 2PC с XA избегаю». Рядом — обязательный пас в статью 07 §5: идемпотентность потребителя (таблица обработанных message-ID в локальной транзакции) — это и есть «exactly-once без распределённых транзакций», так делает Kafka Streams.

Упомянуть одним словом (по запросу): NewSQL (Spanner, CockroachDB, TiDB, FoundationDB) — системы, которые дают внутренние распределённые транзакции через реплицированный координатор на консенсусе — без XA-болей, но с ценой в консенсусе на каждый коммит и ограничением географии (Spanner — синхронные реплики в пределах региона/континента). На секции: «если БД сама умеет распределённые транзакции (NewSQL) — атомарность многошардовая возможна, но дорогая; выбираю её только там, где бизнес реально требует атомарности шире одной шарда».

6. Консенсус: как несколько нод договариваются одинаково

Консенсус — фундамент, о котором на секции достаточно сказать правильные вещи на правильном уровне: зачем, что гарантирует, как устроен, какие сервисы дают его готовым. Не больше.

Зачем. Консенсус — класс эквивалентных задач: выбор единственного значения, линейзуемый CAS, shared log (total order broadcast — «все ноды видят одно и то же в одном порядке»), atomic commit, fetch-and-add. На практике системы дают shared log, а поверх — state machine replication (все реплики применяют одну последовательность команд → одинаковое состояние). Из этого вырастает: линейзуемые транзакции (etcd-подход), fencing tokens (эпоха), leader election, координация вообще. Для секции достаточно связи: «консенсус необходим там, где нужен общий порядок или общее единственное значение — выборы лидера из 05 §5, fencing из 08 §8, разделяемый лок».

Свойства. Три safety-свойства держатся всегда: agreement (все договорились об одном значении), integrity (дважды не договариваются), validity (значение не выдумано). Плюс termination (liveness) — «система в итоге договорится», и вот оно — требует живого большинства: 3 ноды терпят 1 отказ, 5 нод — 2. Это та самая арифметика majority из статьи 08 §9, здесь она называется своим именем.

Механика в одну фразу. Raft: term/эпоха — уникальный лидер эпохи; два голосования — выборы лидера и коммит записи — на пересекающихся кворумах (разные большинства в разные моменты обязательно пересекаются); каждая запись синхронно реплицируется в кворум, поэтому не теряется. Отличие от 2PC в одной фразе: «2PC требует согласия всех (и умирает от одного отказа), консенсус — кворума; поэтому консенсус даёт и отказоустойчивость, и линейзуемость». Цена: консенсус синхронен (каждая запись — round-trip к кворуму), таймауты чувствительны к вариативности сети, больше нод — медленнее кворум.

Практика. Никто не пишет свой Raft на секции: координационные сервисы — ZooKeeper, etcd, Consul (по образцу Chubby) — аутсорсят консенсус на 3–5 нод для тысяч шардов. Что они дают из коробки: locks/leases + fencing tokens (таймаут-аренды, защита от зомби-лидера — статья 08 §8), failure detection (сессии, ephemeral nodes), change notifications, service discovery (который, кстати, кэшировать, а не читать синхронно — линейзуемость для discovery не нужна, KB §13.5). Классика применения в наших схемах: лидерство фоновых воркеров (fan-out ленты, пересчёт кэша), дедуп критичных операций, fencing при failover. Распределённые локи: Redis/Redlock — быстро, но компромисс по надёжности; ZK/etcd — кворумные и надёжные, но дороже; при локах всегда проговаривать fencing token и «лок ≠ транзакция».

Альтернативы консенсусу — назвать, чтобы показать, что мы умеем выбирать: single-leader репликация даёт линейзуемость, пока лидер жив и настоящий (дешевле консенсуса — несинхронный кворум не нужен; но failover уже нелинейзуем — статья 05 §5); кворумы W+R>N дают свежесть, не linearizability (статья 05 §4, доказательство — DDIA гл. 10). Вывод-фраза: «консенсус — последняя инстанция: беру, когда без общего порядка никак (выборы, fencing, атомарный CAS, общий лог); в остальном хватает single-leader и кворума с известными оговорками».

7. Мини-таблица: кейс → уровень согласованности → чем плачу

Ядро раздела — критерий брифа. Таблица — не «наизусть», а мысленный шаблон: для любого кейса провести строку — что гарантирую, каким механизмом, чем плачу. Примеры разобраны по эталонам (§8) и по типам данных секции.

Кейс Уровень согласованности Механизм Чем плачу
Деньги: баланс, списание, перевод Linearizable на операцию + serializable в БД Одна БД с лидером (single-writer), snapshot/SSI, идемпотентность (idempotency key для платёжных вызовов) Латентность записи, одна точка записи на шард; мульти-ДЦ — только active–passive (запись живёт в одном ДЦ)
Инвентарь/лимиты (бронь, остатки) Атомарная запись с условием (CAS) UPDATE ... SET n = n − 1 WHERE n > 0, SELECT ... FOR UPDATE Конкурентность: конфликты → ретраи и UX «попробуйте ещё раз»
Лента (посты друзей) Eventual + причинный порядок по подпискам; свои посты — read-your-writes Async-репликация + маршрутизация «своё с лидера» (05 §3) Лаг в секунды; «лента на момент открытия» — пользователь прощает
Счётчики (лайки, просмотры, followers) Eventual, в итоге догонит; допускается временный рассинхрон Async-инкремент, агрегация в фоне (таблица счётчиков или агрегатор на Kafka, 07), идемпотентность по (user, post) Счётчик «врёт» в моменте; повторы гасятся дедуп-ключом
Профиль пользователя (имя, аватар, настройки) Read-your-writes своей записи, остальным eventual Свои данные — с лидера; чужие — реплики/кэш с TTL Латентность свежести чужих профилей; кэш-инвалидация
Поисковый индекс Eventual, лаг минуты; консистентность индекса не требуется CDC/проекция (07 §10) Индекс отстаёт; search работает на «почти свежих» данных — норма
Уникальность: username, short link, order id Linearizable (fetch-and-add) — уникальный ключ обязан быть уникален всегда Single-source: лидер/последовательность в БД, уникальный индекс, либо консенсус (ZK/etcd) Централизация или консенсус; распределённые ID (снежинка) — только если уникальность без порядка (статья 10)
Выборы лидера / лидерство фонового воркера Strong (линейзуемый CAS/выборы) + fencing ZK/etcd: ephemeral-узел/лидер-эпоха; fencing token при каждом действии Внешняя зависимость: кластер ZK/etcd из 3–5 нод и его латентность на каждое действие
Сессия пользователя/кэш Устаревшая версия допустима — eventual с TTL Кэш + TTL + инвалидация (06) Промах = лишний round-trip, а не ошибка
Аналитика/метрики Eventual, потеря части допустима (best-effort) Очередь + батчи (07), потеря при переполнении чистая Минус точности метрик

Правило чтения таблицы: строка означает осознанный выбор, а не «вот это всегда так». Изменение кейса меняет строку: если бизнесу нужна мгновенная глобальная консистентность счётчиков — строка «Счётчики» переезжает на сильный уровень, и цена вырастает на порядок. Три аксиомы, которые стоят за таблицей:

  1. Слабее согласованность — шире класс аномалий, которые видит пользователь; выбирать её можно только с пониманием, какие именно аномалии допустимы.
  2. Каждая сильная гарантия — это где-то больше латентности/меньше доступности (PACELC) — называть цену всегда.
  3. Идемпотентность — общая валюта: в каждой строке, где появляются ретраи и повторы (платёж, лайк, бронь), идемпотентный приём повторов дешевле, чем linearizability ради отсутствия повторов.

8. Прикладное: как это звучит в эталонах

Лента новостей (главный билет — ../classic-designs.md §2). Классика «какую согласованность выбрали»: свой пост — линеаризуемая запись + read-your-writes (виден сразу — читаем с лидера), ленты подписчиков — eventual с лагом в секунды через fan-out в асинхронные проекции; счётчики лайков — eventual с дедупом; посты селебрити — fan-out on read. При сбое — деградация «лента стареет» (08 §7), а не строгая запись. Цена названа явно: «пользователь не видит пост друга секунду-две — терпимо; видит вечно исчезающие счётчики — нет, поэтому лайки идемпотентны». Это и есть ответ на «какую согласованность выбираете».

Мессенджер (§3). Порядок сообщений — фактически causal по переписке (порядок в пределах чата): чат прибит к одной партиции по chat_id, порядок — порядок в партиции, по построению; офлайн-доставка — eventual-проекция. Уникальность message id — без консенсуса (snowflake — статья 10). Отдельная нота: «порядок сообщений — это не linearizability, а согласованность в пределах беседы; глобального порядка нет, и он не нужен».

URL shortener (эталон Яндекса, §1). Чтение доминирует, ничего строгого нет: KV-зеркало + кэш — eventual (статья 11), запись короткой ссылки — попадает в БД (leader) и проекцией в KV; уникальность короткого ключа обязана быть — поэтому генерация ключа на центральном сервисе/последовательности с уникальным индексом (линеаризуемо для ключа, остальное eventual). Согласованность «стоит» только на ключе — и это осознанный размен: «99 % запросов — чтение; свежесть ключа нужна только через секунду после создания — её и гарантируем».

Медиа-хостинг (§4). Метаданные записи — one leader БД; сам файл в S3, его «последняя версия» — eventual по CDN, и это единственный допустимый режим мульти-регион; подтверждение «файл загружен» — по записи в БД, а не по немедленной доступности с CDN. Никаких распределённых транзакций в пайплайне: outbox для событий «upload → transcode → publish», сага для компенсаций при падении транскодера (07 §10/08 §12).

Сквозной приём — после выбора согласованности почти всегда задать второй вопрос себе: «а что видит пользователь, если гарантия не выполнилась?» Если ответ «ничего страшного» — выбор правильный; если «баг» — гарантия сильнее или компенсация.

9. Ошибки этого раздела

Ошибка Как выглядит Противоядие
CAP как заклинание «Возьмём AP» без объяснения, в каком разделении и зачем CAP — только при разделении; PACELC честнее; формула «кейс → гарантия → цена» (§2)
Не названа цена «Нужна strong consistency» — а платить нечем Цена всегда: латентность/блокировки/меньше доступности/дороже инфраструктура (§2, §7)
Linearizability = serializability Слил два термина в «это когда всё сразу свежее и правильно» Развести: свежесть vs порядок; strict serializability — оба (§3.1)
«У нас eventual» как флаг Куда ни ткни — «eventual», без выбора кейсов Таблица §7: eventual — осознанное решение для конкретного типа данных с перечислением аномалий
2PC «по умолчанию» «Запишем атомарно через 2PC» на эталонном дизайне Избегаю + три причины; конструктив — outbox + сага (§5.1–5.3)
Сага без компенсаций «Сделаем сагу» — а компенсации не спроектированы Компенсирующие действия — часть дизайна с первого дня; идемпотентность шагов (§5.2)
Кворум = linearizability «W+R>N — значит сильная согласованность» Кворум даёт свежесть; linearizability — консенсус/single-leader (§6, 05 §4)
Консенсус = «сложно, не надо» Никогда не упоминается, хотя выборы лидера есть в схеме Назвать, где консенсус в схеме фактически: failover, fencing, лидерство воркеров (§6)
«Поставим свой Raft» Рисуем свой консенсус в дизайне ленты Координационные сервисы (ZK/etcd) — готовый консенсус, не изобретаем (§6)
Без сессионных гарантий Eventual, а пользователь видит «потерянный» свой пост Своё — с лидера, монотонные чтения маршрутизацией (05 §3) — дёшево, а эффект велик
LWW по часам «Свежее по таймстампу» — и тихая потеря данных Vector clocks или отказ от параллельной записи (§3)

10. Что назвать на секции (чек-лист раздела)

Обязательные фразы и действия. Полный чек-лист — ../methodology.md §7; канон — ../knowledge-base.md §7.

На этапе 1 (требования): из нефункциональных требований вытащить консистентность: «какую свежесть допускает пользователь?» — это часть требований, а не наш вкус. Зафиксировать на доске рядом с доступностью и latency-SLO.

На этапе 4 (детали + БД): - Назвать выбор согласованности явно с ценой для каждой таблицы/проекции: «посты линейзуемо (свой — с лидера), лента eventual — лаг секунды, счётчики eventual + идемпотентность». Ссылаться на таблицу §7. - Если в схеме есть асинхронная проекция/событие — сказать «outbox в одной транзакции с данными, доставка at-least-once, потребитель идемпотентен» (§5.3, подробно 07). - Если несколько систем пишут процесс — «сага, шаги локально транзакционны, компенсации спроектированы, оркестрация» (§5.2). - Изоляция: «snapshot по умолчанию; счётчики — атомарной записью; инварианты — serializable/SSI» (§4).

На этапе 6 (эксплуатация/отказы): - Выборы и fencing: «детект кворумом → выборы самой свежей реплики → fencing-токен от зомби» (05 §5/08 §8) — и назвать консенсус под этим: «перевыборы лидера — это консенсус на ZK/etcd, эпоха — это fencing». - Мульти-ДЦ: «внутри ДЦ C, между ДЦ — AP с eventual; синхронная запись между континентами недопустима — латентность» (§2, 08 §9). - На «кворумы дают strong» — поправить: «свежесть чтения дают, линейзуемость нет — там консенсус/single-leader» (§6).

Сквозные формулы раздела: - «Кейс → гарантия → цена: называю уровень согласованности, механизм и чем плачу в каждом узле». - «2PC избегаю: точка блокировки и отказа; вместо атомарности многих систем — одна атомарность (БД + outbox), остальное — сага с компенсациями и идемпотентностью». - «Linearizability — свежесть; serializability — порядок; строгие обе гарантии только вместе (strict serializable)». - «Консенсус — там, где нужен общий порядок: выборы, fencing, атомарный CAS; всё остальное — single-leader + кворум с известными оговорками». - «Между ДЦ — AP; внутри ДЦ — C; за любую C платим латентностью (PACELC)».

11. Самопроверка «умею, если»

Критерий раздела из брифа: объясняю, какую согласованность выбираю для конкретного кейса и чем плачу. Проверка с закрытыми материалами: взять три кейса из таблицы §7 (деньги, лента, уникальность ID) и для каждого вслух провести формулу «гарантия → механизм → цена» за 60 секунд; затем ответить на три зонда: (1) «чем linearizability отличается от serializability?», (2) «почему не 2PC?», (3) «где у вас консенсус в схеме?». Маркеры готовности: (1) на «какую согласованность выберете?» вперёд вылетает строка «кейс → гарантия → цена», а не название системы; (2) определение PACELC произносится автоматически вместе с ценой; (3) анкета «а что видит пользователь при рассинхроне?» — обычный ход, а не сюрприз; (4) опасные места (2PC, LWW, кворум ≠ linearizability) называются предупреждающе, без подсказки. Протокол тренировок и рубрика 0–3 — ../methodology.md §6; сверка с эталонами — ../classic-designs.md §1–4. Если таблица §7 «проходит» по любым трём кейсам за минуту каждый и разговор про согласованность всегда заканчивается ценой — раздел готов.

Связанные документы

  • 00-overview.md — обзорная статья по всем 12 разделам брифа (TASK-45.40); §09 — карта раздела
  • 01-interview-framework.md — фиксация требований (свежесть как NFR) на этапе 1; возврат к листу на этапе 6
  • 02-estimations.md — латентности для оценки цены согласованности (RTT, меж-ДЦ)
  • 05-replication-sharding.md — механика: лог, sync/async/semi-sync, replication lag и сессионные гарантии (§3), кворумы ≠ linearizability (§4), failover/выборы/fencing (§5)
  • 07-queues-streams.md — outbox/CDC (§10), семантики доставки и идемпотентность (§5, §7)
  • 08-fault-tolerance.md — отказы нод с данными, fencing, majority-коммит и мульти-ДЦ (§8–9)
  • 11-reference-designs.md — эталоны, где выбор согласованности звучит целиком (лента §3, мессенджер §4, медиа §5, shortener §2)
  • ../brief.md — бриф: раздел 09 с критерием «умею, если»
  • knowledge-base.md — §7 консистентность и распределённые транзакции (CAP/PACELC, модели, изоляции, 2PC/saga/outbox, консенсус, часы), §13.5 распределённые локи
  • methodology.md — §4 топ-10 ошибок, §6 протокол тренировок, §7 карточка на секцию
  • classic-designs.md — §1 shortener, §2 лента, §3 мессенджер, §4 медиа
  • materials/ddia-2ed/ch-08-transactions.md — источник: изоляции, гонки, 2PC, NewSQL, zero-2PC (TASK-45.39)
  • materials/ddia-2ed/ch-10-consistency-consensus.md — источник: linearizability vs serializability, CAP/PACELC, консенсус, координационные сервисы, fencing (TASK-45.39)
  • Соседние статьи: 08-fault-tolerance.md (отказы и fencing — переиспользует язык консенсуса) · 10-components-patterns.md (распределённая генерация ID — обходит консенсус) · 12-operations-observability.md (метрики рассинхрона: replication lag, лаг проекций)