CAP: теорема, которую цитируют неправильно
Формулировка «выберите два из трёх» не соответствует доказанному. Что именно доказали Гилберт и Линч, что признал Брюер и почему PACELC точнее.
Коротко. CAP не предлагает выбрать два свойства из трёх. Она утверждает: во время разделения сети придётся выбирать между согласованностью и доступностью. Всё остальное время выбор идёт между согласованностью и задержкой, и его CAP вообще не описывает.
«Выберите любые два из трёх: согласованность, доступность, устойчивость к разделению». Формулировка стала общим местом, попадает в презентации и в собеседования, и она не соответствует тому, что доказано.
Разберём по частям, потому что каждое из трёх слов означает не то, что кажется.
Что доказано
Брюер высказал догадку в 1998–2000 годах. Формально её доказали Гилберт и Линч в 2002 году, для асинхронной сетевой модели. Доказательство несложное: если сеть может терять сообщения произвольно, то два узла, разделённые разрывом, не могут одновременно отвечать на запросы и оставаться согласованными — второй узел не знает о записи, принятой первым.
Ключевое в доказательстве — определения, а не сам вывод.
Consistency здесь означает линеаризуемость: система ведёт себя так, будто операции выполняются по одной в какой-то последовательности, согласованной с реальным временем. Это не буква C из ACID. ACID-C — это соблюдение инвариантов схемы внутри транзакции, совсем другое утверждение. Совпадение букв породило, по-моему, больше путаницы, чем сама теорема принесла пользы.
Availability означает: каждый запрос к любому неотказавшему узлу получает ответ. Не «система в основном отвечает», не «доступность 99,9%», а строгое требование ко всем живым узлам без ограничения по времени ответа.
Partition tolerance означает: система продолжает работать, когда сеть теряет произвольное количество сообщений.
Почему «два из трёх» — неверная рамка
Разделение сети — не свойство, которое выбирает архитектор. Это событие, которое происходит. Кабель, коммутатор, перегруженный канал, разъехавшиеся зоны доступности.
Отказаться от P значит объявить, что разрывов не бывает. Это не выбор дизайна, а предположение о мире, и оно ложно для любой системы, где узлы разделены сетью.
Отсюда: CA как категория пуста. Остаётся выбор между CP и AP, и делается он не заранее, а в момент разрыва.
Сам Брюер написал об этом в 2012 году в статье «CAP Twelve Years Later: How the „Rules” Have Changed» — и назвал формулировку «два из трёх» вводящей в заблуждение. Его собственный вывод: разделения редки, поэтому большую часть времени система может давать и C, и A, а решение требуется только на время разрыва и на время восстановления после него.
Чего CAP не описывает вовсе
Самое практичное следствие лежит за пределами теоремы.
Абади в 2012 году предложил расширение PACELC: если разделение (P), выбирай между доступностью (A) и согласованностью (C); иначе (E) — между задержкой (L) и согласованностью (C).
Вторая половина важнее первой, потому что описывает обычный день, а не аварию. Синхронная репликация в другой центр обработки данных не нарушает согласованность и не требует разрывов — она просто добавляет к каждой записи время оборота между площадками. Это плата, которую вы вносите постоянно, и CAP о ней молчит.
Большинство систем, которые называют «выбравшими AP», на самом деле выбрали L: они не хотят ждать подтверждения от удалённой реплики в штатном режиме. Разрывы тут ни при чём.
«Согласованность» — не одно свойство
Между линеаризуемостью и «как-нибудь потом» лежит целая шкала, и она описана задолго до нынешних споров.
Из работ над системой Bayou (Терри и соавторы, середина девяностых) пришли сессионные гарантии — свойства, определённые не для системы целиком, а для одного клиента:
- чтение своих записей — клиент видит собственные изменения;
- монотонное чтение — клиент не видит состояние старше уже увиденного;
- монотонная запись — записи одного клиента применяются в порядке их отправки;
- запись после чтения — запись, сделанная на основе прочитанного, применяется после того, что было прочитано.
Ценность этого списка в том, что почти все требования, которые формулируют словами «нужна сильная согласованность», на деле удовлетворяются одной-двумя строчками отсюда. Пользователь должен видеть свой комментарий — это чтение своих записей, и оно достигается привязкой сессии к реплике или чтением из лидера только для своих записей. Линеаризуемость всей системы для этого не нужна и стоит несопоставимо дороже.
Что означает «eventual consistency»
Термин часто используют как синоним «слабой согласованности», и это неточно.
Строго говоря, итоговая согласованность — утверждение о живости, а не о безопасности: если записи прекратятся, все реплики в конце концов сойдутся. Оно не говорит ни когда это произойдёт, ни что клиент увидит до этого момента.
Утверждение, из которого нельзя вывести ни одного ограничения на наблюдаемое поведение, плохо годится как проектное требование. Поэтому в спецификации системы полезнее писать не «итоговая согласованность», а конкретные сессионные гарантии и границу расхождения реплик, если она известна.
Что с этим делать при проектировании
Три вопроса, которые заменяют цитирование теоремы.
Что делает система во время разрыва? Не «какие у нас буквы», а конкретно: узел, потерявший связь с большинством, отвечает на чтение устаревшими данными или возвращает ошибку? Принимает запись или отказывает? Ответ должен быть записан, а не выведен в момент аварии.
Что происходит после восстановления? Это часть, о которой забывают чаще всего. Если обе стороны принимали записи, кто-то должен их слить. Последняя запись побеждает — самый простой и самый опасный вариант: он молча теряет данные, и потерю не видно. Векторные часы, CRDT, ручное разрешение — все дороже и все честнее.
Какой инвариант вы защищаете? Не «нужна ли согласованность», а какое конкретно утверждение должно оставаться истинным. Остаток не уходит в минус — одно. Пользователь видит свой собственный предыдущий комментарий — совсем другое, и оно достигается гораздо дешевле, чтением-своих-записей вместо линеаризуемости.
Третий вопрос обычно снимает половину спора. Значительная часть требований, которые формулируют как «нужна сильная согласованность», на деле удовлетворяется одной из сессионных гарантий.
Где я могу быть неправ. Если система живёт в одном центре обработки данных на одной стойке, разделения там — событие того же порядка, что и отказ стойки целиком. Тогда рассуждение сводится к вопросу о репликации и восстановлении, и CAP действительно можно не поминать.