Базы данных PostgreSQL занимают одно из центральных мест в Авито. Они являются разделяемой платформой, вокруг которой построено множество дополнительных сервисов. Одной из основных задач при их администрировании является задача восстановления после аварий как самих баз, так и связанной с ними инфраструктуры.
В своём докладе я постараюсь рассказать про:
+ общую схему связей баз данных между собой и с другими компонентами;
+ точки отказа и виды аварий, затрагиваемые связи;
+ бинарную репликацию и архив;
+ логическую репликацию, pgq, londiste, UNDO (REDO), пересоздание репки;
+ скрипт и процедуру переключения при аварии;
+ планы: развитие «восстановлений» по всем связям, автоматика на основе системы zookeeper (etcd и т.п.).
7. Master
promote создаёт на новом мастере параллельную ветвь
времени, а все остальные остаются в старой
• в DWH объявления, которых нет
• xrpc отработал для несуществующих данных
• sphinx выдаёт несуществующие объявления
• sequences начались снова с момента аварии
• логические реплики не работают
8. Master
Не всё так плохо :-)
• выгрузить в DWH промежуток аварии с запасом в час
снова
– 10 минут — мало изменят отчёты, можно
игнорировать
• xrpc, повезло:
– геокодинг
– локально для асинхронных вызовов на мастере
– карма
– TODO, "обратные" функции, триггеры типа undo
londiste
9. Master
Не всё так плохо :-)
• sphinx
– сайта, через 10 минут создан с нуля
– backoffice, не критично, зарефрешить последний час,
пересоздаётся с нуля раз в месяц
• sequences, открутить важные вперёд на 100 000
– item_id
– user_id
– order_id
• UNDO для логических реплик
10. Undo
• subscriber undo [TICK_ID]
– rewind all tables with it UNDO log
• subscriber add-undo-all
– enable UNDO log on all tables
• subscriber remove-undo-all
– disable UNDO log on all tables
• --enable-undolog
– replay generate UNDO log
11. Undo
• триггер на таблицах subscriber
• undo_log таблица с 'I,'U','D', hstore и tick_id
• получение tick_id с мастера
• все строки во всех наблюдаемых таблицах с tick_id
больше мастера — отменить
– INSERT -> DELETE
– UPDATE -> UPDATE
– DELETE -> INSERT
• londiste.completed: last_tick_id = master tick_id
12. Скрипт переключения
• 3 standby — выбираем ближайший к мастеру
• останавливаем другие standby
– pg9.2, pg9.4 можно не перезапускать
• promote
• сдвиг sequences вперёд
• londiste undo
• запускаем standby
• ждём новый timeline на standby
• обновление в DNS
13. Скрипт переключения
• ждём новый timeline на standby
– нет SQL функции
– протокол репликации:
psql -h "$host" -F' ' -c 'IDENTIFY_SYSTEM'
'dbname=replication replication=true'
systemid | timeline | xlogpos
---------------------+----------+------------
6080348884119699418 | 1 | 1/4E0E42D0
14. Standby
• 3 standby
• всё на мастер с потерей части трафика
• быстрое создание нового
– из архива (1 Gbp, NFS HDD) — 12 часов
– с соседнего standby pg_basebackup (10 Gbps, SSD,
мало WAL применять) — 4-6 часа
15. Sphinx сайта
• отдельная реплика для индексации
• данные подготовлены и разложены для быстрой
вычитки в sphinx
• реплика: все данные в shared_buffers
• полная вычитка всех активных объявлений и
перестроение всего индекса каждые 10 минут
• через 10 минут — все индексы готовы заново!
16. Экспорт в DWH
• отдельная реплика со всеми дельтами всех
объявлений за последние 4 дня
– ручной запуск экспорта с указанием промежутка
времени
• архив за полгода
• в крайнем случае — pause standby и заново выгрузить
все объявления
17. Базы-клиенты xrpc
• так как xrpc поверх pgq — можно заново проиграть
(redo) часть событий на восстановленной базе
19. Реплика
• на основе возможностей postgres
– view
– deferred триггер
• подготавливаются данные на мастере в таблице
• londiste реплицирует готовые данные в реплику
20. Реплика
• мастер: набор таблиц с данными для выдачи на сайте
• deferred триггер на каждой, вызывает refresh функцию
• view site_data_v с join по item_id
• таблица site_data_m
• refresh функция:
– блокировка item_id
– delete from site_data_m where item_id = i_item_id
– insert into site_data_m select * from site_data_v where
item_id = i_item_id
• londiste на site_data_m
21. Реплика
• отдельный сервер (* N)
• shared_buffers = sizeof(site_data_m + indexs)
• всё в памяти, fsync=off
• 7000 TPS чтение
• 1000 событий в сек. в очередь на мастере
• londiste (pgq): учёт транзакций, tick_id
22. Авария реплики
• pgq — два или больше потребителя одной очереди
• переключаемся на копию
• упавшую — копируем с рабочей
• заново инициализировать — долго
– реплика сайта — 4 часа
– реплика индексации — 8 часов
• делаем копию с оставшейся реплики
24. Авария реплики
– pgqadm ticker.conf unregister ОЧЕРЕДЬ 'название londiste
consumer'
– pgqadm ticker.conf register ОЧЕРЕДЬ 'название londiste consumer'
– pgqadm ticker.conf status; ждём Lag больше, чем у копии
1) pg_dump -Fc -h prod_host --serializable-deferrable dst_db
pg_restore -Fc -d dst_db -j10
2) dst: select * from londiste.completed
3) src: select * from pgq.consumer where co_name = 'название londiste
consumer'
4) src: select * from pgq.subscription where sub_consumer = 'id из (3)'
5) src: update pgq.subscription set sub_last_tick = 'tick_id из (2)',
sub_batch = null, sub_next_tick = null where sub_consumer = 'id из
(3)'
25. Backup
• fsync в archive_command
– сейчас — копия в облаке
• в планах: два сервера
– archive_command на оба
– restore_command с двух
– pg9.2 pg_receivexlog не работает
●
как защититься от удаления WAL при checkpoint?
(в 9.4 сделали --slot)
●
не делает fsync (в 9.5 сделали --synchronous)
●
не проверяет целостность при запуске
26. Backup и streaming
• в архив пишет master (archive_command), а копия идёт
со standby
• backup завершился, а master не успел отправить WAL в
архив = битый backup
– pg9.2 не используем streaming :-)
– pg9.2 12 часов задержка backup сервера
– pg9.5 archive_mode=always, пишем в архив со
standby!
27. Backup, проверка
скрипт для проверки, распаковка на тестовый сервер:
• запуск кластера, recovery.conf: recovery_end_command
• ждём RECOVERY_END, перезапуск для log: 'end-of-
recovery checkpoint', 'database system is ready'
• анализ csvlog файла, пропускаем SQLSTATE '57P03' # 'the
database system is starting up'
– есть только severity: LOG
– для каждой базы вызываем: select
check_backup(last_txtime)
●
например, create_time - last_txtime < 1 минута
28. Backup, проверка
redo_start = backup_label: START WAL LOCATION
recovery_end = pg_controldata: Minimum recovery ending location
• сообщения, анализируемые в log файле
– 'redo starts at ([0-9A-F/]+)': redo_start
– 'consistent recovery state reached at ([0-9A-F/]+)':
recovery_end
– 'redo done at ([0-9A-F/]+)'
– 'last completed transaction was at log time ([0-9-]+ [0-
9:.]++[0-9]+)': last_txtime
– 'archive recovery complete'
– 'database system is ready to accept connections'
• vacuum базы, sec scan, index scan
– отказались, отчёты по восстановленной базе
30. • можно восстанавливаться и при асинхронной
репликации
• недостаточно восстановить postgres, нужно думать о
связанных системах
– логическая репликация
– внешние индексы (sphinx)
– экспорт данных (DWH и т.п)
• тестирование резервных копий