8 мин

Теорема CAP Брюэра: как думать о C, A и P в распределении

Разбираем теорему CAP как ментальную модель: что означают C, A и P, почему разделения сети неизбежны и как выбирать компромиссы в системах.

Теорема CAP Брюэра: как думать о C, A и P в распределении

Зачем знать CAP: ментальная модель, а не формула

Теорема CAP часто звучит как абстрактная «математика про три буквы», но на практике это удобная ментальная модель: она помогает заранее решить, что именно система должна делать при сбоях связи, а не спорить постфактум, почему «вдруг всё сломалось».

Кто такой Эрик Брюэр и почему CAP обсуждают до сих пор

Эрик Брюэр — исследователь и практик распределённых систем, который в конце 1990‑х сформулировал идею, позже известную как теорема CAP. С тех пор инфраструктура стала быстрее и сложнее, но ключевая проблема не исчезла: сеть может подвести, а данные могут оказаться в нескольких местах одновременно.

CAP продолжают обсуждать, потому что она называет неприятную правду вслух: некоторые требования «хочу всё сразу» несовместимы в момент сетевого разделения.

CAP — не оценка качества, а язык компромиссов

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

  • какие операции обязаны быть строго согласованными;
  • какие функции важнее не останавливать ни при каких условиях;
  • что делать, когда части системы перестают видеть друг друга.

Какие вопросы эта статья поможет решить

По сути, CAP помогает перевести разговор из «нам нужна надёжность» в конкретику:

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

Короткий пример: один пользователь, два дата-центра, пропала связь

Представьте сервис с двумя дата-центрами. Пользователь меняет адрес доставки в одном, а через минуту оформляет заказ через другой, но связь между центрами пропала. В этот момент системе нужно выбрать: отказать в операции ради согласованности или принять операцию, рискуя расхождением данных.

CAP полезна тем, что заставляет принять это решение заранее — на уровне продукта и архитектуры. Это особенно важно, когда вы быстро собираете MVP или внутренний сервис: в TakProsto.AI удобно накидать прототип (web на React, backend на Go + PostgreSQL) и сразу зафиксировать в «planning mode», какие операции у вас CP, а какие можно оставить AP — до того, как появятся реальные пользователи и инциденты.

C, A и P простыми словами: что именно измеряется

Теорема CAP оперирует тремя свойствами распределённой системы. Важно: это не «про качество продукта вообще», а про поведение хранилища/сервиса данных в момент, когда часть узлов перестаёт видеть часть других узлов.

Consistency (C): что клиент видит после записи

Consistency в CAP — это не «данные всегда логически верные» и не «бизнес-правила соблюдены». Речь про наблюдаемую согласованность между копиями данных.

Простая проверка: если клиент записал значение, а потом (или другой клиент) читает эти данные, увидит ли он самую свежую запись, как будто система одна-единственная?

Если ответ «да» — система ближе к C. Если иногда читается «вчерашняя версия» — C ослаблена (и это может быть осознанным выбором).

Availability (A): что значит «всегда отвечает»

Availability в CAP — не то же самое, что аптайм 99.9% по мониторингу. В CAP это свойство звучит так: каждый запрос к работающему узлу получает ответ (не обязательно «успешный» по смыслу бизнеса), и ответ приходит за разумное время.

Ключевой момент: если система в проблемном режиме начинает «подвисать» или часто возвращать таймауты, это уже удар по A, даже если формально «серверы живы».

Partition tolerance (P): работа при разделении сети

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

В условиях разделения система должна решить, что важнее:

  • C: лучше отказать части запросов, чем отдать устаревшие данные;
  • A: лучше ответить, даже если данные могут быть не самыми свежими.

Почему слова в CAP обманчивы

Термины совпадают с «обычными» словами, но означают более узкие вещи:

  • availability ≠ общий аптайм сервиса;
  • consistency ≠ «данные корректны по бизнес-логике»;
  • partition tolerance ≠ «сеть никогда не ломается», а «мы умеем жить, когда она ломается».

Почему P неизбежно: сеть ломается и это нормально

В теореме CAP буква P (Partition tolerance) иногда воспринимается как «ещё одна характеристика, которую можно улучшать». На практике для распределённых систем P — не настройка, а факт реальности: сеть периодически ведёт себя так, будто часть узлов не может связаться с другой частью.

Как возникают разделения

«Разделение» (partition) появляется не только при физическом обрыве. Его провоцируют и более «мягкие» ситуации:

  • обрывы каналов или деградация маршрутов между дата-центрами/зонами;
  • высокие задержки: сообщение формально дойдёт, но слишком поздно;
  • потери пакетов и повторные передачи, которые превращают нормальный обмен в хаос;
  • агрессивные таймауты: система решает, что сосед недоступен, хотя он жив.

