Pull to refresh

Разъяснение по CAP-теореме

NoSQLDistributed systems
Статья "Недопонимание CAP-теоремы" и комментарии к ней свидетельствуют, что непонимание действительно есть. И связано оно не только с неправильным толкованием термина «partitioning», но и с ментальными ошибками на других уровнях. Попробую внести ясность.

Условности

Для начала, договоримся об уровне абстракций.
В рамках теоремы бессмысленно заявлять, что в «реальном мире» не может быть гарантированной связи между узлами распределенной системы. С тем же успехом можно сказать, что такое свойство как «availability» недостижимо в принципе, поскольку любая система перестанет быть доступной при отказе достаточного числа узлов (например, при отказе всех ее узлов).

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

Технически, мы можем создать систему, в которой вероятность потери связи между узлами будет еще меньше, чем вероятность отказа критического числа узлов (которую мы считаем пренебрежимо малой). Благодаря этому мы можем говорить, что распределенная система с гарантированной связью между узлами (невозможностью partitioning'а) — возможна.

Как конкретно этого добиться — неважно. Возможно, понадобится миллионократное дублирование систем связи или изобретение сверхнадежного гравитонного передатчика, который на тучу порядков устойчивей к любым воздействиям, чем наши грубые кремниевые поделки. Факт, что это можно сделать, если кому-то правда приспичит. Делают же компьютеры, в которых 4 процессора считают одно и то же только чтобы перепроверить друг друга. С той же тщательностью можно подойти к гарантиям обеспечения связи.

Смерть милее

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

При разрыве связи все печальнее. Узел не может позволить себе умереть, потому что возможно только он и обеспечивает работоспособность (без связи с другими узлами невозможно понять, есть ли еще кто-то живой). Не может он и синхронизировать свою работу с остальными — связи-то нет. Остается действовать вслепую, на свой страх и риск.

Рассмотрим происходящее на конкретном примере

Допустим, в нашей системе ровно 2 совершенно одинаковых узла — A и B. Каждый из них хранит копию данных второго и может независимо обрабатывать запросы извне. Каждый, обрабатывая запрос, уведомляет второго об изменениях, чтобы данные оставались согласованы.

Вариант 1: узел A умирает.
Система продолжает работать как ни в чем не бывало — B продолжает обрабатывать запросы. Когда A приведут в чувство, он первым делом синхронизируется с B и они вдвоем продолжат работать дальше. Ни доступность, ни согласованность не страдают.

Вариант 2: A и B живы, но связь между ними прервана.
При этом каждый из них продолжает принимать запросы извне, но не может уведомить второго об изменениях. Для каждого узла все выглядит так, будто второй узел умер и он действует в одиночку. Эту ситуацию часто называют «split-brain» — мозг разделился на два полушария, каждое из которых считает себя единственным хозяином ситуации. Система заболела шизофренией.

Если в этот момент на A был обработан запрос на удаление некой записи R, а на B был обработан запрос на модификацию той же самой записи, то данные стали несогласованы. Когда связь между A и B восстановится, при синхронизации всплывет конфликт — удалить R или оставить модифицированную версию? Тут можно выкручиваться разными стратегиями разрешения конфликтов, но consistency мы уже потеряли.

Альтернативный способ решения проблемы — A и B, видя, что потеряли связь друг с другом, перестают обрабатывать запросы. В этом случае согласованность не нарушится, но будет потеряна availability.

Ближе к реальности

Конечно, системы ровно из двух узлов встречаются нечасто. Но то же самое верно, если считать что A и B не два отдельных узла, а два набора узлов. При этом считаем, что общение между двумя любыми узлами в пределах A и в пределах B осталось возможным, но узлы из A не могут взаимодействовать с узлами из B. При этом оба набора продолжают получать и обрабатывать запросы извне.

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

Возвращаясь к теореме

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

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

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

Ну а если в своем проекте вы считаете вероятность сплита достаточно высокой, можно переформулировать теорему таким образом: при возниконовении сплита остается только выбирать — A или C.

Лирическое отступление

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

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

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

Другое дело, что оставшись с половиной мощности, система скорее всего целиком ляжет под нагрузкой. Но можно применить еще одну хитрость — узлы, оставшиеся в изоляции, могут продолжить работать на чтение, не создавая конфликтов. Для систем с интенсивным чтением вполне оправданный ход. Правда, отдаваемые данные не всегда будут актуальны, но часто это лучше чем ничего. Однако при разделении на несколько частей, уже ни одна из них не сможет обрабатывать запросы на запись. Хотя можно придумать что-нибудь и похитрее…

Как ни изворачивайся, в целом CAP-теорему не обойти. Но в большинстве проектов есть огромные просторы для поисков способа выкрутиться, сводя вероятность проблемных сценариев к минимуму.

PS javaspecialist, спасибо за повод для написания этой статьи.
Tags:cap-теоремаcap theoremnosqlsplit-brain
Hubs: NoSQL Distributed systems
Total votes 33: ↑29 and ↓4+25
Views14K

Popular right now

Top of the last 24 hours