kai.kzLab

Сбой GitLab 2017: как удалили рабочую базу и почему не сработал ни один бэкап

Команда KAI Lab · · 10 мин чтения

31 января 2017 года около 23:30 UTC инженер GitLab.com, чиня сломанную репликацию, по ошибке удалил каталог данных PostgreSQL не на резервном, а на основном сервере — пропало около 300 ГБ. Бэкапы не спасли: ежедневный pg_dump падал из-за разницы версий 9.2 и 9.6, письма об ошибках не доходили, снимки дисков для серверов баз не были включены, и базу подняли из LVM-снимка шестичасовой давности.

Потеряны изменения в базе примерно за шесть часов: около 5 000 проектов, 5 000 комментариев и 700 новых аккаунтов. Git-репозитории и вики не пострадали. База вернулась только к вечеру следующего дня, и всё восстановление GitLab транслировала на YouTube.

Главное о сбое в одной таблице

ВопросОтвет
Когда31 января 2017, около 23:30 UTC
Что случилосьКаталог данных PostgreSQL удалён на основном сервере вместо резервного
Сколько удаленооколо 300 ГБ; команду остановили через секунду-две, осталось около 4,5 ГБ
Чем восстанавливалиLVM-снимком, сделанным около 17:20 UTC для staging
Что потеряноизменения в базе с 17:20 до 00:00 UTC: около 5 000 проектов, 5 000 комментариев, 700 новых аккаунтов
Что не пострадалоGit-репозитории, вики, самостоятельно установленные GitLab
Когда вернулисьбаза — 1 февраля в 17:00 UTC, финальные шаги — около 18:00 UTC
Почему так долгокопирование со staging на прод шло около 18 часов: сетевые диски были ограничены примерно 60 Мбит/с

Хронология сбоя GitLab

Время (UTC)Что произошло
31 января, ~17:20Инженер делает LVM-снимок рабочей базы и загружает его в staging
~19:00Нагрузка на базу растёт — по версии GitLab, из-за спама
~23:00Реплика отстаёт, репликация ломается: нужные ей сегменты WAL основной сервер уже удалил
23:00–23:30Попытки пересоздать реплику: pg_basebackup висит без вывода, правятся max_wal_senders и max_connections (было 8000, стало 2000)
~23:30Инженер удаляет каталог данных PostgreSQL, думая, что он на резервном сервере. Это основной. Через секунду-две он замечает и останавливает команду
Сразу послеБэкапов pg_dump нет: бакет S3 пуст. Решено поднимать базу из LVM-снимка 17:20 — альтернатива означала потерять почти сутки
1 февраля, 17:00База GitLab.com восстановлена, пока без webhooks
~18:00Webhooks восстановлены из отдельной копии, восстановление закончено
10 февраляОпубликован постмортем с разбором причин и 15 задачами на исправление

Как всё началось: нагрузка, спам и сломанная репликация

Удаление базы — последний шаг цепочки. Разбор «пять почему» в постмортеме начинается раньше.

  • Нагрузка на базу выросла из-за двух событий сразу: спама и процесса, который удалял аккаунт сотрудника GitLab со всеми данными. Аккаунт пометили как нарушителя после жалобы тролля, а система обработки жалоб позволяла легко пропустить детали.
  • Под нагрузкой основной сервер удалил сегменты журнала WAL раньше, чем реплика успела их получить. Репликация остановилась, реплику пришлось пересоздавать с нуля.
  • pg_basebackup не мог подключиться: на основном сервере не хватало подключений для репликации (max_wal_senders). После правок настроек PostgreSQL не перезапускался из-за слишком большого max_connections. Когда и это починили, pg_basebackup просто висел.
  • Инженер решил, что утилите мешает непустой каталог данных на реплике, и стал его очищать. В первом отчёте GitLab пишет, что он как раз собирался заканчивать работу, потому что было уже поздно.

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

Пять механизмов бэкапа, и ни один не сработал

В первом отчёте 1 февраля GitLab написала: из пяти развёрнутых механизмов бэкапа и репликации ни один не работает надёжно или вообще не настроен. Поимённо GitLab их не перечисляет. По постмортему картина такая.

