Нужна ли вашей распределённой СУБД византийская отказоустойчивость?


Когда инженер проектирует распределённую систему, возникает естественное желание обеспечить сложную и хрупкую архитектуру максимально сильными гарантиями отказоустойчивости и согласованности данных. На первый взгляд кажется, что чем строже модель отказов, тем надёжнее система. Но отказоустойчивость – это не ручка громкости, которую можно выкрутить на максимум. Разные модели отказов решают разные задачи: в одном случае система должна пережить падение узла, потерю связи или зависание реплики, в другом – договориться о едином порядке событий, даже если часть участников ведёт себя так, что их сообщениям уже нельзя доверять.
Для распределённой СУБД ответ на вопрос "узел жив?" почти никогда не является исчерпывающим. Важнее понимать, может ли этот узел прямо сейчас выполнять свою роль.
Представим следующую ситуацию. Мы запускаем Valkey, ограничиваем ему память и проверяем:
PING
Получаем:
PONG
Сервер отвечает, сетевое соединение есть, команда выполнена. Теперь пробуем выполнить запись:
SET new_key value
И получаем ошибку:
OOM command not allowed when used memory > 'maxmemory'
Мы наблюдаем ситуацию, когда узел уже не способен выполнять одну из своих ключевых ролей – принимать записи.
Представьте, что это ведущий узел в кластере. Health check видит PONG, клиентский драйвер продолжает отправлять на него запросы, мониторинг показывает зелёный статус, а реальная запись не проходит. Это полноценная эксплуатационная проблема.
Дальше мы разберёмся, какие именно отказы вообще предполагает распределённая СУБД.
Три типа отказов узла
Узел упал. Процесс завершился, машина выключилась, контейнер умер. Такой отказ неприятен, но понятен: компонента больше нет.
Узел деградировал. Он формально жив, но работает неправильно или недостаточно хорошо: заканчивается память, переполняется диск, растёт задержка, отстаёт репликация, зависает ввод-вывод. Такой узел может отвечать на health check, но уже не соответствовать ожиданиям приложения. В индустрии для этого есть термин gray failure, или «серый отказ»: система не мертва и не здорова, а находится в промежуточном состоянии.
Узел нарушает протокол. Он не просто деградировал, а начинает вести себя произвольно относительно остальных участников системы. Например, реплике A сообщает, что операция записана, реплике B — что такой операции не было, а клиенту возвращает успешный ответ. У разных участников появляется разная картина того, что произошло.
Именно третий случай относится к так называемой византийской модели отказов, или Byzantine Fault Tolerance.
Плохой health check видит только факт ответа процесса, но не его способность выполнять роль в системе. Чтобы быть уверенным в том, что узел способен выполнять свою прикладную роль, одних проверок доступности процесса недостаточно. Нужна модель отказов.
Модель отказов – это набор предположений о том, какие ошибки может допускать неисправный узел и какие гарантии система должна сохранять при таком поведении.
Дальше рассмотрим две важные модели: CFT и BFT.
CFT
CFT – это модель отказоустойчивости к аварийным отказам узлов, от английского Crash Fault Tolerance. Предполагается, что в этой модели узел может упасть, перезапуститься, потерять связь, временно зависнуть, отстать или вернуться после паузы. Корректно спроектированная система при этом сохраняет свои гарантии, пока отказ укладывается в выбранную модель и остаётся достаточное число участников для принятия решений.
CFT опирается на одно ключевое допущение: если узел участвует во взаимодействии с другими узлами, он следует правилам этого взаимодействия. Под правилами здесь понимается протокол работы кластера: однозначно определено, кто принимает записи, кто подтверждает журнал, кто участвует в выборе лидера, какие сообщения считаются допустимыми.
Узел может не ответить, ответить поздно или оказаться недоступным для части кластера. Но, по предположению модели, он не рассылает разным участникам противоречивую информацию.
Перегрузка, отставание репликации, зависший ввод-вывод или неправильная диагностика не являются византийскими отказами. В конкретном протоколе они могут проявляться как задержка, потеря доступности, исключение узла из кворума или потеря пригодности к роли. Если узел при этом не рассылает разным участникам противоречивые сообщения, BFT-модель не требуется.
Если же узел начинает отправлять противоречивые сообщения, мы выходим за пределы CFT-модели. Алгоритм, рассчитанный только на падения, уже не обязан сохранять корректность. Для такой ситуации нужна более жёсткая, византийская модель отказов.
BFT
BFT – это византийская отказоустойчивость (от английского Byzantine Fault Tolerance). Это модель, в которой система должна сохранять корректность даже тогда, когда часть узлов ведёт себя произвольно.
Название связано с классической задачей византийских генералов, сформулированной Лесли Лэмпортом, Робертом Шостаком и Маршаллом Пизом в статье The Byzantine Generals Problem 1982 года. В этой задаче несколько командующих должны договориться об общем действии, но часть участников может быть предателями, поэтому одному генералу будет сказано «атакуем», другому – «отступаем», а до третьего информация вообще может не дойти.
В современных распределённых системах термин «византийский» используют шире. Узел может отправлять разным сторонам противоречивую информацию по разным причинам: ошибка в программе, повреждение данных, неверная настройка, сбой оборудования, компрометация или участие независимого узла вне вашего административного контроля.
Византийский узел может попытаться создать несколько версий истории. Например:
реплике A сообщить: «операция X подтверждена»;
реплике B сообщить: «операции X не было»;
клиенту ответить: «всё записано»;
в журнал записать версию событий, которая не совпадает ни с одной из этих картин.
BFT-алгоритмы пытаются сохранить корректность даже в таких условиях. Именно в этом их основное преимущество: они помогают системе согласовать единый порядок событий при более жёсткой модели отказов — когда часть участников не просто молчит или исчезает, а продолжает участвовать и может рассылать противоречивые сообщения.
Разумеется, это работает только при выполнении предположений самого BFT-протокола: число византийских узлов не превышает допустимый предел, сообщения корректно аутентифицируются, а модель сети соответствует тому, на что рассчитан протокол.
Как CFT работает в СУБД
На вопрос «что делать, если компонент сломался?» отвечают большинство механизмов высокой доступности в промышленных СУБД. CFT проявляется здесь не как одна конкретная технология, а как набор инженерных приёмов: репликация, выбор лидера, кворумы, номера эпох и защита от split-brain.
Репликация. Данные или метаданные хранятся на нескольких узлах. Эти узлы могут быть репликами друг друга целиком или хранить копии отдельных частей состояния. Отказ одного узла не должен приводить к отказу всей системы. Репликация может быть синхронной, асинхронной, кворумной, логической или физической. Детали различаются, но цель одна: сохранить работу системы при отказе части инфраструктуры.
Лидер и реплики. Во многих системах есть ведущий узел — лидер. Он принимает записи, а реплики получают изменения. Если лидер падает, система должна выбрать нового лидера — автоматически или через отдельный механизм переключения.
Здесь возникает риск split-brain: две части кластера могут одновременно решить, что именно у них находится настоящий лидер. Если обе стороны начнут принимать записи, история данных может разойтись. Поэтому недостаточно просто выбрать новый узел. Нужно убедиться, что старый лидер больше не имеет права принимать изменения.
Кворум. Кворум – это минимальное число участников, которое должно подтвердить решение, чтобы система считала его принятым. Он нужен, когда решение нельзя доверить одному узлу: при выборе лидера или подтверждении записи в журнале.
В типичной конфигурации с большинством для группы из трёх узлов кворумом может быть любая пара узлов. Если один узел упал, два оставшихся всё ещё способны принимать решения. Важно, что любые две такие пары обязательно пересекаются хотя бы в одном узле: например, пары A+B и B+C пересекаются в узле B. Благодаря этому новое решение не принимается в полной изоляции от уже подтверждённого старого решения.
Номера эпох и отстранение старого лидера. Чтобы отличать старого лидера от нового, распределённые системы используют номера эпох, термы или поколения. У каждого периода лидерства есть номер. Новый лидер получает более свежий номер, а сообщения из старой эпохи можно отвергнуть.
Номера эпох помогают узлам понять, какое лидерство актуально, но они не гарантируют, что старый лидер физически не сможет выполнить запись. Поэтому требуется дополнительный механизм отстранения. Для этого используются блокировки, внешние координаторы, временные права лидерства и другие механизмы управления доступом к записи.
Это тоже часть практической CFT-инженерии. В CFT-модели старый лидер не пытается сознательно обмануть систему, но он может жить в устаревшей эпохе и продолжать считать себя главным. Задача системы — не дать такому узлу принимать новые изменения.
CFT защищает систему при предположении, что узлы не пытаются сознательно обмануть друг друга. В BFT-модели это предположение исчезает.
Как BFT проявляется на практике
BFT не появляется в системе сам по себе. Чтобы византийская отказоустойчивость работала, система должна быть изначально спроектирована под более жёсткую модель отказов: с достаточным числом участников, более широкими подтверждениями, аутентификацией сообщений и проверяемой историей событий.
Большее число участников. В CFT-модели для переживания одного падения часто достаточно трёх узлов: один может исчезнуть, два оставшихся сохранят большинство. В византийской модели плохой узел не исчезает. Он продолжает участвовать и может активно портить картину мира. Поэтому система изначально должна быть рассчитана на большее число участников. Для классических BFT-протоколов репликации конечного автомата обычно требуется не менее 3f + 1 участников, чтобы пережить f византийских узлов. Например, чтобы пережить один византийский узел, нужно минимум четыре узла. Чтобы пережить два — минимум семь. Это не универсальная формула для всех возможных вариантов BFT, но хороший ориентир.
Более широкое подтверждение. В CFT-системе от потери решения при падении части узлов защищает большинство. В BFT-системе подтверждение должно быть достаточно широким, чтобы отдельные византийские участники не могли навязать ложную историю. Поэтому узлы не просто собирают голоса, а сравнивают сообщения между несколькими участниками и ждут подтверждений, которые дают уверенность: корректные участники видят одну и ту же историю.
Количество сообщений. В CFT-модели протокол обычно проверяет, что решение подтверждено достаточным числом участников. В BFT-модели этого мало: нужно защититься от ситуации, когда часть узлов рассылает разные версии событий разным сторонам. Поэтому корректные участники должны обмениваться подтверждениями шире и сравнивать полученную картину между собой. В классических BFT-протоколах это даёт больше сетевых сообщений; для PBFT на отдельных фазах характерна квадратичная сложность обмена сообщениями, порядка O(n²).
Аутентификация сообщений. Если узел может вести себя произвольно, важно понимать, кто именно отправил сообщение и можно ли доказать его происхождение. Поэтому BFT-системы используют цифровые подписи, коды аутентификации сообщений и другие криптографические средства. В CFT-системах TLS и аутентификация тоже часто используются, но в BFT они становятся частью модели корректности: без проверки происхождения сообщений невозможно отличить честное сообщение от поддельного или противоречивого.
Проверяемая история. В BFT-мире важна не только текущая доступность, но и возможность доказать, какая история была согласована. Поэтому рядом с BFT-идеями часто появляются проверяемые журналы, хеш-цепочки, деревья Меркла, журнал аудита и другие механизмы, которые помогают обнаружить расхождение истории или попытку подмены данных.
Все эти механизмы обслуживают практическую задачу консенсуса в более жёсткой модели отказов. Они нужны, чтобы корректные участники могли договориться об одной истории событий, принять следующее решение и безопасно двигаться дальше, даже если часть узлов продолжает участвовать и нарушает доверие к общей истории.
Важно не путать отдельные механизмы с полноценной BFT-СУБД. Подписи, проверяемые журналы или деревья Меркла сами по себе ещё не делают базу данных византийски отказоустойчивой. Они повышают целостность данных и позволяют проверить историю изменений, но не заменяют весь путь обработки операции внутри СУБД.
От модели отказов к реальной системе
Чтобы не смешивать понятия, полезно выстроить следующую цепочку.
Модель отказов отвечает на вопрос: что может пойти не так? В CFT-модели узел может упасть, зависнуть, потерять связь или вернуться после паузы. В BFT-модели узел может продолжать участвовать и отправлять разным участникам противоречивые сообщения.
Протокол согласования отвечает на вопрос: как участникам договориться о решении при такой модели отказов? Например, кто сейчас лидер, какая операция следующая, какой журнал считается принятым.
СУБД – это уже вся система целиком: протокол согласования, хранение данных, исполнение транзакций, индексы, ограничения, чтения, снапшоты, восстановление, резервные копии и изменение состава кластера.
Это справедливо как для CFT, так и для BFT. Одного алгоритма выбора лидера недостаточно, если репликация, восстановление и переключение при отказе реализованы неправильно.
BFT-протокол согласования помогает узлам договориться о порядке событий в условиях, когда часть участников может продолжать работу и отправлять противоречивые сообщения. Он отвечает на вопрос: какая операция была первой, какая второй, какая третьей. Но база данных должна не только согласовать порядок операций. Она должна ещё одинаково их выполнить и не дать клиенту получить состояние, сформированное нечестным участником в обход согласованной истории.
Допустим, BFT-протокол согласовал порядок команд:
T1, затем T2, затем T3
Это ещё не гарантия, что все корректные узлы получат одинаковое состояние базы. Для этого нужно, чтобы исполнение этих команд было детерминированным и проверяемым.
Например, если транзакция использует текущее время, случайное число, внешний вызов, недетерминированный порядок обработки конкурентных операций или побочные эффекты хранимой процедуры, разные узлы могут получить разные результаты даже при одном и том же порядке команд.
То же касается чтений, снапшотов и восстановления. Клиент не должен получать данные напрямую от узла, который может быть византийским, если этот ответ не проверен или не подтверждён правилами протокола. Снапшот и восстановление тоже должны соответствовать согласованной истории, иначе нечестный узел сможет показать состояние, которое не было принято системой.
Отдельная проблема – изменение состава кластера. Если система позволяет добавлять и удалять участников, то сама процедура изменения состава кластера должна быть защищена от византийского поведения. Иначе недоверенный участник может попытаться повлиять на состав группы, кворумы или правила принятия решений.
Именно поэтому можно взять BFT-протокол для отдельного слоя системы, но не получить автоматически BFT-СУБД. Протокол может византийски отказоустойчиво согласовать порядок команд, но, если движок базы выполняет их недетерминированно, чтения обходят согласованное состояние, восстановление не проверяет историю, а изменение состава кластера не защищено, вся система не получает полноценных BFT-гарантий.
Выбор BFT – это не замена одного алгоритма выбора лидера на другой. Это изменение всего контракта исполнения: как операции упорядочиваются, как исполняются, как проверяется результат, как обслуживаются чтения, как восстанавливается состояние и что считается доказательством корректности.
Почему BFT редко нужен корпоративному кластеру СУБД
В большинстве корпоративных сценариев все узлы кластера принадлежат одной организации и управляются по единым правилам. При этом один административный домен не обязательно означает один дата-центр или одну физическую площадку. Кластер может быть разнесён по нескольким дата-центрам или облачным провайдерам, но, если он управляется одной командой, одной политикой доступа, одним процессом обновлений и единым контуром эксплуатации, для нашей темы это всё ещё один доверенный домен.
В такой системе основные угрозы обычно выглядят так:
закончилась память;
заполнился диск;
реплика отстала;
ведущий узел завис;
случился сетевой раздел;
клиенты устроили шторм повторных запросов;
новая версия принесла ошибку;
оператор ошибся с конфигурацией;
старый лидер вернулся после паузы.
Это серьёзные проблемы, которые могут приводить к потере доступности, потере данных, нарушению задержек, повреждению состояния и долгим инцидентам. Но это не те проблемы, для которых нужен BFT.
BFT защищает от недоверенного поведения участников протокола, а не от любой эксплуатационной деградации. Он не починит плохой health check, не заменит мониторинг, не исправит неверную настройку репликации, не спасёт от заполненного диска, не решит проблему плохого плана запроса и не отменит необходимость тестировать восстановление.
Какая инженерия нужна внутри CFT-модели
Если в системе действительно есть недоверенный участник, который может рассылать противоречивые сообщения, CFT-приёмы не заменят BFT. Они не решают проблему недоверия.
На практике работа с CFT — это не только алгоритм консенсуса или механизм репликации. Это ещё эксплуатационные и диагностические слои, которые помогают удерживать систему в рамках её модели отказов: не перепутать деградировавший узел со здоровым, старого лидера с актуальным, отстающую реплику с пригодной для чтения, а успешный health check — с корректностью всей системы.
Для этого используются стандартные инженерные приёмы.
Проверка роли узла. Ведущий узел должен иметь право принимать записи. Реплика должна быть достаточно свежей для тех чтений, которые на неё отправляют. Узел, который отвечает на health check, но отстал на минуты, может быть непригоден для части запросов.
Контроль отставания репликации. Реплика может быть жива, но устарела. Здесь важна не абстрактная живость, а конкретная метрика: насколько далеко реплика отстала от источника изменений и допустимо ли это для данного типа запросов.
Защита от split-brain. Система должна не только выбрать нового лидера, но и не дать старому лидеру продолжать принимать записи. Для этого используются временные права лидерства, внешние координаторы, кворумы, номера эпох, блокировки и механизмы отстранения старого лидера. Их задача — подтвердить, что право принимать записи сейчас есть только у одного узла.
Контроль целостности. Контрольные суммы, цифровые подписи, валидация резервных копий, проверка журналов, сверка реплик, журнал аудита, хеш-цепочки, деревья Меркла и сквозные проверки целостности помогают обнаружить повреждение данных или расхождение состояния.
Эти механизмы повышают проверяемость: позволяют быстрее заметить, что история данных разошлась, запись повреждена, резервная копия не восстанавливается или реплика содержит не то состояние, которое от неё ожидали. Но они не поднимают систему до BFT-модели и не заменяют протокол согласования между недоверенными участниками.
Мониторинг и health check. Проверка «процесс отвечает» полезна, но недостаточна. Проверка должна соответствовать роли узла и быть безопасной по побочным эффектам. Для лидера это может быть проверка готовности принимать записи, для реплики — проверка свежести данных, для участника консенсуса — способность безопасно участвовать в кворуме.
Где BFT действительно нужен
BFT становится естественным там, где часть участников нельзя считать полностью доверенными.
Например:
несколько банков ведут общий журнал операций;
регулятор и участники рынка поддерживают общий реестр;
несколько компаний участвуют в системе клиринга;
организации хотят общий журнал аудита без единого владельца;
участники сети не доверяют друг другу, но должны договориться о порядке событий.
Здесь недоверенный участник не обязательно означает злонамеренный прямо сейчас. Достаточно того, что участник находится вне вашего административного контроля. Поэтому системе нужен механизм, который позволяет согласовать общую историю – даже без единого владельца всех узлов.
На практике византийская отказоустойчивость проявляется не на уровне всей базы данных, а на уровне отдельного слоя согласования.
Hyperledger Fabric 3.0 добавил BFT-службу упорядочивания на базе SmartBFT. Важно, что это BFT-компонент слоя ordering service, а не превращение всей системы Fabric в универсальную BFT-СУБД.
CometBFT, форк и наследник Tendermint Core, используется как BFT-движок репликации конечного автомата для приложений и блокчейн-сетей, которым нужно договориться о порядке событий между независимыми узлами.
Algorand использует собственный вариант византийского соглашения для формирования блоков без единого центрального владельца журнала.
Эти примеры показывают, что BFT оказывается естественным не внутри типичного кластера СУБД одной компании, а на границе доверия между независимыми участниками.
Блокчейны – самый известный пример такого мира. Но BFT – это не только про криптовалюты. Он может быть нужен в закрытых сетях участников, консорциумных системах, межорганизационных реестрах и системах, где нет единого доверенного владельца истории.
CFT и BFT: схема сравнения
Вопрос | CFT | BFT |
Как ведёт себя плохой узел? | Молчит, тормозит, исчезает, возвращается после паузы | Шлёт разные сообщения разным участникам, навязывает ложную историю |
Что допускает модель? | Узел может пропасть, отстать или жить в старой эпохе | Узел может продолжать участвовать и рассылать противоречивые сообщения |
Главная угроза | Потеря доступности, split-brain, старый лидер, потеря кворума | Недоверенный участник, противоречивые версии событий |
Типичные алгоритмы | Raft, Paxos, Zab, Viewstamped Replication | PBFT, HotStuff-подобные протоколы |
Минимальный размер группы | 2f + 1, чтобы пережить f падений | 3f + 1, чтобы пережить f византийских узлов |
Где чаще встречается | Корпоративные СУБД и инфраструктура данных | Блокчейны, консорциумы, межорганизационные системы |
Главный вопрос | Что делать, если компонент сломался? | Что делать, если компонент стал недоверенным? |
Таким образом, BFT — это не просто "более надёжный CFT". Это другая модель угроз.
BFT – это не максимальный уровень надёжности
Инженеры любят сильные гарантии. Строгая согласованность кажется лучше слабой. Синхронная репликация кажется надёжнее асинхронной. Кворум кажется лучше одиночной записи. BFT на этом фоне может звучать как ещё один пункт в линейке: «давайте возьмём самое сильное».
Но BFT — не более высокий уровень качества, а другая модель угроз и другая цена: больше участников, более широкие кворумы, больше сообщений, обязательная аутентификация и более жёсткий контракт исполнения. Такая цена оправдана только тогда, когда система действительно защищается от недоверенных участников протокола.
Если же проблема в том, что реплика отстаёт, старый лидер не отстранён, мониторинг не видит деградацию, а health check проверяет только PING, BFT не является решением. Это вопросы ресурсов, ролей, задержек, эксплуатации и диагностики, а не недоверия.
Ответ здесь — инженерная дисциплина внутри выбранной CFT-модели.
Заключение
Классические корпоративные кластеры СУБД чаще всего живут в мире поломок, а не в мире предательства.
Для этого мира хорошо подходит CFT-модель. Она не обещает защиты от злонамеренных участников, но даёт практическую основу для отказоустойчивости внутри одного административного домена: консенсус там, где он нужен, корректные кворумы, защита от split-brain, ролевой health check, мониторинг, проверка репликации, контроль целостности, резервное копирование и тестирование восстановления.
Полноценный BFT нужен тогда, когда меняется модель доверия к участникам системы, а не когда просто хочется «ещё больше надёжности».
Именно поэтому типичному корпоративному кластеру СУБД в одном доверенном домене BFT обычно не нужен. Но ему всегда нужна хорошая инженерия вокруг CFT и ясное понимание тех гарантий, которые система действительно даёт.
KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.