С точки зрения приложения итог один: часть запросов не может надёжно подтвердить, что другая сторона получила или обработала данные.

Почему «идеальная сеть» — опасное допущение

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

Чем partition отличается от «сервер упал»

Когда сервер упал, картина относительно ясная: он не отвечает никому. При partition всё коварнее: сервер может быть жив, обслуживать локальных клиентов и писать данные — просто другая часть системы его «не видит». В итоге возникают два «островка истины», которые расходятся во времени и состояниях.

Практический вывод

Если у вас более одного узла и между ними сеть, то P неизбежно. Поэтому главный вопрос не «как избежать partition», а как система должна себя вести, когда оно случилось: продолжать отвечать ценой расхождения данных или останавливать часть операций ради строгой согласованности.

Главный вывод CAP: при разделении выбираем C или A

Классическая формулировка теоремы CAP звучит так: если сеть разделилась (partition), то система не может одновременно гарантировать и согласованность (C), и доступность (A).

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

Что именно означает «выбираем»

При сетевом разделении у вас остаётся два принципиальных варианта поведения:

  • CP (Consistency + Partition tolerance): сохраняем согласованность, но готовы потерять доступность для части запросов.
  • AP (Availability + Partition tolerance): сохраняем доступность, но готовы потерять строгую согласованность.

«P» почти всегда считаем неизбежным свойством распределённой системы, поэтому на практике CAP чаще читают как: при разделении выбираем между C и A.

Если выбираем CP: чем платит пользователь

В CP-подходе система предпочитает не отвечать, чем ответить противоречиво.

Пользовательский опыт обычно выглядит так:

  • запрос может завершиться ошибкой (например, 503) или таймаутом;
  • запись/изменение может быть отклонено до восстановления связи между частями кластера;
  • данные, которые система всё же возвращает, не «расходятся» между узлами.

Проще: лучше «временно недоступно», чем «доступно, но неправда».

Если выбираем AP: чем платит пользователь

В AP-подходе система старается всегда ответить, даже если разные части кластера видят мир по‑разному.

Для пользователя это может означать:

  • он видит «старые» данные после обновления;
  • два пользователя одновременно получают разные ответы на один и тот же вопрос;
  • возможны конфликты при записи, которые будут разруливаться позже.

Проще: лучше «работает сейчас», но возможны расхождения, которые потом сойдутся.

Частая ошибка: думать, что CAP — это про «всегда C» или «всегда A»

CAP не требует выбрать один режим навсегда для всей системы. Реальные продукты часто принимают решение по операциям и по частям данных: например, платежи делать CP (строгая корректность важнее), а ленту обновлений — AP (важнее отклик и непрерывная работа).

Теорема лишь фиксирует границу: когда сеть разделилась, одновременно «всегда правильно» и «всегда доступно» не получится.

CP и AP на практике: что чувствует пользователь

Разделите операции по CAP
Разложите операции по риску: платежи и права как CP, ленты и счётчики как AP.

CAP часто обсуждают как выбор «внутри системы», но на самом деле он проявляется в том, что видит и переживает пользователь в момент проблем со связью между узлами.

Если система выбирает CP: лучше отказ, чем «старые» данные

В CP-подходе при сетевом разделении система старается не выдавать ответ, который может противоречить «истине». Поэтому пользователь чаще сталкивается с таймаутами, ошибками «сервис недоступен», повторной отправкой запроса, иногда — с временной блокировкой операций.

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

Так обычно делают там, где цена ошибки выше цены ожидания:

  • деньги и балансы (оплата, списания, остаток на счёте);
  • права доступа и безопасность (кто может видеть/менять данные);
  • уникальность и инварианты (нельзя создать два заказа с одним номером).

Иными словами, система предпочитает честно отказать, чем показать потенциально «старые» данные или принять действие, которое потом придётся откатывать.

Если система выбирает AP: отвечаем всегда, но возможны расхождения

В AP-подходе сервис продолжает отвечать даже при проблемах связи между частями системы. Пользователь реже видит ошибки, интерфейс остаётся «живым», действия принимаются и подтверждаются.

Обратная сторона — временные расхождения. Пользователь может увидеть:

  • разный статус одного и того же объекта на разных устройствах;
  • «пропавшее» действие, которое появится через несколько секунд;
  • счётчик просмотров/лайков, который скачет или обновляется с задержкой.

