Сбой 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:00 | Webhooks восстановлены из отдельной копии, восстановление закончено |
| 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 не делал бэкапы, и никто об этом не знал?
- Кончилось место в S3
- Версия pg_dump не совпадала с версией базы, а письма об ошибках не доходили
- Cron был выключен
- Бэкапы удалял злоумышленник
Показать ответ
Верный ответ — 2. Задача запускалась, но дамп утилитой 9.2 падал на базе 9.6. Письма об ошибке отклонял принимающий сервер, потому что они не были подписаны DMARC. Ошибка была, но её никто не видел.
Вопрос 2. Какая мера лучше всего предотвратила бы такую потерю?
- Запретить работать ночью
- Регулярно восстанавливать базу из бэкапа и алертить на провал бэкапа
- Добавить шестой механизм бэкапа
- Уволить инженера
Показать ответ
Верный ответ — 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 отчитывался об успехе, и его надо переписать так, чтобы он падал громко. Ближе к концу года — восстановление базы из снапшота.
Начать можно бесплатно: тест навыков и первые задания первого месяца.
Источники
- GitLab. Postmortem of database outage of January 31, 10.02.2017 — хронология, причины, механизмы бэкапа, 15 задач на исправление.
- GitLab. GitLab.com database incident, 01.02.2017 — первый отчёт: «пять механизмов», около 4,5 ГБ из 300, репозитории и вики не пострадали.
- PostgreSQL. Документация: pg_dump — совместимость версий pg_dump и сервера.