МеханизмКак должен был работатьЧто оказалось
pg_dump раз в сутки с загрузкой в S3ежедневная логическая копия базыЗапускался на сервере приложения, где нет каталога данных PostgreSQL. Поэтому Omnibus брал pg_dump 9.2, а база была на 9.6, и дамп падал с ошибкой. Бакет S3 оказался пуст
Уведомления об ошибках cronписьмо, если задача упалаПисьма не были подписаны DMARC, и принимающий сервер их отклонял. О падении бэкапов никто не знал
Снимки дисков в Azureснимок диска целикомВключены для NFS-серверов, но не для серверов баз: считалось, что остальных бэкапов хватит
LVM-снимки раз в суткиснимок раздела с базойНе предназначались для восстановления после аварии. Плановый был почти суточной давности
Репликация на второй сервергорячая копия для переключенияНужна для отказоустойчивости, а не для восстановления. Сломалась под нагрузкой, а её данные стёрли при попытке её починить

Спас снимок, который не был бэкапом. В 17:20 инженер сделал LVM-снимок, чтобы загрузить свежие данные в staging. Из него и подняли прод, потеряв изменения за шесть с лишним часов.

Почему восстановление растянулось на 18 часов

Данные пришлось копировать со staging на прод. Staging работал в классическом Azure без Premium Storage, сетевые диски были ограничены примерно 60 Мбит/с, и копирование заняло около 18 часов. Проблема была в дисках, а не в сети или серверах.

Ещё одна деталь: при синхронизации в staging из базы удаляются webhooks, чтобы копия случайно не дёргала внешние сервисы. Поэтому базу сначала подняли без них, а webhooks потом достали из отдельной копии.

Всё это время GitLab транслировала восстановление на YouTube: пик — около 5 000 зрителей.

Уроки для DevOps

1. Бэкап, который никто не восстанавливал, — не бэкап

У GitLab было пять механизмов и ни одного проверенного. В списке исправлений есть автоматическое тестирование восстановления из бэкапов PostgreSQL. Минимальная версия для себя: раз в неделю поднимать копию из свежего бэкапа на отдельной машине и проверять, что данные на месте — число строк, дата последней записи.

2. Мониторить сам бэкап, а не полагаться на письма

Похожий урок — у Atlassian в 2022 году: скрипт удалил сайты клиентов, и у части клиентов восстановление заняло до двух недель: процесс был рассчитан на восстановление одного сайта, а не сотен сразу (разбор).

Письма от cron — ненадёжный канал: их режут фильтры, их не читают. GitLab записала в задачи мониторинг бэкапов в Prometheus. Что проверять метрикой:

  • возраст последнего успешного бэкапа;
  • размер файла: пустой архив в несколько байт — тоже сбой;
  • код возврата всей цепочки, а не последней команды.
#!/bin/bash
set -euo pipefail                  # падать при любой ошибке, в том числе внутри пайпа
pg_dump orders | gzip > "$FILE"    # без pipefail код возврата был бы от gzip
test -s "$FILE"                    # пустой файл — ошибка

3. Версии инструментов бэкапа — часть конфигурации

pg_dump 9.2 против базы 9.6 — классическая тихая ошибка после обновления. Утилита бэкапа должна быть той же или более новой версии, чем сервер, и запускаться там, где это гарантировано. Обновили базу — проверьте бэкап в тот же день.

4. Репликация — не бэкап

Реплика копирует всё, включая ошибки и удаления. Для защиты от «удалили руками» нужно восстановление на момент времени: архив журналов WAL плюс базовые копии. GitLab поставила себе задачу изучить point-in-time recovery и инструменты WAL-E и pgbarman. Сегодня для этого чаще берут pgBackRest или WAL-G. С ними базу можно вернуть на состояние за минуту до ошибочной команды, а не на снимок шестичасовой давности.

5. Защита от «не тот сервер»

  • Приглашение командной строки на проде другого цвета и с полным именем хоста.
  • Разрушительные операции на базах — по runbook, с проверкой роли сервера перед командой (SELECT pg_is_in_recovery(); на реплике вернёт true).
  • Уставший человек ночью — фактор риска. Сложную операцию лучше передать или делать вдвоём.

6. Скорость восстановления — тоже требование