Это хорошо подходит для сценариев, где важнее непрерывность, а не мгновенная точность: ленты и рекомендации, просмотры, телеметрия, аналитика, кэшированные карточки контента.

Где проходит граница: допустимая временная неточность

Практический вопрос не «CP или AP вообще», а «для каких операций и данных». Часто один продукт комбинирует подходы: платежи и права — CP, а лента и счётчики — AP.

Полезная проверка границы предметной области: если пользователь может принять неверное решение из-за временной неточности (купить не то, получить лишний доступ, увидеть неправильный баланс) — тяготеем к CP. Если же неточность лишь слегка ухудшает опыт и сама «рассосётся» при синхронизации — AP обычно оправдан.

Согласованность — это спектр, а не переключатель

Многие читают теорему CAP так, будто «согласованность» — это кнопка: либо данные всегда одинаковые везде, либо «хаос». В реальных системах согласованность почти всегда выбирают как диапазон приемлемого расхождения и правила, как с ним жить.

Что пользователь видит в типичных сценариях

Представьте две реплики.

Вы записали новое значение в реплику A (например, изменили адрес доставки). Сразу после этого запрос на чтение попадает в реплику B — и там ещё старый адрес. Это не обязательно ошибка: это проявление задержки распространения изменений.

Ещё один сценарий — конфликт обновлений: два клиента почти одновременно меняют один и тот же объект в разных репликах (например, редактируют профиль или корзину). В какой-то момент система должна решить, что считать «истиной».

Уровни согласованности: от строгой до «в итоге»

Удобно думать о согласованности как о шкале:

  • Strong consistency: после успешной записи все последующие чтения видят новое значение. Проще для бизнес-логики, но чаще дороже по задержкам и доступности.
  • Bounded staleness (ограниченная устарелость): чтения могут быть «старыми», но не старше заданного предела (по времени или по числу версий). Это компромисс, который можно измерять.
  • Eventual consistency: если обновлений больше нет, реплики со временем сойдутся. В моменте чтение может быть устаревшим, и это нужно учитывать в продукте.

Как примирять расхождения (идея, не магия)

Когда возможны конфликты, заранее выбирают стратегию:

  • Last-write-wins: побеждает «последняя» запись (по времени/версии). Просто, но может терять изменения.
  • Merge: объединение полей/операций по правилам домена (например, складывать количества в корзине, а не перетирать).
  • CRDT: структуры данных, которые гарантированно сходятся при обмене операциями между репликами.

Главное — формулировать требования измеримо: «устаревание не более X секунд», «выплата без двойного списания», «две правки не должны теряться». Так согласованность становится управляемым параметром, а не верой в идеальную синхронность.

Кворум и репликация: где прячется компромисс

Репликация нужна, чтобы сервис не падал из‑за одной машины и чтобы данные были ближе к пользователю. Но как только у вас несколько копий одних и тех же данных, появляется вопрос: сколько копий должны подтвердить операцию, чтобы мы считали её успешной? Здесь и прячется компромисс между согласованностью (C) и доступностью (A).

Кворум на интуитивном уровне

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

Интуиция простая: если чтение и запись опираются на пересекающиеся множества реплик, то шанс увидеть «старое» состояние заметно падает.

Как настройки чтения и записи балансируют C и A

Во многих хранилищах можно выбрать параметры вида:

  • W (write quorum) — сколько реплик должно подтвердить запись
  • R (read quorum) — со скольких реплик мы читаем/сверяем ответ
  • N — общее число реплик

Упрощённое правило: если R + W > N, чтения и записи пересекаются, и система чаще ведёт себя «как согласованная». Если сделать W меньше, записи будут проходить при меньшем числе доступных узлов — это повышает доступность, но увеличивает риск прочитать устаревшие данные.

Важно: это не магическая гарантия, а настройка «в какую сторону» вы смещаете поведение при сбоях и задержках.

Цена кворума

Кворум — это плата за уверенность:

  • Задержка: нужно дождаться нескольких подтверждений, а медленная реплика тормозит всю операцию.
  • Стоимость: больше сетевого трафика и нагрузки.
  • Деградация при сбоях: если часть узлов недоступна, операции с высоким W/R начинают чаще получать ошибки или таймауты.

То есть кворум помогает приблизиться к C, но делает систему более чувствительной к сетевым проблемам и «тормозящим» узлам.

Практическое правило для обсуждения

Полезно заранее разделить операции на два класса:

  1. Требуют кворума: деньги, остатки на складе, права доступа, уникальность (например, «только один активный тариф»).
  2. Можно ослабить: ленты, счётчики просмотров, рекомендации, черновики — то, где кратковременная неточность терпима.

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

