Три локации, одна правда: где держать главную копию данных
Три сервера в трёх локациях звучит как правильная архитектура: ближе к пользователям, устойчивее к падению одного дата-центра, есть куда переключиться при аварии. Но если ни на одном этапе проектирования никто явно не написал, какая из трёх копий главная, а какие — производные, вы получили не отказоустойчивость, а три версии данных с равными правами на существование. Рано или поздно они разойдутся, и тогда единственный вопрос — какая правильная — окажется вопросом без ответа.
Содержание
- Почему несколько копий данных — это не одна проблема, а три разных
- Что происходит без явного мастера: механика расхождения
- Split-brain — крайний случай той же болезни
- Практический подход: одна копия — источник истины, остальные — производные
- Многомастеровая (multi-master) архитектура — это осознанный выбор, а не побочный эффект
- Как проверить, что у вас на самом деле, а не что вы думаете
- Сценарий восстановления после расхождения — что делать, если правды уже две
Почему несколько копий данных — это не одна проблема, а три разных
Прежде чем говорить про мастер и реплики, стоит развести три разные причины, по которым данные вообще оказываются в нескольких местах, потому что для каждой годится своя архитектура.
Первая причина — доступность (availability). Один сервер упал — сервис не должен упасть вместе с ним. Здесь копия нужна как горячий или тёплый резерв, который в норме не принимает запросов на запись, а включается при отказе основного узла.
Вторая причина — резервирование (durability). Копия нужна не для скорости отклика и не для отказоустойчивости здесь и сейчас, а на случай, если основная копия физически уничтожена — сгорел дата-центр, отозвали лицензию у провайдера, ошиблись руки администратора. Это уже ближе к бэкапу, чем к реплике, и часто разница между «у нас есть вторая копия» и «у нас есть рабочий бэкап» стирается: живая копия наследует любую ошибку из мастера почти мгновенно, а бэкап на фиксированный момент времени — нет.
Третья причина — географическая близость к пользователям (latency). Пользователи в Лондоне не должны ждать 150 мс до сервера в Москве на каждый запрос. Здесь копия данных нужна для чтения рядом с пользователем, а не для отказоустойчивости.
Проблема начинается, когда все три причины смешивают в одну архитектуру «у нас три сервера с данными», не проговорив, какая копия отвечает за что. Сервер в UK, который держат для скорости чтения европейских пользователей, вдруг начинает принимать записи «раз он всё равно рядом» — и вот у вас уже не read-реплика, а второй независимый источник истины, о существовании которого никто формально не договаривался.
Что происходит без явного мастера: механика расхождения
Представим типичный сценарий без злого умысла. У компании есть основная база в дата-центре A. Ради скорости для международных клиентов подняли копию в дата-центре B. Изначально это read-реплика: записи идут в A, репликация тянет их в B, приложение в регионе B читает локально. Работает нормально.
Дальше происходит что-то из следующего списка, встречается почти в каждой инфраструктуре:
- у администратора B кончается терпение ждать репликацию для локальной правки — он логинится в базу B напрямую и правит запись руками, «раз всё равно быстрее»;
- в приложении region B по ошибке конфига указан write-эндпоинт на локальную базу вместо основной — и часть трафика начинает писать в B;
- при аварии в A трафик переключили на B «временно», а через два часа A подняли обратно и включили как было, забыв, что B за эти два часа уже принял записи самостоятельно;
- у обеих баз включена двусторонняя репликация «для надёжности», и обе точки в теории равноправны — но при реальном конфликте (одна и та же строка изменена в обоих местах почти одновременно) СУБД либо молча берёт «последнюю по времени» правку, либо падает в ошибку конфликта, который никто не мониторит.
После любого из этих сценариев в A и B — разные данные по одному и тому же ключу. И вот тут наступает настоящая проблема: не сам факт расхождения (это может случиться в любой распределённой системе), а отсутствие ответа на вопрос «какая версия верна». Если бы был явно зафиксированный мастер, ответ был бы автоматическим: верна версия из мастера, всё остальное — по определению устаревшая копия, которую нужно перезаписать. Без мастера решение принимается на месте, в панике, и типичная ошибка — «синхронизировать» в неверную сторону: взять более свежую по времени модификации запись из B и залить её поверх A, хотя именно A должен был быть источником истины, а расхождение в B возникло из-за случайной записи, которую вообще не следовало туда пускать.
Мы разбирали похожий по механике случай при переезде с двойной записью на два адреса: двойная запись в две базы при переезде — где лежат ловушки. Логика та же: пока обе стороны считаются «действующими», конфликт неизбежен, а его цена растёт вместе с числом мест, куда идёт запись.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверSplit-brain — крайний случай той же болезни
Тот же самый вопрос «кто из узлов главный прямо сейчас» встаёт ещё острее в кластерах с автоматическим выбором лидера — PostgreSQL с Patroni, MySQL Group Replication, etcd, любой consensus-кластер. При разрыве сети между дата-центрами каждая часть кластера может решить, что именно она осталась «живой» и должна принимать записи. Если в конфигурации нет кворума большинства, обе половины начинают писать параллельно — и это уже не гипотетический риск, а классический split-brain, подробно разобранный в статье split-brain в кластере базы — как он рождается.
Важная параллель: split-brain — это автоматизированная версия той же ошибки, которую вручную совершает команда, когда у неё нет договорённости о мастере. В обоих случаях причина одна — отсутствие единственного источника истины, закреплённого либо технически (кворум, лидер-элекшн), либо организационно (документ и права доступа). Разница только в том, что кластер с кворумом умеет сам отказаться писать, если не набрал большинство голосов, а команда без документированной иерархии — нет.
Практический подход: одна копия — источник истины, остальные — производные
Рабочая модель, которая снимает 90% подобных инцидентов, формулируется одним предложением: одна копия данных является авторитетным источником истины (source of truth), все остальные копии либо реплицируются от неё только на чтение, либо синхронизируются в одну сторону — от мастера к ним, а не наоборот.
Это не абстрактный принцип, а набор конкретных решений:
- Один мастер на запись. У каждой сущности данных (базы, таблицы, документа) есть ровно один сервер, куда разрешена запись в обычном режиме. Остальные — read-only.
- Технически закреплённый режим read-only, а не honor system. Недостаточно договориться на словах «в B не пишем». На уровне СУБД реплика должна физически не принимать запись —
hot_standbyв PostgreSQL,read_only=1в MySQL, права доступа на уровне пользователя приложения, которому в региональной базе выдан коннект только с SELECT-грантами. - Явная точка переключения при аварии (failover), а не автоматическая по умолчанию. Если мастер недоступен, кто-то — человек или контролируемый механизм с кворумом — должен явно решить: считаем ли мы, что старый мастер мёртв, и переключаем роль на реплику. До этого решения реплика остаётся read-only, даже если приложение недоступно для записи. Это неприятно для пользователей в моменте, но предотвращает раздвоение данных.
- Документ, а не только код. Даже при полностью автоматизированном кворумном кластере должен существовать читаемый людьми документ (README инфраструктуры, wiki, runbook) с одной строкой: «источник истины по клиентским данным — сервер X в локации Y, все остальные копии производные». Это не для машины — это для человека, который в 3 часа ночи разбирается, почему данные разошлись, и должен за 30 секунд понять, откуда брать верную версию.
- Мониторинг лага и расхождения, а не только факта работы репликации. Реплика может быть «зелёной» в мониторинге (процесс запущен) и одновременно отставать на часы или содержать записи, которых нет в мастере, если в неё когда-то писали напрямую. Про типичный сценарий с отставанием — в статье как работает репликация и отставание реплики.
Таблица ниже — простой чек-лист «кто есть кто» для трёх копий, который стоит буквально выписать и приложить к документации проекта:
| Локация | Роль | Пишет? | Что делать при расхождении |
|---|---|---|---|
| RU (основной ДЦ) | Мастер / источник истины | Да, все записи | Эталон. Остальные копии приводятся к нему |
| UK | Read-реплика для EU-трафика | Нет (read-only) | Пересоздать реплику от мастера, локальные правки — потеря по определению |
| US | Read-реплика / DR-резерв | Нет, кроме явного failover | При выявлении расхождения — сверка с мастером, не наоборот |
Многомастеровая (multi-master) архитектура — это осознанный выбор, а не побочный эффект
Здесь важно не впасть в другую крайность и не сказать «многомастеровые схемы — это всегда плохо». Multi-master репликация (например, BDR для PostgreSQL, Galera Cluster для MySQL/MariaDB, active-active конфигурации CockroachDB) — легитимная и рабочая архитектура для задач, где запись действительно нужна из нескольких точек одновременно: например, локальная запись в каждом регионе ради низкой задержки при большом объёме локального трафика.
Но у осознанной multi-master архитектуры всегда есть то, чего нет в случайно возникшей ситуации из раздела выше:
- явный механизм разрешения конфликтов на уровне СУБД (last-write-wins с синхронизированными часами, CRDT-подобные структуры данных, векторные часы) — а не «какая запись пришла в реплику последней по таймстампу файловой системы»;
- продуманное партиционирование данных так, чтобы конфликты вообще возникали редко (пользователь региона RU пишет свои данные в RU-узел, пользователь региона US — в US-узел, пересечение по одним и тем же строкам минимально);
- осознанная цена — если БД должна дожидаться подтверждения записи с нескольких узлов (синхронная схема), это прямой удар по задержке записи, разбор компромисса — в статье синхронная репликация — цена надёжности;
- мониторинг именно конфликтов записи как метрики, а не только факта живости кластера.
Если ничего из этого не спроектировано, а «многомастеровость» — это просто исторически сложившаяся ситуация, когда несколько серверов случайно принимают запись, — это не архитектура, а технический долг, который однажды предъявит счёт в виде расхождения данных без понятного способа их сверить.
Как проверить, что у вас на самом деле, а не что вы думаете
Практическая проверка занимает час и обычно вскрывает неприятные сюрпризы. Пройдите по каждой локации с данными и ответьте на четыре вопроса:
1. Какой пользователь приложения подключается к этой копии?
SELECT usename, application_name FROM pg_stat_activity;
-- если видите запись через пользователя с правом INSERT/UPDATE
-- на "read-реплику" — вот и первый кандидат в аварию
2. Read-only на уровне СУБД реально включён?
-- PostgreSQL: проверить, standby ли узел
SELECT pg_is_in_recovery();
-- true = физически не может принять запись, это и нужно
-- MySQL: проверить read_only и super_read_only
SHOW VARIABLES LIKE '%read_only%';
3. Лаг репликации мониторится и алертится, а не проверяется вручную?
-- PostgreSQL, на мастере:
SELECT client_addr, state, sent_lsn, replay_lsn,
pg_wal_lsn_diff(sent_lsn, replay_lsn) AS lag_bytes
FROM pg_stat_replication;
- Есть ли документ (не в чьей-то голове, а в репозитории или wiki), где явно написано, какая копия — источник истины?
Если хотя бы один ответ отрицательный — у вас не спроектированная многолокационная архитектура, а набор серверов с данными, которые пока не разошлись только потому, что не было повода. Повод рано или поздно случается: обычно это авария в основной локации и решение «переключиться, пока чинят», принятое второпях без явного протокола возврата.
Сценарий восстановления после расхождения — что делать, если правды уже две
Если расхождение уже случилось, порядок действий должен быть определён заранее, а не изобретаться в моменте.
- Зафиксируйте обе версии до любых действий. Снимите дамп или снапшот обеих копий с точным временем снятия, прежде чем что-то менять. Это единственный шанс потом разобраться, что именно разошлось.
- Определите источник истины по документу, а не по интуиции. Если в документации записано, что мастер — сервер в RU, то верна версия RU, даже если версия в UK выглядит «более полной» или «более свежей» на первый взгляд.
- Не сливайте данные автоматически. Автоматическое слияние двух версий одной строки (взять более новую по timestamp, взять непустое значение) кажется безопасным, но именно так теряются легитимные изменения, которые были сделаны в мастере позже точки расхождения, но раньше момента слияния.
- Пересоздайте нелегитимную копию с нуля от мастера, а не пытайтесь «догнать» её точечными правками. Реплика, в которую когда-то писали напрямую, доверия не заслуживает целиком — в ней могут быть скрытые артефакты в местах, которые вы не проверяли.
- Найдите и закройте канал, через который возникла запись не в том месте — неверный конфиг эндпоинта, оставленный доступ на запись, ошибку в скрипте failover. Без этого шага история повторится.
После инцидента полезно свериться не только по количеству строк, но и по содержимому: совпадающее число записей в обеих копиях не гарантирует, что значения в них совпадают — расхождение может прятаться внутри строк, которые формально существуют в обеих версиях.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Чем реплика для отказоустойчивости отличается от бэкапа?
Реплика — это живая, постоянно обновляемая копия, которая наследует любую ошибку из мастера почти мгновенно, включая случайное удаление данных. Бэкап — это копия на фиксированный момент времени, изолированная от текущего состояния мастера, которая позволяет откатиться назад. Обе нужны одновременно, они закрывают разные риски, и одна не заменяет другую.
Можно ли сделать так, чтобы приложение само переключало запись на ближайший сервер?
Технически можно, но это и есть переход к multi-master архитектуре — со всеми требованиями к разрешению конфликтов, партиционированию данных и мониторингу, описанными выше. Если это не осознанное решение с этими компонентами, а просто «пусть приложение пишет туда, куда быстрее», вы получаете риск расхождения без защиты от него.
Как физически запретить запись в read-реплику, если разработчики иногда логинятся туда напрямую?
Заведите отдельного пользователя СУБД для реплики с правами только SELECT, а суперпользовательский доступ выдавайте по отдельному запросу с логированием, а не как учётку по умолчанию для повседневной работы. Технический барьер надёжнее договорённости.
Что делать, если исторически сложилось так, что писали в несколько мест и непонятно, какое из них правильное?
Начните с фиксации фактического состояния: снимите снапшоты всех копий, сравните их между собой по контрольным полям (не по количеству строк, а по хешам содержимого ключевых таблиц), и на основе бизнес-логики — где логичнее всего вести первичный учёт — назначьте мастера явным решением, а не техническим измерением. После этого перестройте остальные копии от него.
Обязательно ли все три локации размещать у одного провайдера?
Нет, географическое распределение и распределение по провайдерам — разные оси резервирования, и их можно комбинировать. Но важно, чтобы независимо от того, у кого физически стоит сервер, роль мастера и реплик была определена так же чётко.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →