Репликация
Интуиция: главная книга и её копии
Представь бухгалтерию с одной главной книгой. Записывать в неё может только главный бухгалтер. В филиалах лежат копии книги, и курьер регулярно привозит туда новые страницы. Любой сотрудник может заглянуть в ближайшую копию. Так главный бухгалтер не утонет в вопросах «а сколько у нас на счёте?».
Есть два подвоха:
- курьер иногда опаздывает, и копия показывает вчерашние цифры;
- если главный бухгалтер заболел, кому-то из филиала нужно срочно стать главным.
Это и есть репликация. Дальше разберём каждый подвох отдельно.
Схема: лидер и фолловеры
Правила простые:
- Запись принимает только лидер.
- Лидер пишет изменения в лог (в PostgreSQL это WAL, в MySQL — binlog) и отправляет его фолловерам.
- Чтение можно отправлять на реплики. Так масштабируются нагрузки, где чтений намного больше, чем записей.
Считаем: сколько нужно реплик
Допустим, у нас лента новостей на 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) проходит в три шага:
- Обнаружить. Лидер перестал отвечать на heartbeat дольше таймаута, обычно 10–30 секунд.
- Выбрать нового лидера. Берём реплику с самым свежим логом. Решение принимает консенсус: Patroni + etcd, оркестратор, облачный Multi-AZ.
- Переключить клиентов. Обновляем DNS или VIP либо перенастраиваем прокси (PgBouncer, HAProxy). Остальные реплики начинают следовать за новым лидером.
Что может пойти не так — это и стоит назвать интервьюеру:
- Потеря записей. При асинхронной репликации последние транзакции старого лидера могли не дойти до реплик. Они пропадут.
- Split-brain. Старый лидер «ожил» и тоже принимает записи. Теперь лидеров два, и данные расходятся. Защита — fencing: старому узлу запрещают писать, например через STONITH или номер эпохи.
- Неудачный таймаут. Слишком короткий вызовет ложные переключения при сетевых лагах. Слишком длинный — долгий простой.
Мультилидер — коротко
Можно разрешить запись в несколько лидеров, обычно по одному на дата-центр. Пользователь в Европе пишет в европейский лидер, в Азии — в азиатский, а лидеры обмениваются изменениями.
- Плюсы: низкая задержка записи в разных регионах, переживает падение целого ДЦ.
- Минусы: конфликты записи. Два человека одновременно поменяли одно поле в разных ДЦ. Их разрешают по правилу last-write-wins (с риском потерять данные), через CRDT или логику приложения.
Озвучивай мультилидер только когда в задаче есть несколько регионов и важна запись с низкой задержкой. Без этого он даёт больше сложности, чем пользы.
Как это звучит на собесе
«Нагрузка в основном на чтение, поэтому ставлю PostgreSQL в схеме лидер–фолловеры: записи идут в лидера, чтения распределяю по 4–5 репликам. Репликация полусинхронная: одна синхронная реплика страхует от потери данных, остальные асинхронные. Из-за lag профиль владельца читаю с лидера. При падении лидера Patroni переключает на синхронную реплику, старый узел отсекается fencing'ом. Если упрёмся в записи — это уже про шардинг».
Частые ошибки
- Говорить, что реплики ускоряют записи. Не ускоряют.
- Забывать про replication lag и молча читать с реплик сразу после записи.
- Обещать «нулевую потерю данных» при асинхронной репликации.
- Описывать failover без упоминания split-brain.
- Предлагать мультилидер «на всякий случай», не назвав проблему конфликтов.
Освоил эти пять пунктов — значит, репликацию ты уже объяснишь увереннее половины кандидатов.