Репликация баз данных: почему master-slave уже недостаточно

Классическая репликация master-slave работала отлично последние 20 лет. Один узел (master) принимает записи, остальные (slave) только читают. Просто, понятно, предсказуемо.

Примечание: в современной терминологии master-slave часто заменяется на primary-replica или leader-follower из-за негативных ассоциаций старых терминов. В этой статье мы используем оба набора терминов как синонимы, поскольку техническая суть от названия не меняется.

Но в 2025 году вы запускаете глобальное приложение с пользователями в Сингапуре, Франкфурте и Сан-Франциско. Латентность к единственному master в Вирджинии убивает пользовательский опыт. Попытка перенести master в Европу убивает уже азиатских пользователей. И тут вы понимаете: модель, которая работала для блогов и интернет-магазинов, не масштабируется для современных требований.

Добро пожаловать в мир, где master-slave — это baseline, а не решение.

Что не так с классической репликацией

Репликация master-slave работает элегантно просто. Один узел — master — принимает все операции записи. Изменения записываются в журнал транзакций и отправляются на узлы-slave. Slave применяют изменения в том же порядке и обслуживают операции чтения. Если master падает, один из slave повышается до master.

Для приложений где 80-90% запросов это чтение (блоги, новостные сайты, каталоги продуктов), это работает превосходно. Вы масштабируете чтение горизонтально, добавляя slave. Запись остаётся централизованной на одном узле, что гарантирует консистентность.

Проблема первая: задержка репликации

Задержка репликации — это время между записью на master и применением на slave. В идеальном мире это миллисекунды. В реальном мире это может быть секунды или даже минуты при высокой нагрузке.

Типичный сценарий: пользователь регистрируется, система записывает учётную запись на master. Пользователя перенаправляет на страницу профиля, запрос попадает на slave. Slave ещё не получил обновление. Пользователь видит ошибку "учётная запись не найдена" через секунду после успешной регистрации.

Это не теоретическая проблема. Исследования показывают что задержка репликации остаётся одной из главных жалоб на классическую архитектуру. При пиковой нагрузке slave могут отставать на минуты, создавая видимую пользователям несогласованность.

Проблема вторая: единая точка отказа для записи

Slave обеспечивают отказоустойчивость для чтения. Но запись? Всё идёт через один узел. Если master падает, система перестаёт принимать записи до тех пор, пока один из slave не повысится до master. Этот процесс failover может занять от секунд до минут в зависимости от настройки.

Автоматическое повышение slave сложнее чем кажется. Нужно выбрать slave с наиболее актуальными данными, переконфигурировать приложения на новый master, убедиться что старый master (если вернётся) не будет продолжать принимать запросы. Каждый шаг — потенциальная точка сбоя. Многие команды предпочитают ручное повышение именно из-за этой сложности.

Проблема третья: ограничения масштабируемости записи

Добавление slave масштабирует чтение. Но запись? Ограничена производительностью одного узла. Если ваше приложение пишет 10,000 транзакций в секунду и приближается к пределу возможностей master, что делать? Вертикальное масштабирование (больше процессоров, памяти, быстрее диски) работает до определённого предела. Дальше вы упираетесь в физические ограничения железа.

Многие команды обнаруживают эту проблему когда уже поздно. Вы спокойно растёте несколько лет, добавляя slave по мере роста трафика чтения. Потом вдруг взрывной рост записей — новая функция, вирусный контент, маркетинговая кампания — и master становится узким местом. Решения нет быстрого.

Проблема четвёртая: глобальная латентность

Пользователь в Сингапуре подключается к приложению. Slave базы данных находится в том же регионе — чтение быстрое, 5-10 миллисекунд. Но для записи запрос должен пройти полмира до master в Вирджинии. 200+ миллисекунд туда, 200+ обратно. Каждое сохранение, каждое обновление — полсекунды задержки.

Современные пользователи ожидают мгновенного отклика. Задержка в полсекунды на каждое действие превращает приложение в мучение. Особенно для интерактивных функций — совместное редактирование, чаты, обновления в реальном времени.

Почему это стало критично сейчас

Десять лет назад большинство приложений были региональными. Американский стартап обслуживал американских пользователей, европейская компания — европейских. Master-slave работала отлично.

Сейчас даже маленький стартап рассчитывает на глобальную аудиторию с первого дня. SaaS приложения обслуживают пользователей на всех континентах. Мобильные приложения работают везде где есть интернет. Ожидания пользователей выросли — они хотят той же скорости что и у Google Docs или Figma, где изменения появляются мгновенно независимо от локации.

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

Современные подходы к репликации

Репликация с несколькими master (multi-master)

Вместо одного master создаётся несколько. Каждый может принимать записи. Изменения синхронизируются между всеми master. Звучит как идеальное решение для глобальных приложений — master в каждом регионе, низкая латентность везде.