Задержки, таймауты и «кажется, всё упало»

Доведите прототип до продакшена
Разверните приложение с хостингом и кастомным доменом, чтобы показать команде живой результат.

Когда обсуждают теорему CAP, часто путают «разделение сети» (P) с обычной медлительностью. На практике всё начинается с задержек: запрос не обязательно «не дошёл», он может просто идти слишком долго. И именно здесь рождается ощущение, что система упала.

Latency: почему «медленно» легко спутать с CAP

Задержка (latency) — это время, которое проходит от отправки запроса до получения ответа. В распределённых системах она складывается из сети, очередей, диска, CPU, пауз GC, перегруженных балансировщиков и т. д.

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

Таймаут превращает «медленно» в «недоступно»

Таймаут — это ваше явное решение, сколько ждать. Если ответ не пришёл вовремя, клиент считает операцию неуспешной и запускает повторы, переключение на реплику, фолбэк или возвращает ошибку пользователю.

Ключевой момент: даже если сервер всё ещё обрабатывает запрос и позже запишет данные, клиент уже ушёл. Так появляются «двойные списания», повторные заказы, расхождения между сервисами — и необходимость выбирать, что важнее: согласованность (C) или доступность (A) в условиях «как будто P».

Пример: рост задержки между регионами

Представьте, что вы держите данные в двух регионах. Обычно межрегиональная задержка 60–80 мс, но из‑за перегрузки канала стала 300–500 мс с редкими всплесками до секунд.

  • Если вы требуете строгую согласованность (C), запись ждёт подтверждения удалённого региона. При росте latency вы чаще упираетесь в таймауты — сервис выглядит недоступным (A падает).
  • Если вы выбираете доступность (A), вы принимаете запись локально и «догоняете» второй регион позже. Пользователь реже видит ошибки, но временно может наблюдать устаревшие данные.

Практика: измеряйте tail latency и настраивайте таймауты осмысленно

Средняя задержка почти всегда обманчива. Смотрите tail latency: p95/p99 (а иногда p99.9). Именно хвосты обычно «съедают» таймауты и создают эффект «всё легло».

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

Как выбрать компромисс: чек-лист требований к системе

CAP — не про «правильный выбор», а про осознанные требования. Чтобы не спорить абстрактно («нам нужна согласованность!»), зафиксируйте, что именно система обязана делать при сбоях сети и задержках.

1) Определите критичные инварианты

Сформулируйте 2–5 утверждений, которые нельзя нарушать никогда — даже ценой отказа части запросов. Примеры:

  • «Баланс счёта не может уйти в минус»
  • «Один и тот же заказ нельзя оплатить дважды»
  • «Права доступа применяются сразу, без “окна”»

Если инварианты жёсткие, вы заранее допускаете, что при разделении сети часть операций будет отклоняться или ставиться «на паузу» ради C.

2) Опишите деградацию: что показываем, если не можем гарантировать C

Заранее решите, как выглядит сервис в режиме AP (или «мягкой» согласованности):

  • показываем кэш/последнее известное состояние с пометкой «может быть неактуально»;
  • разрешаем только чтения, а записи ставим в очередь;
  • выключаем «опасные» функции (например, возвраты/списания), оставляя безопасные.

Главное — чтобы деградация была понятна пользователю и не ломала инварианты из пункта 1.

3) Классифицируйте операции

Разделите операции по риску и поведению при повторах:

  • чтения vs записи (и какие чтения допускают устаревание);
  • идемпотентные операции (повтор безопасен) и неидемпотентные (нужен ключ идемпотентности);
  • операции, которые можно выполнить асинхронно (с последующим подтверждением).

Это помогает точечно выбирать компромисс: например, «каталог товаров — AP, оформление заказа — CP».

4) Свяжите выбор с метриками и целями

Без измерений компромисс превращается в веру. Зафиксируйте:

  • долю ошибок и таймаутов (availability в терминах SLI);
  • допустимое устаревание данных (staleness), например p95 «возраста» чтения;
  • время восстановления после разделения сети (RTO) и допустимую потерю данных (RPO, если уместно);
  • SLA/SLO: какие цифры обещаем бизнесу и что считаем нарушением.

Когда эти пункты записаны, архитектурное решение перестаёт быть спором про термины и становится проверяемой спецификацией поведения системы при сбоях.

Типичные ошибки в трактовке CAP и как их избежать

Подключите команду и друзей
Пригласите коллег и получите бонусы за рефералов, пока вместе проектируете компромиссы.

