Сергей Бурладян (Avito)
Всем привет, меня зовут Сергей Бурладян, я работаю на Авито администратором базы данных.
Я работаю со следующими системами:
Это наша центральная база данных 2 ТБ, 4 сервера - 1 мастер, 3 резервных.
Еще у нас есть логическая репликация на основе лондисте (это от Skytools), внешний сфинкс-индекс, различные загрузки во внешние системы — такие как DWH, например.
Также у нас есть собственные разработки в области удаленного вызова процедур, так называемый xrpc. Хранилище на 16 баз.
И еще цифра, что наш бэкап занимает 6 часов, а его восстановление около 12. Хотелось бы, чтобы в случае различных сбоев этих систем простой нашего сайта занимал не более 10 минут.
Если попытаться представить различные связи этих систем, они выглядят примерно так:
И как не потерять все это в результате аварии?
Какие несчастные случаи могут произойти?
Я в основном рассматриваю аварии с потерей сервера, плюсом для мастера может быть и такая случайность, как взрыв данных.
Давай начнем.
Допустим, какой-то администратор по ошибке сделал обновление не откуда.
Это случалось с нами несколько раз.
Как защититься от этого? Мы защищаем себя, имея резервную систему, которая применяет WAL с 12-часовой задержкой.
Когда произошла такая авария, мы взяли эти данные из режима ожидания и загрузили обратно в мастер.
Вторая авария, которая может случиться с мастером – это потеря сервера.
Мы используем асинхронную репликацию и после потери сервера должны перевести какой-то резервный.
А т.к.
репликация у нас асинхронная, то необходимо выполнять различные другие процедуры по восстановлению связанных систем.
Наш мастер является центральным и является источником данных, поэтому если он переключается, а репликация асинхронная, то мы теряем часть транзакций, и получается, что часть системы находится в недостижимом будущем для нового мастера.
Вручную все это сделать сложно, поэтому нужно сразу делать скриптом.
Как выглядит авария? Во внешних системах появляются объявления, которых уже нет на мастере, сфинкс при поиске выдает несуществующие объявления, последовательности отскакивали, логические реплики, в частности из-за этого, тоже перестали работать (londiste).
Но не все так плохо, все можно восстановить.
Мы посидели, подумали и спланировали процедуру восстановления.
В частности, мы можем просто снова выгрузить DWH. И непосредственно, т.к.
если у нас простой в 10 минут, то в ежемесячных отчетах изменение этих потерянных элементов просто не видно.
Как восстановить xrpc? Мы используем xrpc для геокодирования, для вызова асинхронных процедур на мастере и для расчета кармы пользователя.
Соответственно, если мы что-то геокодировали, т.е.
из адреса мы превратили это в координаты на карте, а потом этот адрес исчез, то ничего страшного, что оно останется геокодированным, просто второй раз мы не будем геокодировать один и тот же адрес, соответственно, восстанавливать ничего не нужно.
Вызов локальной процедуры асинхронен, потому что он локальный, он находится на одном и том же сервере базы данных, даже в одной и той же базе данных, и поэтому, когда мы переключали базу данных, он консистентен.
Также не нужно ничего восстанавливать.
Карма пользователя.
Мы решили, что если пользователь сделал что-то плохое, а потом произошел несчастный случай и мы потеряли эти плохие предметы, то карму пользователей тоже не нужно восстанавливать.
Он сделал эти плохие вещи, пусть они останутся с ним.
Сайт Сфинкса.
У нас есть два сфинкса — один для сайта, другой для бэк-офиса.
Sphinx, на котором работает сайт, реализован таким образом, что полностью перестраивает весь свой индекс каждые 10 минут. Соответственно, произошла авария, они восстановились, и через 10 минут индекс полностью восстановился и соответствовал мастеру.
А по поводу бэкофиса мы решили, что это тоже не критично, мы можем обновить часть изменившейся после восстановления рекламы, и плюс раз в месяц полностью пересобираем весь бэкофис сфинкса, и все эти аварийные элементы будут почищены.
Как восстановить последовательности, чтобы они не отскакивали? Мы просто выбирали важные для нас последовательности, такие как item_id, user_id, первичный ключ платежа, и после аварии прокручивали вперед тысяч на 100 (решили, что нам этого будет достаточно).
Восстанавливаем логическую репликацию с помощью нашей системы, это патч для londiste, который делает UNDO для логической реплики.
Patch Undo — это три команды.
Сама команда и две команды отмены добавления/удаления для логической реплики.
А еще в реплее в лондисте мы добавили флаг, чтобы он передавал TICK_ID от мастера в переменную сеанса Postgres.
Это нужно непосредственно в самой реализации Undo, потому что она реализована — просто триггеры по всем таблицам подписчиков.
Триггер записывает в таблицу истории, какая именно операция произошла.
В целевой таблице.
Он помнит этот Ticket_id, переданный мастером в этой записи.
Соответственно, когда произошла авария, логическая реплика оказалась в будущем, и ее необходимо очистить, чтобы восстановить изменения, которые находятся из недостижимого будущего.
Это делается путем выполнения обратных запросов, т. е.
для вставки мы удаляем, для обновления мы обновляем предыдущие значения, а для удаления мы вставляем.
Мы не делаем все это вручную, мы делаем это с помощью скрипта.
Что особенного в нашем сценарии? У нас есть три асинхронных резервных сервера, поэтому перед переключением нам нужно выяснить, какой из них ближе всего к мастеру.
Далее выбираем этот резервный, ждем, пока он проиграет оставшиеся WAL из архива, и выбираем его в качестве будущего мастера.
Далее мы используем Postgres 9.2. Особенность этой версии в том, что для того, чтобы резервный переключился на новую акцию и мастер, их необходимо остановить.
По идее в 9.4 этого сделать уже нельзя.
Соответственно, мы продвигаем, перемещаем последовательности вперед, выполняем процедуру отмены и запускаем режим ожидания.
И тут еще интересный момент — нужно дождаться, пока standby подключится к новому мастеру.
Мы делаем это, ожидая появления временной шкалы нового мастера на соответствующем резервном сервере.
И оказывается, что в Postgres нет такой функции SQL; в режиме ожидания невозможно понять график.
Но решаем это так: получается, что можно подключиться по протоколу репликации Postgres к резервному, и там после первой команды резервный сообщит свой таймлайн, выделенный красным.
Это наш основной сценарий восстановления.
Пойдем дальше.
Как мы можем напрямую восстановиться, если внешние системы развалятся? Например, режим ожидания.
у нас три дежурных, как я уже сказал, мы просто берем и переключаемся на оставшийся дежурный, если один из них упадет. В крайнем случае, даже если мы потеряем весь режим ожидания, мы можем переключить трафик на мастер.
Здесь будет потеряна часть трафика, но в принципе сайт будет работать.
Здесь была еще одна хитрость — сначала я продолжал создавать новые резервные серверы из резервной копии, затем у нас появились SSD-сервера, и я все равно продолжал восстанавливать резервные серверы из резервной копии.
Потом оказалось, что если брать из бэкапа, то восстановление занимает 12 часов, а если просто взять pg_basebackup из какого-нибудь рабочего резерва, то времени уходит гораздо меньше.
Если у вас несколько резервистов, можно попробовать проверить это у вас.
Если сфинкс сайта сломается.
Сфинкс нашего сайта написан таким образом, что полностью перестраивает весь индекс, а сфинкс сайта - это вся активная реклама сайта.
Сейчас все 30 или 35 миллионов объявлений на сайте индексируются этой системой.
Индексация происходит из отдельной логической реплики, она подготовлена специально для индексации и сделана таким образом, что всё раскладывается в памяти, а индексация происходит очень быстро, поэтому мы можем делать индексацию каждые 10 минут, полностью с нуля.
У нас есть логические реплики в парах.
И если мы теряем реплику, мы переключаемся на ее резерв.
А если со сфинксом что-то случится, то через 10 минут он полностью переиндексируется, и всё будет хорошо.
Как восстановить экспорт в DWH? Допустим, мы что-то экспортировали, в DWH произошла авария, и мы потеряли часть последних данных.
Наш экспорт в DWH проходит через отдельную логическую реплику, и в этой реплике хранятся последние четыре дня.
Мы можем просто вручную снова вызвать скрипт экспорта и загрузить все эти данные.
Плюс еще есть архив на полгода.
Или, на крайний случай, т. к.
у нас несколько резервных серверов, мы можем взять один из них, поставить его на паузу и перезалить, в общем, все данные с мастера в СХД.
У нас Xrpc реализован поверх pgq (это Skytools), и благодаря этому мы можем делать такие хитрые вещи.
Pgq — это, по сути, просто таблица в базе данных, в которой хранятся события.
Выглядит примерно так же, как на картинке.
Есть время события и идентификатор транзакции.
Когда мы восстановили клиент xrpc, мы можем вернуться в эту очередь и воспроизвести те события, которых нет в получателе.
Xdb — у нас есть хранилище из нескольких баз.
16 баз расположены на восьми машинах.
Резервируем это хранилище следующим образом — просто настраивается бинарная репликация Postgres с одной машины на другую.
Те.
Первая машина зарезервирована в резерве у второй, вторая у третьей, а восьмая у первой.
Кроме того, при воспроизведении WAL тоже есть задержка в четыре дня, т. е.
фактически бэкап любой из этих нод мы имеем за четыре дня.
Теперь я вам подробно расскажу о реплике, что это такое.
Наша логическая реплика построена на основе возможностей Postgres; у него есть представление по мастеру и отложенный триггер по необходимым таблицам.
Эти триггеры запускают специальную функцию, которая записывает данные в отдельную таблицу.
Его можно рассматривать как материализованное представление.
А потом этот планшет реплицируется в логическую репу с помощью лондиста.
Непосредственно это выглядит примерно так, подробно останавливаться на этом не буду.
А сам сервер логической реплики, зачем он вообще нужен? Это отдельный сервер.
Характеризуется тем, что все, что есть в памяти, т. е.
shared_buffers, имеет такой размер, что вся эта таблица и ее индексы полностью вмещаются в нее.
Это позволяет таким логическим репликам обслуживать большую нагрузку, в частности, например, одна реплика обслуживает 7000 транзакций в секунду, и в нее от мастера ставится в очередь 1000 событий.
это логическая реплика, реализованная с помощью londiste и pgq, поэтому там есть удобная штука — отслеживание того, какие транзакции уже потеряны на этой логической реплике.
И на основе этой вещи вы можете делать такие вещи, как «Отменить».
Я уже говорил, что у нас две реплики, восстановиться можно простым переключением.
Если одна реплика потеряна, переключаемся на вторую.
Это возможно, поскольку pgq позволяет нескольким потребителям подписываться на одну и ту же очередь.
Репа упала, и тогда нам нужно восстановить ее копию.
Если сделать это просто средствами лондисте, то на сайт-репу у нас теперь уходит 4 часа, а на сфинкс - 8 часов, т.к.
там вызываются триггеры, которые срезают данные для удобной индексации Сфинксом, а это все очень долго .
Но оказалось, что есть другой способ создать опавшую репу — можно сделать pg_dump с рабочей.
Но если просто сделать pg_dump и запустить на нем londiste, то это не сработает, потому что londiste отслеживает и мастером, и логической репликой текущую позицию потерянной транзакции.
Поэтому там еще необходимо предпринять дополнительные шаги.
После восстановления дампа на мастере нужно поправить тик_ид, чтобы он совпадал с тик_идом, который есть на восстановленной репе.
Если да, скопируйте через pg_dump, тогда все это займет не более 15 минут.
Сам алгоритм выглядит примерно так.
Резервное копирование предназначено для защиты от аварий, но аварии могут произойти и непосредственно с самой резервной копией.
Например, в Postgres команда архивирования WAL не указывает, что должна делать fsync, когда WAL записывается в архив.
А ведь это важная вещь и позволяет обезопасить себя, скажем, от аварийной перезагрузки архива.
Кроме того, наша резервная копия еще и подкреплена тем, что копируется во внешнее облако.
Но в наших планах: мы хотим сделать два активных архивных сервера, чтобы archive_command писала в оба WAL. Еще можно сказать, что сначала мы экспериментировали с pg_receivexlog для того, чтобы получать непосредственно на сами архивные серверы WAL, но оказалось, что в 9.2 его практически невозможно использовать, потому что он не выполняет fsync, не отслеживает какой у него WAL уже получен от мастера, который можно почистить на КПП.
Теперь это завершено в Postgres. И, возможно, в будущем мы будем использовать не archive_command, а все-таки pg_receivexlog.
Мы сами не используем стриминг.
Те.
То, о чем я говорил, все основано только на архиве WAL. Это сделано из-за того, что сложно предоставить архив при потоковой передаче, потому что если, например, взять архив из режима ожидания, резервное копирование завершено, а мастер еще не успел заархивировать все эти нужные WAL для восстановления резервной копии.
И получаем битый бэкап.
Это можно обойти, если, например, наш резервный сервер, с которого мы берем бэкап, отстает на 12 часов, как и наш.
Или в Postgres 9.5 был установлен параметр archive_mode=always, который предотвратил бы эту проблему.
Можно будет легко сделать резервную копию из режима ожидания и получить WAL прямо из режима ожидания в архив.
Недостаточно просто сделать резервную копию; вам также необходимо проверить, все ли резервное копирование выполнено правильно.
Мы делаем это на тестовом сервере и для этого написали специальный скрипт проверки бэкапа.
Он основан на том, что он проверяет после восстановления сервера и выводит сообщения об ошибках в журнал сервера.
Причем для каждой восстановленной на кластере базы данных вызывается специальная проверочная функция check_backup, которая выполняет дополнительные проверки.
В частности, такая проверка заключается в том, что дата последней транзакции должна отличаться от даты последнего объявления не более чем на минуту.
Те.
если дыр нет, считаем, что бэкап восстановился корректно.
На слайдах вы можете увидеть, какие конкретно ошибки мы анализируем в журнале при проверке бэкапа.
Раньше мы проверяли резервные копии, очищая всю базу данных и читая таблицы, но потом решили отказаться от этого, поскольку отчеты мы все равно считаем из восстановленной резервной копии, и если отчеты были рассчитаны правильно, в них нет дыр или странных значений, то резервная копия была сделана правильно.
Я говорил об асинхронной репликации, но иногда хочется сделать ее синхронной.
Наш Авито состоит из множества сервисов, один из таких сервисов – платежный сервис.
А за счет того, что он выделен, мы можем делать для него синхронную репликацию, ведь он работает на отдельной базе.
Там не такая большая нагрузка и стандартная латентность сети позволяет включить там синхронную репликацию.
Что мы можем сказать в конце? Все-таки, несмотря на то, что репликация синхронная, работать и восстанавливаться можно в этом режиме, если посмотреть на свои подключенные системы, то можно понять, как их можно восстановить.
Также важно протестировать резервные копии.
Еще одно замечание.
У нас есть скрипт восстановления, в конце его надо поменять ДНС, потому что.
у нас есть мастер или слейв - это зафиксировано в ДНС.
Сейчас мы думаем об использовании какой-нибудь системы вроде ZooKeeper для автоматического переключения DNS. Такие планы.
Данный доклад представляет собой стенограмму одного из лучших выступлений на конференции разработчиков высоконагруженных систем.Теги: #Системное администрирование #Резервное копирование #Администрирование сервера #резервные копии #highload #avito #Сергей Бурладян #Восстановление данныхСейчас мы активно готовимся к конференции 2016 года — в этом году HighLoad++ пройдет в Сколково 7 и 8 ноября.
Команда Авито традиционно показывает очень сильные показатели, например, в этом году это будут:
Некоторые из этих материалов мы также используем в нашем онлайн-курсе по разработке высоконагруженных систем.
- Опыт миграции между дата-центрами / Михаил Тюрин
- Индексы Sphinx 3.0 и RT на основном поиске Авито / Андрей Смирнов, Вячеслав Крюков;
HighLoad.Руководство — это цепочка специально отобранных писем, статей, материалов, видео.
Наш учебник уже содержит более 30 уникальных материалов.
Соединять!
-
Вебмани Мини Для Linux
19 Oct, 24 -
Xss: Радужный Твиттер Выпущен!
19 Oct, 24 -
Цепная Кайна
19 Oct, 24