Реальность сложнее. Главная проблема — конфликты. Два пользователя одновременно обновляют одну запись на разных master. Один в Европе устанавливает статус "активен", другой в Азии устанавливает "неактивен". Когда узлы синхронизируются, какое значение побеждает?

Стратегии разрешения конфликтов существуют. "Последняя запись побеждает" основывается на временных метках, но часы в распределённых системах не идеально синхронизированы. "Первая запись побеждает" требует центрального арбитра, что убивает преимущества распределённости. Логика разрешения на уровне приложения перекладывает сложность на разработчиков.

Исследования показывают что multi-master работает хорошо когда записи можно разделить по регионам или категориям. Например, пользователи в Европе пишут только в европейский master, азиатские — в азиатский. Но это требует тщательного дизайна на уровне приложения и не работает для глобально распределённых данных.

Бесконфликтные реплицируемые типы данных (CRDT)

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

Простой пример: счётчик. Вместо хранения одного значения, каждая реплика хранит вектор значений — по одному для каждого узла. Когда узел увеличивает счётчик, он увеличивает свою позицию в векторе. При синхронизации узлы объединяют векторы, беря максимум каждой позиции. Итоговое значение — сумма всех позиций. Неважно в каком порядке происходили увеличения, результат всегда один.

CRDT используются в Redis Enterprise для геораспределённых баз данных, в Riak для высокодоступного хранилища, в системах совместного редактирования типа Figma и Google Docs. Они решают проблему конфликтов элегантно — просто не допуская их возникновения.

Но есть цена. CRDT требуют больше памяти — нужно хранить дополнительные метаданные. Производительность зависит от типа данных и операций. Не все операции можно реализовать как CRDT — некоторые требуют координации. И главное — требуется сборка мусора, иначе метаданные растут бесконечно.

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

Распределённый консенсус

Протоколы типа Raft и Paxos позволяют группе узлов прийти к согласию о порядке операций даже при сбоях. Это основа современных распределённых баз данных типа CockroachDB, TiDB, YugabyteDB.

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

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

Реальные кейсы провалов

GitHub и задержка репликации

В 2018 году GitHub пережил крупный сбой из-за проблем с репликацией. Основной и реплика разъединились на 43 секунды. За это время на основной продолжали поступать записи. Когда соединение восстановилось, выяснилось что реплика отстала настолько что обычная синхронизация не работает. Потребовалось восстановление из резервной копии и ручная синхронизация данных.

Проблема усугубилась тем что автоматические системы мониторинга не обнаружили расхождение достаточно быстро. Пользователи видели разные данные в зависимости от того на какую реплику попадал запрос. Результат — несколько часов деградации сервиса и потеря части данных.

Урок: задержка репликации не просто неудобство. При определённых условиях она может привести к катастрофическому расхождению данных. Нужны не только инструменты мониторинга, но и процессы для быстрого обнаружения и реакции.

Figma и переход на CRDT

Figma начинала с традиционной архитектуры клиент-сервер. При совместном редактировании все изменения отправлялись на сервер, который разрешал конфликты и рассылал обновления всем клиентам. Это работало, но масштабировалось плохо. При большом количестве одновременных редакторов сервер становился узким местом.

Переход на CRDT позволил перенести разрешение конфликтов на клиентскую сторону. Каждое изменение применяется локально мгновенно, затем отправляется другим клиентам. Конфликты разрешаются автоматически благодаря свойствам CRDT. Результат — мгновенный отклик и масштабируемость до сотен одновременных редакторов.

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

Amazon Shopping Cart и конфликты

Классический пример из академической литературы — корзина покупок Amazon в ранние годы. При репликации с несколькими основными узлами возникала проблема: пользователь удалял товар из корзины на одном узле, но тот же товар добавлялся обратно из-за конфликта с другим узлом.

Amazon выбрал принцип "добавление побеждает удаление". Лучше показать пользователю товар который он вроде бы удалил (и он может удалить снова), чем потерять товар который он хотел купить. Это бизнес-решение, встроенное в техническую архитектуру.

Урок: разрешение конфликтов не чисто техническая задача. Нужно понимать бизнес-контекст и принимать решения исходя из того что приемлемо для пользователей и бизнеса.

Когда использовать какой подход

Master-slave остаётся отличным выбором для многих приложений. Если ваше соотношение чтение-запись сильно смещено к чтению, если пользователи в основном в одном регионе, если задержка записи в сотни миллисекунд приемлема — не усложняйте. Эта модель проверена десятилетиями, инструменты зрелые, большинство разработчиков её понимают.

Несколько master имеет смысл когда у вас чёткое географическое или функциональное разделение данных. Европейские пользователи пишут европейские данные, азиатские — азиатские. Или разные категории данных живут на разных master. Главное — избегайте ситуации где одни и те же данные могут изменяться на разных узлах.