CAP полезна ровно до тех пор, пока вы обсуждаете реальные сбои и поведение системы. Большинство проблем начинаются, когда теорему превращают в «ярлык для архитектуры» или в обещание маркетинга.

Антипаттерн: «сделаем и C, и A, и P — просто добавим серверы»

Добавление серверов помогает выдерживать нагрузку и переживать падение отдельных узлов, но не отменяет сетевые разделения (P). Если сеть между двумя частями кластера рвётся, системе всё равно нужно решить: останавливаем часть операций ради согласованности (C) или продолжаем отвечать, рискуя расхождением (A).

Как избежать: заранее определить, какие операции обязаны быть согласованными, а какие могут «догонять» позже. И зафиксировать, что при разделении сеть диктует выбор, а не количество машин.

Путаница CAP с ACID и транзакциями в одной БД

ACID — про поведение транзакций в пределах одной базы (и её журнала/блокировок). CAP — про ситуацию, когда данные распределены, и связь между частями системы может пропасть или задержаться.

Как избежать: в разговорах уточняйте контекст: «Мы обсуждаем транзакцию внутри одного узла?» или «Мы обсуждаем запись, которая должна разойтись по репликам через сеть?» Это разные задачи и разные компромиссы.

Ошибка формулировки: «у нас 99,99% аптайм — значит A»

Доступность в CAP — это не про месячный аптайм. Это про то, что каждый запрос получает ответ (пусть даже ошибку бизнес-уровня) во время проблем со связью между узлами.

Как избежать: измеряйте A сценарно: «что вернёт система при разделении?» и «какие запросы начнут таймаутиться?» — а не только общими процентами.

Как обсуждать CAP с командой: сценарии отказов

Самый практичный способ — список конкретных ситуаций и ожидаемых ответов:

  • «Клиент записал заказ в регионе A, а регион B временно изолирован — что увидит пользователь в B?»
  • «Платёж прошёл, но подтверждение не дошло — мы повторяем операцию или показываем статус “обрабатывается”?»

Фиксируйте решения как требования к продукту и проверяйте их тестами отказов и метриками (подробнее — в разделе /blog/observability-basics).

Практические шаги: проектирование, тестирование, наблюдаемость

CAP полезен только тогда, когда превращается в конкретные решения: что именно делает система, когда сеть «режется», узлы тормозят или часть реплик недоступна.

1) Зафиксируйте поведение при partition

Самый практичный артефакт — короткий документ (1–2 страницы), где описано: какой выбор делаем при разделении сети и как это проявляется для пользователя.

Укажите явно:

  • какие операции считаются критичными для согласованности (например, списание денег, финализация заказа) и что происходит при проблемах сети: блокируем, возвращаем ошибку, переводим в «ожидание»;
  • какие операции можно «отпустить» в доступность (например, лента, рекомендации): допускаем устаревшие данные, показываем кэш, деградируем функциональность;
  • политики таймаутов и ретраев: сколько ждём, сколько раз повторяем, когда говорим пользователю «повторите позже».

Если вы разрабатываете продукт итеративно, полезно хранить такие договорённости рядом с исходниками и деплоем. Например, в TakProsto.AI удобно вести архитектурные заметки в planning mode, быстро поднимать стенды, а при изменении кворумов/таймаутов — откатываться через snapshots и rollback, если эксперимент ухудшил p99 или доступность.

2) Набор тестов: симуляция разделения, задержек, отказов

В тестовом окружении регулярно моделируйте три класса событий:

  • разделение сети между зонами/нодами;
  • рост задержек (как следствие: таймауты, повторные запросы, дубли);
  • отказ узлов и «полу-отказы», когда сервис отвечает медленно или частично.

Проверяйте не только «сервис жив», но и инварианты: нет ли двойных списаний, нарушений лимитов, некорректных статусов заказов.

3) Наблюдаемость: что алертить

Настройте алерты на ранние признаки деградации:

  • рост ошибок (по типам: таймауты, 5xx, отмены);
  • увеличение лага репликации;
  • рост конфликтов/слияний (если допускаете eventual consistency).

Важно заранее описать, какие метрики означают «мы в режиме CP» или «мы в режиме AP», чтобы дежурный понимал ожидаемое поведение.

Куда двигаться дальше

Если хочется глубже в практику согласованности (кворумы, версии, разрешение конфликтов), переходите к материалу /blog/consistency-patterns.

А при выборе управляемой инфраструктуры и компромисса по SLA/цене полезно свериться с /pricing.

Похожие статьи