Репликация базы и переключение лидера
Поднял реплику из копии основной базы и проверил три вещи. Первая: попытка записать на реплику даёт «cannot execute INSERT in a read-only transaction». Вторая: при обычной работе задержка применения - 2,9 миллисекунды. Третья: под нагрузкой в 800 тысяч вставок на реплику какое-то время не доезжало 8,1 мегабайта журнала, но за три секунды она догнала.
Стержень: синхронность это выбор между потерей данных и задержкой записи; чтение с реплики упирается в отставание; а слот репликации при упавшей реплике съедает диск основной базы.
// Формулировки: «синхронная или асинхронная?», «можно ли читать с реплики?», «реплика отстала на час, что делать?»
Как это устроено и сколько стоит синхронность
Реплика получает поток записей журнала предзаписи с основной базы и проигрывает их у себя. Она работает в режиме восстановления и потому доступна только на чтение - это не настройка, а следствие устройства.
При асинхронной репликации основная база подтверждает запись клиенту сразу, не дожидаясь реплики. При синхронной - ждёт, пока реплика подтвердит получение. Замерил цену: 300 транзакций в одну сессию дали среднюю задержку 0,553 миллисекунды при асинхронной и 0,964 при синхронной, пропускная способность упала с 1808 транзакций в секунду до 1037. То есть каждая фиксация - момент, когда транзакция считается окончательно записанной, - подорожала примерно в 1,7 раза. И это на соседнем контейнере, где сеть идеальная; между разными площадками дата-центра цена будет заметно выше.
// Взамен синхронность даёт гарантию: подтверждённая клиенту запись есть минимум в двух местах. Выбор формулируется так: сколько подтверждённых транзакций мы готовы потерять при потере основной базы. Ноль - синхронная, «несколько последних» - асинхронная.
- асинхронная репликация
- основная база отвечает клиенту, не дожидаясь реплики
- синхронная репликация
- ответ клиенту только после подтверждения от реплики
- режим восстановления
- состояние реплики: она проигрывает журнал и доступна только на чтение
Чем опасна синхронность и чем опасен слот
Проверил, что будет, если синхронная реплика упадёт. Остановил её и запустил вставку, ограничив ожидание пятью секундами. Через восемь секунд запрос всё ещё висел в состоянии ожидания подтверждения от реплики. Ограничение времени запроса ЭТО НЕ ПРЕРЫВАЕТ: механизм ждёт отдельно и на такие настройки не смотрит. Другие сеансы в этот момент строку не видели. Как только я снял требование синхронности, запрос завершился и строка появилась.
Отсюда правило: одна синхронная реплика - это риск полной остановки записи при её падении. Поэтому либо держат две и требуют подтверждения от любой одной, либо мирятся с асинхронностью.
// Вторая ловушка живёт рядом. Слот репликации - это отметка «реплика дочитала досюда», и основная база обязана хранить журнал до этой отметки. Замерил: при остановленной реплике и вставке 400 тысяч строк слот удерживал уже 54 мегабайта журнала и продолжал расти. Реплика молчит неделю - диск основной базы кончается. Когда я вернул реплику, она догнала за считанные секунды, и удержание упало до нуля. Поэтому у слотов ставят предел размера удерживаемого журнала: лучше потерять отставшую реплику, чем основную базу.
- слот репликации
- отметка «реплика дочитала досюда»; база хранит журнал до неё
- предел удержания
- ограничение на объём журнала, который слот вправе удерживать
Чтение с реплики и переключение
Разгружать основную базу чтением с реплики можно, но не любым чтением. Реплика всегда немного отстаёт: у меня при спокойной работе это 2,9 миллисекунды, а под нагрузкой в 800 тысяч вставок отставание доходило до 8,1 мегабайта неприменённого журнала и рассасывалось за три секунды. Значит, читать с реплики можно отчёты и аналитику, а вот прочитать только что записанное собой - нельзя: пользователь сохранит профиль и увидит старую версию.
Рабочее правило: запросы, где нужна собственная свежая запись, идут в основную базу; всё остальное можно на реплику. И обязательно нужен мониторинг отставания в двух видах: в секундах и в байтах неприменённого журнала.
// Переключение при аварии - отдельная тема, и она сложнее, чем кажется. Реплику надо повысить до основной, все приложения должны узнать новый адрес, а старую основную нельзя пускать обратно как есть, иначе получатся две базы, принимающие запись одновременно, с расходящимися данными. Поэтому переключение либо автоматизируют готовым инструментом, который умеет отгонять старую основную, либо делают руками по написанному заранее порядку. Отдельно проговаривают, сколько транзакций потеряется: при асинхронной репликации это те, что не успели доехать.
- отставание реплики
- насколько она позади; меряется и в секундах, и в объёме неприменённого журнала
- повышение реплики
- перевод реплики в режим основной базы, принимающей запись
- две основные разом
- обе принимают запись и расходятся; самое дорогое последствие плохого переключения
Как отвечать: «Синхронная репликация или асинхронная?»
Это выбор между потерей данных и задержкой записи, и формулируется он одним вопросом: сколько подтверждённых транзакций мы готовы потерять при потере основной базы. Если ноль - синхронная. Я мерил её цену: средняя задержка транзакции выросла с 0,55 до 0,96 миллисекунды, а пропускная способность упала с 1800 до 1040 в секунду, и это на соседней машине с идеальной сетью. Но важнее другое, и это я тоже проверял: если единственная синхронная реплика падает, запись на основной базе просто встаёт, причём ограничение времени запроса эту ситуацию не прерывает - я ждал восемь секунд при ограничении в пять, и запрос всё ещё висел. Поэтому синхронную схему делают минимум с двумя репликами и подтверждением от любой одной. Если потеря нескольких последних транзакций допустима, беру асинхронную и слежу за отставанием.
Ответ формулирует выбор через потерю данных, подкрепляет цену числами и называет режим отказа. Про невозможность прервать ожидание знают только те, кто это ловил.
На чём валятся
- −Ставят одну синхронную реплику и получают полную остановку записи при её падении.
- −Рассчитывают, что ограничение времени запроса спасёт от зависшего ожидания реплики.
- −Читают с реплики то, что сами только что записали, и показывают пользователю старые данные.
- −Забывают про слот отставшей реплики: он удерживает журнал, пока не кончится диск.
- −Не готовят порядок переключения заранее и в аварию получают две базы, принимающие запись.
Проверьте себя
Пять вопросов из банка по этой подтеме. Всего их 7, остальные разбираются в тренажёре.
- Чтение перевели на реплики, и пользователи стали жаловаться: сохранённые изменения пропадают из формы. Почему?A)Реплика отдаёт данные из своего кэша и не видит новых строкB)Балансировщик отправляет чтение и запись на разные базыC)Запись ушла на лидера, а чтение попало на реплику с отставаниемD)Пул соединений отдал сессию с закрытой транзакцией
показать ответ и разбор
+C)Запись ушла на лидера, а чтение попало на реплику с отставанием// разбор: Асинхронная реплика отстаёт: секунды в норме, десятки секунд под нагрузкой или при долгой транзакции. Пользователь сохранил запись на лидере и тут же прочитал её с реплики, где изменений ещё нет. Лечится маршрутизацией: критичные по свежести чтения (сразу после записи, профиль, корзина) идут на лидера, остальное — на реплики. Плюс мониторинг отставания с алертом и отключение реплики из балансировки при превышении порога.
- Автопереключение подняло реплику лидером, а старый лидер вернулся в сеть и продолжил принимать запись. Как это предотвращают?A)Ручным подтверждением переключения дежурным инженеромB)Кворумом в хранилище состояния и отключением старого лидераC)Запретом автоматического переключения на уровне балансировщикаD)Синхронной репликацией: она исключает такие ситуации
показать ответ и разбор
+B)Кворумом в хранилище состояния и отключением старого лидера// разбор: Раздвоение лидера возникает, когда решение принимается без общего согласия. Потому кластеры вроде Patroni хранят состояние в системе с кворумом (etcd, Consul), лидером становится тот, кто удержал аренду, а узел, потерявший её, обязан демотировать себя сам — вплоть до остановки службы. Отдельно настраивают отсечение (fencing): отключение адреса или машины, чтобы отставший узел не принимал клиентов.
- Чтение с реплик добавили, чтобы разгрузить базу. Помогают ли реплики масштабировать и запись тоже?A)Да, запись распределяется по всем репликам поровнуB)Нет: реплики масштабируют чтение, запись идёт только на лидераC)Да, если включить синхронную репликацию на всехD)Да, реплики умеют принимать запись по очереди
показать ответ и разбор
+B)Нет: реплики масштабируют чтение, запись идёт только на лидера// разбор: Классическая репликация лидер-реплика масштабирует только чтение: реплики отдают SELECT'ы и снимают нагрузку с лидера, но всю запись принимает единственный лидер, а реплики лишь применяют его изменения. Значит, потолок записи упирается в один узел. Чтобы масштабировать запись, нужны другие подходы: шардирование (разбить данные по узлам), разнесение нагрузки по доменам, иногда мультилидер (со своими сложностями конфликтов). Реплики эту задачу не решают.
- Иногда данные на реплике заметно отстают от лидера. Отчего растёт лаг репликации и чем это грозит?A)От числа индексов на реплике, лаг чинят их удалениемB)Только от версии СУБД, лечится обновлениемC)От размера пула соединений на лидереD)От всплесков записи и нагрузки на реплике
показать ответ и разбор
+D)От всплесков записи и нагрузки на реплике// разбор: Лаг репликации — задержка между тем, как изменение применено на лидере и на реплике. Растёт при всплесках записи (реплика не успевает применять поток), долгих запросах/блокировках на самой реплике (конфликт применения), медленной сети, слабом диске реплики. Последствия: устаревшее чтение (read-your-writes ломается) и риск потери данных при failover, если продвинуть отстающую реплику. Поэтому лаг мониторят и учитывают при маршрутизации чтения и при выборе кандидата на промоушен.
- При аварийном переключении на асинхронную реплику потеряли последние подтверждённые записи. Почему и чем это регулируют?A)Async коммит подтверждают до реплики (RPO>0)B)Реплика повредилась при переключении, это сбой промоушенаC)Потеря из-за отсутствия бэкапа, добавьте ночной дампD)Клиент не повторил запросы, надо включить ретраи
показать ответ и разбор
+A)Async коммит подтверждают до реплики (RPO>0)// разбор: При асинхронной репликации лидер подтверждает клиенту коммит, не дожидаясь реплики. Если лидер внезапно падает, изменения, ещё не доехавшие до реплики, теряются при её промоушене — это ненулевой RPO (окно возможной потери). Регулируют выбором режима: синхронный коммит (ждать подтверждения реплики) даёт RPO≈0, но добавляет задержку каждой записи и зависит от доступности реплики. Компромисс — кворумная/полусинхронная репликация. Это осознанный размен «потеря данных против задержки».
дальше
Теорию прочитали. Навык ставится повторением
В Сеньорчике эта подтема идёт в ежедневных сессиях: движок возвращает её, пока ответы не станут уверенными, и ведёт прогресс отдельно по каждой подтеме. Теория внутри тоже бесплатна, лимит только на количество вопросов в день.