Ctrl K
Урок

Репликация

Интуиция: главная книга и её копии

Представь бухгалтерию с одной главной книгой. Записывать в неё может только главный бухгалтер. В филиалах лежат копии книги, и курьер регулярно привозит туда новые страницы. Любой сотрудник может заглянуть в ближайшую копию. Так главный бухгалтер не утонет в вопросах «а сколько у нас на счёте?».

Есть два подвоха:

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

Это и есть репликация. Дальше разберём каждый подвох отдельно.

Схема: лидер и фолловеры

HTTPSзаписьWAL / логWAL / логWAL / логчтениечтениеКлиентклиентAPI-сервиссервисЛидер (primary)БДРеплика #1БДРеплика #2БДРеплика #3БД
Все записи идут в лидера, он рассылает лог изменений репликам, чтения распределяются по репликам

Правила простые:

  1. Запись принимает только лидер.
  2. Лидер пишет изменения в лог (в PostgreSQL это WAL, в MySQL — binlog) и отправляет его фолловерам.
  3. Чтение можно отправлять на реплики. Так масштабируются нагрузки, где чтений намного больше, чем записей.

Считаем: сколько нужно реплик

Допустим, у нас лента новостей на 20 000 RPS, и 90% запросов — чтения.

Что Расчёт Итог
Чтения 20 000 × 0,9 18 000 RPS
Записи 20 000 × 0,1 2 000 RPS
Один узел держит чтений примерно ~5 000 RPS
Реплик для чтения 18 000 / 5 000 ≈ 3,6 → 4, плюс 1 в запас 5 реплик
Записи все уходят в лидера 2 000 RPS на один узел

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

Синхронно или асинхронно

Самый важный вопрос: ждёт ли лидер подтверждения от реплик, прежде чем ответить клиенту «записано».

Синхронная Асинхронная Полусинхронная
Лидер ждёт все реплики никого хотя бы одну реплику
Задержка записи высокая минимальная умеренная
Потеря данных при падении лидера нет возможна почти нет
Если реплика «легла» записи встают ничего не ломается ждём другую реплику

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

Replication lag и «читать свою запись»

При асинхронной репликации реплика отстаёт от лидера. Обычно на миллисекунды, но под нагрузкой — на секунды и даже минуты. Это replication lag.

Классический баг выглядит так: пользователь сменил аватар, страница перезагрузилась, а аватар старый. Запись ушла в лидера, а чтение попало на отставшую реплику. Пользователь решает, что сайт сломан.

Нам нужна гарантия read-your-writes: пользователь всегда видит свои собственные изменения. Вот как её обеспечить:

  • Своё читаем с лидера. Профиль, настройки и корзину владельца читаем с лидера, чужие данные — с реплик.
  • Окно после записи. В течение N секунд после записи (например, 5–10) все чтения этого пользователя идут в лидера. Метку времени можно хранить в cookie или сессии.
  • Позиция в логе. Запоминаем LSN (позицию последней записи пользователя) и читаем только с той реплики, которая догнала эту позицию.
  • Sticky-реплика. Один пользователь всегда читает с одной реплики. Это не даёт данным «прыгать назад во времени» (monotonic reads).

На собесе скажи прямо: «Реплики асинхронные, поэтому возможен lag. Для read-your-writes читаю данные владельца с лидера или жду, пока реплика догонит LSN его записи». Это сразу показывает зрелость.

Отказ лидера: failover

Лидер упал. Записывать некуда. Переключение (failover) проходит в три шага:

  1. Обнаружить. Лидер перестал отвечать на heartbeat дольше таймаута, обычно 10–30 секунд.
  2. Выбрать нового лидера. Берём реплику с самым свежим логом. Решение принимает консенсус: Patroni + etcd, оркестратор, облачный Multi-AZ.
  3. Переключить клиентов. Обновляем DNS или VIP либо перенастраиваем прокси (PgBouncer, HAProxy). Остальные реплики начинают следовать за новым лидером.

Что может пойти не так — это и стоит назвать интервьюеру:

  • Потеря записей. При асинхронной репликации последние транзакции старого лидера могли не дойти до реплик. Они пропадут.
  • Split-brain. Старый лидер «ожил» и тоже принимает записи. Теперь лидеров два, и данные расходятся. Защита — fencing: старому узлу запрещают писать, например через STONITH или номер эпохи.
  • Неудачный таймаут. Слишком короткий вызовет ложные переключения при сетевых лагах. Слишком длинный — долгий простой.

Мультилидер — коротко

Можно разрешить запись в несколько лидеров, обычно по одному на дата-центр. Пользователь в Европе пишет в европейский лидер, в Азии — в азиатский, а лидеры обмениваются изменениями.

  • Плюсы: низкая задержка записи в разных регионах, переживает падение целого ДЦ.
  • Минусы: конфликты записи. Два человека одновременно поменяли одно поле в разных ДЦ. Их разрешают по правилу last-write-wins (с риском потерять данные), через CRDT или логику приложения.

Озвучивай мультилидер только когда в задаче есть несколько регионов и важна запись с низкой задержкой. Без этого он даёт больше сложности, чем пользы.

Как это звучит на собесе

«Нагрузка в основном на чтение, поэтому ставлю PostgreSQL в схеме лидер–фолловеры: записи идут в лидера, чтения распределяю по 4–5 репликам. Репликация полусинхронная: одна синхронная реплика страхует от потери данных, остальные асинхронные. Из-за lag профиль владельца читаю с лидера. При падении лидера Patroni переключает на синхронную реплику, старый узел отсекается fencing'ом. Если упрёмся в записи — это уже про шардинг».

Частые ошибки

  • Говорить, что реплики ускоряют записи. Не ускоряют.
  • Забывать про replication lag и молча читать с реплик сразу после записи.
  • Обещать «нулевую потерю данных» при асинхронной репликации.
  • Описывать failover без упоминания split-brain.
  • Предлагать мультилидер «на всякий случай», не назвав проблему конфликтов.

Освоил эти пять пунктов — значит, репликацию ты уже объяснишь увереннее половины кандидатов.