Split-brain: как кластер базы данных превращается в два кластера и почему это конец
У вас кластер из нескольких узлов базы данных, и где-то между ними на несколько секунд или минут пропадает связь — упал канал между дата-центрами, перегрузился коммутатор, зависла виртуальная сеть у облачного провайдера. Связь восстанавливается, всё как будто снова работает, а через какое-то время вы обнаруживаете две несовместимые версии одних и тех же данных или упрямый отказ кластера принимать записи. Это и есть split-brain — не баг конкретной СУБД, а системная ловушка, в которую попадает любой кластер с неправильно настроенным выбором лидера. Разбираемся, как она устроена и почему единственная надёжная защита — это кворум.
Содержание
- Что такое сетевой раздел и почему это не то же самое, что падение узла
- Почему обе половины кластера решают, что они главные
- Что происходит с данными, когда обе половины принимают записи
- Кворум: как большинство голосов не даёт кластеру расколоться
- Почему кластер из двух узлов особенно уязвим к split-brain
- Как это устроено в реальных системах
- Как понять, что кластер уже расколот, и что делать дальше
Что такое сетевой раздел и почему это не то же самое, что падение узла
Кластер базы данных держится на постоянном обмене сигналами между узлами: кто жив, кто лидер, кто реплицирует данные. Когда узел падает целиком — выключился сервер, закончилась память, ядро упало в панику — остальные узлы это видят: соединение рвётся, узел не отвечает, и решение очевидное — исключить его и продолжить без него.
Сетевой раздел (network partition) — принципиально другая ситуация. Ни один узел не падает, все процессы живы и диски целы. Пропадает только связь между группами узлов — например, кластер размещён в двух дата-центрах, и магистральный канал между ними лёг. С точки зрения узлов в дата-центре A узлы в дата-центре B выглядят недоступными, и одновременно с точки зрения узлов в дата-центре B узлы в дата-центре A выглядят точно так же недоступными.
Разница критична: при падении узла кластер теряет часть себя, но сохраняет единое представление о реальности — оставшиеся узлы согласны между собой, кто жив. При сетевом разделе кластер раскалывается на две группы, и каждая видит только себя — а с той стороны раздела всё выглядит зеркально. Ни одна группа не может отличить «эти узлы упали» от «эти узлы просто недоступны, но живы».
Причины разделов в реальной инфраструктуре обычно скучные: перегруженный аплинк между дата-центрами, флап BGP-сессии у провайдера, VLAN, переставший прописываться после переключения коммутатора, случайно закрытый порт кластерного протокола в security group между подсетями. Split-brain не требует экзотики — обычной кратковременной сетевой аномалии достаточно, если механизм выбора лидера настроен неправильно.
Почему обе половины кластера решают, что они главные
В кластере с одним лидером (primary) всё держится на договорённости: активна ровно одна копия, которая принимает записи, остальные — реплики, которые эти записи получают и применяют. Проблема начинается в момент, когда реплика перестаёт видеть лидера.
Реплика не знает, *почему* лидер пропал. С её колокольни ситуация «лидер упал» и ситуация «лидер жив, но пропала сеть между нами» неразличимы — приходит одинаковый результат: таймаут, нет ответа на heartbeat. Если механизм выбора лидера настроен наивно — «не вижу лидера дольше N секунд, беру роль на себя» — при сетевом разделе узлы по одну сторону решат, что старый лидер умер, и изберут нового из своей группы. А старый лидер по другую сторону раздела как ни в чём не бывало продолжит быть лидером — он ведь физически жив, просто не видит остальных, и с его точки зрения это они куда-то пропали.
Результат — два узла с ролью лидера одновременно, каждый уверен, что он единственный, и оба готовы принимать записи. Приложения, подключённые к разным сторонам раздела (в распределённой системе клиенты обычно идут к ближайшему или любому доступному узлу через балансировщик), начинают писать в обе копии независимо. Это и есть split-brain в буквальном смысле — единый мозг кластера раскололся на два, и каждая половина действует автономно, не зная о существовании другой.
Важный нюанс: split-brain — это не отказ конкретного узла и не баг в коде СУБД. Это следствие того, что распределённая система пытается принять решение («кто главный») в условиях, когда часть информации недоступна, и алгоритм выбора лидера позволяет двум группам прийти к разным ответам одновременно.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверЧто происходит с данными, когда обе половины принимают записи
Пока раздел длится, обе половины кластера живут собственной жизнью. Каждая принимает записи, применяет их к своей копии данных и генерирует новые записи в своём журнале — write-ahead log в PostgreSQL, binlog в MySQL, oplog в MongoDB. Внутренняя репликация от «лидера» к «репликам» внутри каждой половины при этом работает штатно, просто только внутри неё, потому что связи с другой половиной нет.
С точки зрения каждой половины ничего не сломано: клиенты подключаются, запросы выполняются, ошибок никто не видит. Настоящая проблема раскрывается не во время раздела, а после его исчезновения — в момент, когда связь между узлами восстанавливается.
К этому моменту у вас на руках две ветки одного кластера, независимо разошедшиеся в данных. Если это была одна и та же строка (баланс счёта, статус заказа), обе половины могли изменить её по-разному, и в журналах обеих версий будет разная последовательность операций с одной и той же точки расхождения. Это тот же класс проблемы, что и обычное отставание реплики — только там реплика позже применяет то же самое, что лидер, а здесь два независимых лидера применили *разное*.
Слить две разошедшиеся ветки автоматически и без потерь в общем случае невозможно. СУБД не умеет мержить конфликтующие транзакции так, как git мержит текстовые файлы — если оба узла независимо выполнили UPDATE над одной строкой или INSERT с конфликтующим уникальным ключом, единственный технически честный выход — выбрать одну ветку источником истины, а вторую откатить или архивировать для ручного разбора. Это означает потерю транзакций, принятых во второй, отброшенной половине за время раздела — а клиенты, отправившие их, уже получили ответ «успешно записано».
Именно поэтому split-brain — это не «кластер немного полежал», а действительно конец: не с точки зрения работоспособности железа, а с точки зрения целостности данных. Часть подтверждённых операций гарантированно теряется или требует ручного разбора, и чем дольше длился раздел и выше нагрузка на запись, тем больше объём расхождения.
Кворум: как большинство голосов не даёт кластеру расколоться
Единственный надёжный способ не допустить одновременного существования двух лидеров — не позволять узлам избирать лидера в одиночку или в составе меньшинства. Это и есть идея кворума: новый лидер может быть избран только той группой узлов, которая насчитывает больше половины от общего числа голосующих участников кластера.
Логика простая и от этого надёжная: при любом сетевом разделе кластер может расколоться на несколько групп, но среди них математически может существовать не более одной, где есть строгое большинство узлов. Если у вас пять узлов и раздел делит их на группы 3 и 2, только группа из трёх имеет право избрать лидера — у неё есть кворум (3 из 5). Группа из двух кворума не имеет и, если правило соблюдается честно, лидера избрать не может — она просто перестаёт принимать записи и уходит в режим только для чтения (или отказывает в обслуживании) до восстановления связи.
Подробно про механику кворума и про то, почему обязательно нужно нечётное число голосующих узлов, разобрано в статье про кворум и зачем в кластере нужен третий узел — здесь важно зафиксировать главное: кворум не предотвращает сам сетевой раздел, он предотвращает опасное *следствие* раздела — одновременное существование двух лидеров, готовых писать. Меньшинство добровольно самоустраняется от права принимать решения, потому что не может доказать себе, что оно не меньшинство.
Кворумные системы — etcd, Consul, ZooKeeper, встроенные механизмы Raft в базах вроде CockroachDB или YugabyteDB — требуют нечётного числа узлов не потому что чётное «не работает», а потому что раздел ровно пополам не даёт большинства ни одной из сторон. Лучше временный полный отказ в обслуживании, чем расхождение данных — это осознанный компромисс, а не недоработка.
Почему кластер из двух узлов особенно уязвим к split-brain
Два узла — самый частый и самый опасный случай. Раздел между двумя узлами всегда делит кластер 1 к 1: большинство от двух — это два, а при расколе у каждой стороны есть только один голос. Формального кворума не может добиться никто.
На практике администраторы часто «решают» эту проблему неправильно — назначают один из узлов приоритетным и говорят алгоритму: «не видишь второго, а сам жив — становись лидером». Это отключает защиту кворума полностью и превращает двухузловой кластер в гарантированный источник split-brain при любом сетевом сбое, потому что теперь оба узла при разделе рассуждают одинаково и оба решают, что вправе быть главным.
Та же ловушка хорошо видна не только в СУБД, но и в кластерах виртуализации — конкретный разбор того, как две ноды кластера запустили одну и ту же виртуальную машину, — это split-brain того же происхождения, просто на уровне гипервизора: два узла, нет третьего голоса, оба уверены, что вправе действовать. Про то, почему кластер Proxmox из двух узлов — плохая идея именно по этой причине, стоит прочитать, если планируете кластеризацию на двух серверах — механика та же, что и в кластере базы данных.
Правильных решения для двухузлового кластера три: добавить третий полноценный узел с независимой сетью до первых двух, использовать внешний арбитр — witness-узел или облачный quorum device, который не хранит данные, но участвует в голосовании, либо сознательно держать переключение лидера ручным, принимая более низкую доступность взамен на то, что split-brain технически невозможен.
Как это устроено в реальных системах
Разные СУБД решают задачу кворума по-разному, но принцип везде один — большинство голосов.
| Система | Механизм выбора лидера | Минимум узлов без арбитра | Внешний арбитр |
|---|---|---|---|
| PostgreSQL + Patroni | DCS (etcd/Consul/ZooKeeper) хранит лидерский ключ с TTL, кворум обеспечивает сам DCS | 3 узла DCS | не нужен отдельно, DCS сам кворумный |
| etcd / Consul (Raft) | Протокол Raft: лидер избирается только при голосах строгого большинства | 3 узла | witness/learner-узел без права голоса |
| MySQL Group Replication / Galera | Групповая коммуникация, транзакция коммитится только при подтверждении большинства узлов группы | 3 узла | garbd (arbitrator) — третий «голос» без хранения данных |
| MongoDB Replica Set | Голосование за primary среди voting-членов реплика-сета | 3 голосующих члена | arbiter — процесс без данных, только голос |
| Redis Sentinel | Sentinel-узлы голосуют за то, что master недоступен (quorum в конфиге) | 3 Sentinel-процесса | можно разносить Sentinel по хостам отдельно от Redis |
| Proxmox VE (corosync) | Кворум corosync на уровне кластера гипервизоров | 3 узла | QDevice — внешний кворумный сервис |
Общая деталь, которую стоит проверить в любой из этих систем: арбитр должен физически находиться в независимом сегменте сети — не за тем же коммутатором и не в том же дата-центре, что два основных узла. Арбитр, отключающийся вместе с одной из сторон раздела, кворум не защищает — он просто добавляет узел, который окажется недоступен именно тогда, когда нужен.
Отдельно стоит проверить таймаут, после которого узел решает, что лидер пропал (ttl в Patroni, down-after-milliseconds в Sentinel, election_timeout в Raft-подобных системах). Слишком короткий увеличивает риск ложного срабатывания на кратковременный сетевой затор — кластер начинает переизбирать лидера при каждой микро-аномалии, создавая лишнюю нагрузку и риск конфликтов. Слишком длинный — увеличивает простой при реальном отказе лидера. Универсального правильного числа не существует, его подбирают под конкретную инфраструктуру и проверяют учениями, а не берут из примера в документации.
Как понять, что кластер уже расколот, и что делать дальше
Первый и самый надёжный признак — health-check самого кластерного ПО прямо говорит об этом: Patroni в логах пишет о невозможности получить лидерский лок, corosync сообщает о потере кворума (Quorum lost), MongoDB replica set показывает больше одного узла в состоянии PRIMARY при опросе rs.status() с разных сторон раздела. Не полагайтесь на то, что приложение «просто скажет об ошибке» — пока раздел активен, обе половины отвечают на запросы без видимых ошибок, потому что каждая по отдельности работает штатно.
Второй признак — расхождение между узлами после восстановления связи: одинаковый запрос к двум узлам, формально считающимся репликами одного кластера, возвращает разные данные, или узел при попытке присоединиться отказывается это делать с ошибкой о разошедшихся журналах (timeline diverged в PostgreSQL, конфликт GTID в MySQL).
Если раскол уже произошёл, порядок действий такой: сначала остановить приём записи на обеих половинах, не пытаясь «на скорую руку» решить, какая правильная, пока обе продолжают писать. Дальше сравнить объём и содержание расхождения — если обе половины писали только в логически непересекающиеся таблицы, теоретически возможен ручной мерж, но это исключение. В общем случае одна сторона выбирается источником истины (обычно та, что была лидером до раздела и имеет непрерывный журнал транзакций без разрывов), вторая архивируется целиком для последующего разбора и ручного переноса данных, которые нельзя было потерять, с проверкой на дубликаты.
После восстановления кворумного консенсуса важно разобрать причину самого раздела — split-brain почти никогда не повторяется по той же причине, если её найти и устранить: перенастроить маршрутизацию, развести узлы кластера и witness-узел по независимым каналам, пересмотреть таймауты. Аренда серверов в нескольких независимых локациях с отдельными каналами связи — практический способ снизить вероятность одновременной потери связи сразу по нескольким маршрутам.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Может ли split-brain произойти в кластере из одного дата-центра?
Да, если между узлами есть хотя бы один сетевой сегмент, способный отказать независимо от остальных — общий коммутатор, VLAN, файрвол-правило. Расстояние не главный фактор, главный — независимость каналов связи и корректность кворума.
Если у меня три узла и кворум настроен правильно, split-brain невозможен в принципе?
Правильный кворум исключает одновременное существование двух лидеров с правом на запись. Но он не спасает от временной потери доступности: меньшинство при разделе уходит в read-only или отказывает полностью — с точки зрения приложения это тоже сбой, просто безопасный, без расхождения данных.
Чем fencing (STONITH) отличается от кворума?
Кворум решает, кто *имеет право* быть лидером. Fencing — принудительное отключение узла, который потерял это право, но физически ещё может отвечать на запросы. В критичных инсталляциях их комбинируют: кворум определяет нового лидера, fencing гарантированно изолирует старого.
Можно ли обойтись без арбитра, если оба узла в одном «надёжном» облаке?
Внутренняя сеть облачного провайдера — тоже сеть со своими отказами: обновления инфраструктуры, ошибки в security groups. «Надёжная» не значит «без разделов никогда» — арбитр или третий узел закрывает именно этот риск.
Что даёт четвёртый узел вместо третьего?
Кворум для четырёх — три из четырёх, защита та же, что для трёх, но раздел 2 на 2 снова оставляет кластер без большинства ни на одной стороне, как и с двумя узлами. Чётное число не даёт дополнительной защиты по сравнению с нечётным на один меньше — только увеличивает стоимость инфраструктуры.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →