сеньорчикОткрыть в Telegram
← вся теориятеория к собесу · Базы и брокеры в проде

Репликация базы и переключение лидера

Репликация и переключение

Поднял реплику из копии основной базы и проверил три вещи. Первая: попытка записать на реплику даёт «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, остальные разбираются в тренажёре.

  1. #dvo_db_replication1 / 5
    Чтение перевели на реплики, и пользователи стали жаловаться: сохранённые изменения пропадают из формы. Почему?
    A)Реплика отдаёт данные из своего кэша и не видит новых строк
    B)Балансировщик отправляет чтение и запись на разные базы
    C)Запись ушла на лидера, а чтение попало на реплику с отставанием
    D)Пул соединений отдал сессию с закрытой транзакцией
    показать ответ и разбор
    +C)Запись ушла на лидера, а чтение попало на реплику с отставанием

    // разбор: Асинхронная реплика отстаёт: секунды в норме, десятки секунд под нагрузкой или при долгой транзакции. Пользователь сохранил запись на лидере и тут же прочитал её с реплики, где изменений ещё нет. Лечится маршрутизацией: критичные по свежести чтения (сразу после записи, профиль, корзина) идут на лидера, остальное — на реплики. Плюс мониторинг отставания с алертом и отключение реплики из балансировки при превышении порога.

  2. #dvo_db_replication2 / 5
    Автопереключение подняло реплику лидером, а старый лидер вернулся в сеть и продолжил принимать запись. Как это предотвращают?
    A)Ручным подтверждением переключения дежурным инженером
    B)Кворумом в хранилище состояния и отключением старого лидера
    C)Запретом автоматического переключения на уровне балансировщика
    D)Синхронной репликацией: она исключает такие ситуации
    показать ответ и разбор
    +B)Кворумом в хранилище состояния и отключением старого лидера

    // разбор: Раздвоение лидера возникает, когда решение принимается без общего согласия. Потому кластеры вроде Patroni хранят состояние в системе с кворумом (etcd, Consul), лидером становится тот, кто удержал аренду, а узел, потерявший её, обязан демотировать себя сам — вплоть до остановки службы. Отдельно настраивают отсечение (fencing): отключение адреса или машины, чтобы отставший узел не принимал клиентов.

  3. #dvo_db_replication3 / 5
    Чтение с реплик добавили, чтобы разгрузить базу. Помогают ли реплики масштабировать и запись тоже?
    A)Да, запись распределяется по всем репликам поровну
    B)Нет: реплики масштабируют чтение, запись идёт только на лидера
    C)Да, если включить синхронную репликацию на всех
    D)Да, реплики умеют принимать запись по очереди
    показать ответ и разбор
    +B)Нет: реплики масштабируют чтение, запись идёт только на лидера

    // разбор: Классическая репликация лидер-реплика масштабирует только чтение: реплики отдают SELECT'ы и снимают нагрузку с лидера, но всю запись принимает единственный лидер, а реплики лишь применяют его изменения. Значит, потолок записи упирается в один узел. Чтобы масштабировать запись, нужны другие подходы: шардирование (разбить данные по узлам), разнесение нагрузки по доменам, иногда мультилидер (со своими сложностями конфликтов). Реплики эту задачу не решают.

  4. #dvo_db_replication4 / 5
    Иногда данные на реплике заметно отстают от лидера. Отчего растёт лаг репликации и чем это грозит?
    A)От числа индексов на реплике, лаг чинят их удалением
    B)Только от версии СУБД, лечится обновлением
    C)От размера пула соединений на лидере
    D)От всплесков записи и нагрузки на реплике
    показать ответ и разбор
    +D)От всплесков записи и нагрузки на реплике

    // разбор: Лаг репликации — задержка между тем, как изменение применено на лидере и на реплике. Растёт при всплесках записи (реплика не успевает применять поток), долгих запросах/блокировках на самой реплике (конфликт применения), медленной сети, слабом диске реплики. Последствия: устаревшее чтение (read-your-writes ломается) и риск потери данных при failover, если продвинуть отстающую реплику. Поэтому лаг мониторят и учитывают при маршрутизации чтения и при выборе кандидата на промоушен.

  5. #dvo_db_replication5 / 5
    При аварийном переключении на асинхронную реплику потеряли последние подтверждённые записи. Почему и чем это регулируют?
    A)Async коммит подтверждают до реплики (RPO>0)
    B)Реплика повредилась при переключении, это сбой промоушена
    C)Потеря из-за отсутствия бэкапа, добавьте ночной дамп
    D)Клиент не повторил запросы, надо включить ретраи
    показать ответ и разбор
    +A)Async коммит подтверждают до реплики (RPO>0)

    // разбор: При асинхронной репликации лидер подтверждает клиенту коммит, не дожидаясь реплики. Если лидер внезапно падает, изменения, ещё не доехавшие до реплики, теряются при её промоушене — это ненулевой RPO (окно возможной потери). Регулируют выбором режима: синхронный коммит (ждать подтверждения реплики) даёт RPO≈0, но добавляет задержку каждой записи и зависит от доступности реплики. Компромисс — кворумная/полусинхронная репликация. Это осознанный размен «потеря данных против задержки».

дальше

Теорию прочитали. Навык ставится повторением

В Сеньорчике эта подтема идёт в ежедневных сессиях: движок возвращает её, пока ответы не станут уверенными, и ведёт прогресс отдельно по каждой подтеме. Теория внутри тоже бесплатна, лимит только на количество вопросов в день.