CRDT блестят в приложениях реального времени где нужна мгновенная локальная реакция и допустима временная несогласованность. Совместное редактирование, мгновенные сообщения, совместные доски, игры. Но будьте готовы инвестировать в понимание математики за CRDT и разработку правильной сборки мусора.

Распределённый консенсус выбирайте когда нужна строгая консистентность при геораспределении. Финансовые транзакции, инвентаризация, системы бронирования — везде где несогласованность недопустима. Платите латентностью за гарантии консистентности.

Для большинства сложных приложений ответ — гибрид. Разные типы данных требуют разных стратегий репликации. Критичные данные типа платежей идут через консенсус. Профили пользователей реплицируются master-slave. Совместное редактирование использует CRDT. Кеши используют ленивую репликацию без строгой консистентности.

Скрытые сложности современной репликации

Сборка мусора в CRDT

CRDT накапливают метаданные о каждой операции для корректного объединения. Без сборки мусора эти метаданные растут бесконечно. Простая операция добавления элемента в набор может оставить след который никогда не удаляется.

Стратегии сборки мусора существуют. Временные метки с expiry — удаляем метаданные старше определённого времени. Контрольные точки — периодически создаём снимок состояния и удаляем всё что до него. Согласованное удаление — координируемся между узлами когда безопасно удалять метаданные.

Каждая стратегия имеет компромиссы. Временное удаление может потерять данные если узел был офлайн дольше периода expiry. Контрольные точки требуют координации и могут создавать пики нагрузки. Согласованное удаление частично убивает преимущества CRDT — отсутствие необходимости координации.

Каузальность и порядок доставки

Некоторые CRDT требуют каузального порядка доставки операций. Если операция B зависит от операции A, B должна применяться после A на всех узлах. Обеспечение каузального порядка в распределённой системе нетривиально.

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

Существуют более эффективные структуры типа interval tree clocks, но они сложнее в реализации и понимании.

Мониторинг и отладка

Отладка проблем репликации в традиционной архитектуре master-slave относительно прямолинейна. Есть один источник правды (master), есть логи репликации, есть чёткие метрики задержки. Проблема видна и понятна.

В распределённой системе с несколькими master или CRDT всё сложнее. Нет единого источника правды. Состояние может временно расходиться между узлами. Определить почему данные выглядят именно так требует понимания всей истории операций на всех узлах.

Инструменты для мониторинга и отладки распределённых баз данных менее зрелые чем для традиционных. Нужны специализированные метрики, визуализация состояния синхронизации, трейсинг операций через узлы. Многие команды разрабатывают собственные инструменты, что добавляет сложности.

Стоимость экспертизы

Master-slave понимает большинство разработчиков. Найти специалиста по PostgreSQL или MySQL репликации не проблема. Обучающих материалов море, best practices хорошо документированы, Stack Overflow полон ответов.

Для CRDT или распределённого консенсуса пул экспертов на порядки меньше. Обучающие материалы в основном академические статьи. Мало готовых решений, много нужно разрабатывать самостоятельно. Наём специалиста дороже и дольше.

Это не аргумент против современных подходов, но реальность которую нужно учитывать. Если вы стартап с командой из пяти человек, инвестиции в освоение CRDT могут не окупиться. Если вы компания масштаба Figma или Discord, они абсолютно оправданы.

Главные выводы

Master-slave не мертва. Для многих приложений это всё ещё правильный выбор. Простая, надёжная, хорошо понятная архитектура с зрелыми инструментами и широким пулом экспертизы.

Но мир изменился. Глобальные приложения, интерактивность в реальном времени, ожидания мгновенного отклика — всё это требует переосмысления подходов к репликации. Одна модель больше не подходит всем.

Несколько master решают проблему глобальной латентности, но создают проблему конфликтов. CRDT решают конфликты элегантно, но требуют инвестиций в понимание и реализацию. Распределённый консенсус даёт строгую консистентность, но платой за это является латентность.

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

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

Хорошая новость: инструменты и платформы становятся лучше. Управляемые базы данных берут на себя сложность репликации. Библиотеки CRDT становятся доступнее. Протоколы консенсуса стандартизируются. То что пять лет назад требовало месяцев работы PhD специалистов, сейчас доступно обычной команде разработки.

Плохая новость: фундаментальные компромиссы распределённых систем никуда не делись. Теорема CAP всё ещё верна — нельзя одновременно иметь консистентность, доступность и устойчивость к разделению сети. Латентность ограничена скоростью света. Конфликты неизбежны в системе где несколько узлов могут изменять данные независимо.

Лучший подход: начинайте просто. Master-slave отлично работает до определённого масштаба. Когда упрётесь в ограничения — анализируйте конкретные проблемы и решайте их целенаправленно. Не пытайтесь решить глобальное распределение, конфликты и строгую консистентность одновременно. Решайте проблемы по мере их возникновения, инвестируя в сложность только там где она оправдана бизнес-ценностью.