CAP-теорема
Предпосылки: базовое понимание репликации (leader/follower, синхронная и асинхронная), понятие network partition, ACID (достаточно знать, что Consistency в ACID — это про constraints).
← Шардинг | Модели консистентности →
Пока база данных живёт на одной машине, всё просто: записал — прочитал — увидел то же значение. Проблемы начинаются, когда данные распределяются по нескольким узлам, связанным сетью.
Два узла: San Francisco и New York. Оба хранят balance = 100. Клиент в San Francisco делает UPDATE balance = 50. В этот момент связь между городами рвётся.
San Francisco New York
┌─────────────┐ X ┌─────────────┐
│ balance=50 │ │ balance=100 │
└─────────────┘ partition └─────────────┘
Клиент в New York хочет прочитать balance. Узел должен решить: что делать?
Network partition — данность, не выбор
Network partition — это не partitioning (шардирование данных). Network partition — ситуация, когда узлы системы не могут общаться друг с другом: сеть разделилась на изолированные части.
ДО partition: ПОСЛЕ partition:
Node A ←──────→ Node B Node A ✕ Node B
│ │ │ │
└───────┬───────┘ │ │
│ │ │
Node C Node C (изолирован)
В распределённой системе partition неизбежен. Сеть ненадёжна: кабель перережут, роутер зависнет, датацентр потеряет связь. Это не вопрос «если», а вопрос «когда». Отказаться от partition tolerance нельзя — если данные на нескольких машинах, сеть между ними может разорваться, и система должна как-то себя вести в этот момент.
CAP-теорема формулирует ограничение: когда происходит network partition, система может сохранить либо consistency, либо availability — но не оба сразу.
«Выбери 2 из 3» — распространённое, но неверное упрощение. P — не опция, а данность. Реальный выбор — между C и A в момент partition.
Два варианта ответа
У узла в New York ровно два пути.
Вариант CP — отказать: «Я не могу связаться с San Francisco. Возможно, там были изменения. Отдавать потенциально устаревшие данные я не имею права.» Клиент получает ошибку. Данные остаются консистентными, но доступность потеряна.
Вариант AP — ответить: «Отдам то, что знаю: balance = 100.» Клиент получает ответ, но значение устаревшее (реальное — 50). Доступность сохранена, консистентность потеряна.
Третьего варианта нет. Нельзя одновременно отдать правильные данные и отдать хоть какие-то данные, когда неизвестно, какие данные правильные.
Consistency в CAP — линеаризуемость
CAP Consistency — это не ACID Consistency. ACID Consistency означает, что транзакция переводит базу из одного валидного состояния в другое, не нарушая constraints (NOT NULL, FOREIGN KEY, CHECK). Это правила внутри одной базы.
CAP Consistency (она же linearizability) — требование к распределённой системе: после завершения записи любое последующее чтение с любого узла вернёт это значение или более новое. Система ведёт себя так, будто копия данных одна. Именно это свойство нарушается, когда New York отдаёт balance = 100 вместо актуальных 50.
Availability в CAP — ответ от каждого узла
CAP Availability — это не SLA availability (99.9% uptime за период). CAP Availability означает: каждый запрос к работающему узлу получает ответ с данными. Не ошибку, не таймаут — ответ. Узел не имеет права сказать «подожди, пока я свяжусь с остальными». CP-вариант нарушает именно это: New York отказывает клиенту, хотя сам узел работает.
Кворум: кто должен продолжать работу
CP-система отказывает изолированному узлу. Но кто определяет, кто изолирован? Правило большинства — кворум: узел обслуживает запросы, только если связан с большинством кластера. Большинство может быть только одно — split brain исключён на уровне арифметики. Поэтому кластеры обычно нечётные (3, 5, 7): при чётном числе возможно разделение без большинства ни у кого.
CP на практике: PostgreSQL с синхронной репликацией
Кворум определяет, кто работает, а кто нет. Но CP-поведение проявляется и на уровне каждой отдельной записи: при синхронной репликации primary не подтверждает COMMIT, пока синхронная реплика не подтвердила получение WAL (Write-Ahead Log).
Клиент Primary Replica
│ │ │
│── INSERT ─────→│ │
│ │── WAL ────────────→│
│ │←── ACK ────────────│
│←── COMMIT OK ──│ │
Если связь с репликой рвётся, primary не может получить ACK — и блокирует все записи. Клиент ждёт, получает таймаут, затем ошибку. Если падает primary — реплика read-only и записи тоже невозможны.
Система жертвует доступностью ради консистентности: данные не расходятся, но при partition записи останавливаются. Настройки, контролирующие это поведение — synchronous_commit и synchronous_standby_names — описаны в репликации PostgreSQL.
PostgreSQL с асинхронной репликацией
Синхронная репликация — не единственный режим PostgreSQL. При асинхронной репликации primary не ждёт подтверждения от реплики и отвечает «COMMIT OK» сразу. Если реплика отвалится — primary продолжает работать. Но если primary упадёт до того, как WAL дошёл до реплики — данные потеряны. Это сдвигает PostgreSQL ближе к AP-поведению.
AP на практике: Redis
Redis-реплики получают данные от master асинхронно. При потере связи с master Sentinel выбирает новый master из доступных реплик.
San Francisco (изолирован) New York
┌────────────┐ X ┌────────────┐
│ Old Master │ │ Replica1 │
│ │ │ Replica2 │
└────────────┘ └────────────┘
Sentinel в New York обнаруживает недоступность master, набирает кворум среди Sentinel-ов и промоутит одну из реплик. Как именно Sentinel обнаруживает сбой (SDOWN → ODOWN), набирает кворум и выбирает реплику — в Sentinel. Но старый master в San Francisco не знает, что его заменили — он продолжает принимать записи от локальных клиентов.
San Francisco New York
┌──────────────┐ X ┌──────────────┐
│ Old Master │ │ NEW Master │
│ balance=500 │ │ balance=100 │
└──────────────┘ └──────────────┘
↑ ↑
клиент пишет клиент пишет
Когда сеть восстанавливается, старый master узнаёт о новом, становится репликой и перезаписывает свои данные данными нового master. Все записи, принятые старым master за время partition, теряются безвозвратно.
CP-система в такой ситуации отказала бы в записи изолированному узлу — клиент получил бы ошибку, но данные не потерялись бы. Redis выбирает доступность: клиент всегда получает «OK», но часть записей может исчезнуть.
Спектр, а не бинарный переключатель
Реальные системы не делятся строго на «чистый CP» и «чистый AP». PostgreSQL со синхронной репликацией — CP, но переключение на асинхронную сдвигает поведение к AP. Redis по умолчанию AP, но команда WAIT позволяет дождаться подтверждения от реплик — приближая к CP-поведению на отдельных операциях. Cassandra идёт дальше: consistency level настраивается per-query — ONE даёт AP-поведение, QUORUM приближает к CP, ALL — жёсткий CP.
Более того, разные части одной системы могут иметь разные требования. Банковская платформа может использовать CP для списаний и зачислений (ошибка в балансе — юридические и финансовые последствия), и AP для отображения истории транзакций (показать данные секундной давности допустимо). Выбор CP или AP определяется ценой ошибки в конкретной операции.
Eventual Consistency
AP-системы сохраняют доступность ценой расхождения данных между узлами. Расхождение не может длиться вечно — когда partition заканчивается, узлы должны синхронизироваться. Гарантия, которую дают AP-системы: данные когда-нибудь станут одинаковыми на всех узлах, но не определено когда — через 10ms, через 5 секунд или позже.
Запись: новый пост
│
v
Node A: видит пост сразу
Node B: ещё не видит ──→ (100ms) ──→ видит
Node C: ──→ ──→ (200ms) ──→ видит
Это и есть replication lag, видимый как свойство системы: записал на одном узле — прочитал с другого — получил устаревшее значение. Eventual consistency — название для гарантии «рано или поздно синхронизируется, но когда именно — не обещаем».
Когда partition заканчивается и узлы AP-системы снова видят друг друга, им нужно разрешить конфликты — ситуации, когда разные узлы приняли разные записи для одних и тех же данных. Типичные стратегии: last-write-wins (по timestamp; одна запись теряется), merge (объединение изменений; работает не для всех типов данных), передача конфликта приложению. Идеального решения нет — это цена AP. Подробнее о стратегиях — в разрешении конфликтов.
Sources
- Gilbert, Lynch, 2002, Brewer’s Conjecture and the Feasibility of Consistent, Available, Partition-Tolerant Web Services: формальное доказательство CAP-теоремы
- Kleppmann, 2017, Designing Data-Intensive Applications, Chapter 9: Consistency and Consensus
- Brewer, 2012, CAP Twelve Years Later: How the “Rules” Have Changed: уточнение от автора теоремы — CAP как спектр, а не бинарный выбор