Даже правильный бэкап бесполезен, если его копирование займёт 18 часов, а бизнес готов ждать час. Знайте своё RTO — допустимое время восстановления — и проверяйте его на реальном объёме данных.

7. У сохранности данных должен быть владелец

Последний пункт в списке GitLab — назначить ответственного за сохранность данных. Когда за бэкапы отвечают «все», на деле не отвечает никто.

8. Разбор без поиска виноватых

В постмортеме инженер не назван по имени, и GitLab отдельно пишет, что в будущих разборах имена будет скрывать. Разбор ищет, почему система позволила одной команде стереть прод и почему пять механизмов защиты не сработали, а не кого наказать. Именно поэтому из него получился список из 15 конкретных задач, а не выговор.

Что проверить у себя завтра:

  • когда вы последний раз восстанавливали базу из бэкапа — не «делали бэкап», а именно восстанавливали;
  • придёт ли алерт, если бэкап не сделается или окажется пустым;
  • совпадает ли версия утилиты бэкапа с версией базы после последнего обновления;
  • можно ли вернуть базу на момент времени, а не только на последний снимок.

Как писать такой разбор, чтобы из него выросли изменения, а не поиск виноватого, — в статьях как писать постмортем и blameless-культура.

Реши сам

Два вопроса из разбора этого сбоя в симуляторе DevOps KAI Lab.

Вопрос 1. Почему ежедневный pg_dump не делал бэкапы, и никто об этом не знал?

  1. Кончилось место в S3
  2. Версия pg_dump не совпадала с версией базы, а письма об ошибках не доходили
  3. Cron был выключен
  4. Бэкапы удалял злоумышленник
Показать ответ

Верный ответ — 2. Задача запускалась, но дамп утилитой 9.2 падал на базе 9.6. Письма об ошибке отклонял принимающий сервер, потому что они не были подписаны DMARC. Ошибка была, но её никто не видел.

Вопрос 2. Какая мера лучше всего предотвратила бы такую потерю?

  1. Запретить работать ночью
  2. Регулярно восстанавливать базу из бэкапа и алертить на провал бэкапа
  3. Добавить шестой механизм бэкапа
  4. Уволить инженера
Показать ответ

Верный ответ — 2. Пять непроверенных механизмов дали ноль рабочих. Один проверенный лучше. Регулярное восстановление показало бы пустой бакет до того, как бэкап понадобился, а метрика — что свежих бэкапов нет.

Частые вопросы

Что случилось с GitLab 31 января 2017 года? Инженер, восстанавливая репликацию, удалил каталог данных PostgreSQL на основном сервере GitLab.com вместо резервного. Около 300 ГБ пропали, сервис лёг, а бэкапы оказались нерабочими.

Сколько данных потеряла GitLab? Изменения в базе с 17:20 до 00:00 UTC 31 января: около 5 000 проектов, 5 000 комментариев и 700 новых аккаунтов. Задачи, merge request'ы, сниппеты и другие записи за это время тоже пропали.

Пострадали ли Git-репозитории? Нет. Потерялись только данные в базе. Git-репозитории и вики на GitLab.com не пострадали, самостоятельно установленные GitLab — тоже.

Почему не сработали бэкапы? pg_dump падал из-за разницы версий 9.2 и 9.6, а письма об ошибках отклонялись. Снимки дисков для серверов баз не были включены. Репликация сломалась ещё до удаления. LVM-снимки не предназначались для восстановления, но один из них, сделанный для staging, и спас ситуацию.

Сколько длилось восстановление? Базу вернули 1 февраля в 17:00 UTC, последние шаги закончили около 18:00 UTC. Больше всего времени заняло копирование данных со staging: около 18 часов из-за медленных сетевых дисков.

Пройти похожий сбой самому

Историю GitLab легко читать как чужую. В симуляторе DevOps KAI Lab она становится вашей: во втором месяце есть тикет «Бэкап, который молча не работает». Скрипт бэкапа базы месяц делал пустые архивы, а cron отчитывался об успехе, и его надо переписать так, чтобы он падал громко. Ближе к концу года — восстановление базы из снапшота.

Начать можно бесплатно: тест навыков и первые задания первого месяца.

Источники

Читать дальше