RE:NODE

Базы данных12 мин чтения

Backup и восстановление баз данных: дампы, которые возвращаются

Как снять согласованный дамп PostgreSQL или MongoDB, где его хранить, как восстановить одну таблицу или всё целиком и как доказать, что это сработало.

Обновлено

0 прочтений

Backup баз данных ломается иначе, чем backup файлов. Наполовину скопированный файл мира очевидно испорчен: он не загрузится, и вы узнаете об этом за минуту. Наполовину согласованный дамп базы восстанавливается чисто и тихо неверен: в таблице orders есть строки, которых нет в order_items, внешний ключ указывает в пустоту, и приложение работает, пока кто-нибудь не откроет ту единственную страницу, которая затрагивает обе таблицы. Инструменты, чтобы этого избежать, существуют и стоят вам один флаг.

Коротко: используйте pg_dump для PostgreSQL и mongodump для MongoDB, никогда не копируйте файлы работающего каталога данных, пишите дамп за пределы машины и по расписанию восстанавливайте его во временную базу, чтобы знать, что он работает. Ниже - подробности, включая важные флаги, то, что дампы оставляют за бортом, и что делать, когда потерять час записей недопустимо.

Почему копия файлов работающей базы - это не backup#

База данных - это набор файлов, имеющих смысл только вместе в один момент времени. Пока она работает, страницы записываются, журнал упреждающей записи опережает файлы данных, а часть того, что приложение считает зафиксированным, ещё находится в памяти. tar, обходящий этот каталог, берёт файл A в 03:00:01, а файл Z в 03:04:12, и в результате получается набор файлов, которые никогда не существовали вместе.

В PostgreSQL восстановление после такого иногда возможно и всегда неприятно. В MongoDB с движком WiredTiger это обычно просто повреждённый каталог данных. «Я сделал backup папки» - самый частый способ убедиться, что backup не был backup.

Есть три законных подхода, и отвечают они на разные вопросы:

СпособЧто даётЦена
Логический дамп (pg_dump, mongodump)Переносимая копия, которую можно читать глазами; восстановление одной таблицыМедленно восстанавливается на больших объёмах; это снимок, а не непрерывный процесс
Физический backup (pg_basebackup, снимок файловой системы при остановленной базе)Быстрое восстановление всего кластераПривязан к той же основной версии и платформе; всё или ничего
Непрерывное архивирование (доставка WAL, oplog набора реплик)Восстановление на выбранную секундуНужно хранилище под вашим контролем и настоящая настройка

Для одной-двух баз за игровым сервером, ботом или веб-приложением правильный ответ - логические дампы, и дальше в статье предполагаются они. Непрерывное архивирование вынесено в отдельный раздел в конце, потому что в тот момент, когда вы не можете позволить себе потерять час записей, ничто другое не подойдёт.

PostgreSQL: как снять дамп, которому можно доверять#

pg_dump открывает одну транзакцию с уровнем изоляции repeatable read и читает всю базу из этого единственного снимка. Всё в файле относится к одному моменту, сколько бы ни длился дамп и что бы ни делало приложение в это время. За это свойство вы и платите.

bash
$ export PGPASSWORD=... # better: use ~/.pgpass, see below$ pg_dump --host=db.example.net --port=5432 --username=app \    --format=custom --no-owner --no-privileges \    --file=/backups/app-$(date +%F).dump app
ФлагЗачем
--format=custom (-Fc)Со сжатием, и единственный формат, из которого pg_restore умеет восстанавливать выборочно
--format=directory (-Fd)Единственный формат, поддерживающий параллельный дамп с -j
--no-ownerНе выводить ALTER ... OWNER TO, что даёт ошибку, если у цели другие роли
--no-privileges (-x)Пропустить GRANT/REVOKE; полезно при восстановлении во временную базу
--schema-only / --data-onlyТолько структура или только строки, для сравнений и частичного восстановления
--table=orders (-t)Одна таблица. Учтите, что зависимости он не подтягивает
--jobs=4 (-j)Параллельный дамп, только формат directory

Две вещи pg_dump не включает, и обе больно бьют в день восстановления:

  • Роли, пароли и табличные пространства. Они относятся ко всему кластеру, а не к отдельной базе. Снимайте их отдельно командой pg_dumpall --globals-only > globals.sql и храните файл рядом с дампом. Без него восстановление на свежий сервер даёт базу, в которой пользователя приложения не существует.
  • Сами расширения. В дампе есть CREATE EXTENSION postgis;, но нет самого PostGIS. Расширение должно быть доступно на целевом сервере, иначе восстановление остановится на этом месте.

Не передавайте учётные данные в командной строке: всё, что там набрано, видно в списке процессов любому другому пользователю машины. Используйте файл паролей:

~/.pgpass (chmod 600)
db.example.net:5432:app:app_user:the-password

Одно правило о версиях, которое стоит запомнить: запускайте бинарники pg_dump и pg_restore версии не ниже версии сервера, с которым работаете. Более старый pg_dump откажется работать с более новым сервером, а дамп, созданный новой основной версией, не всегда загрузится в более старую. При переходе между основными версиями снимайте дамп инструментами новой версии.

PostgreSQL: как вернуть данные#

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

bash
$ createdb -h db.example.net -U app app_restore$ pg_restore --host=db.example.net --username=app --dbname=app_restore \    --no-owner --no-privileges --jobs=4 /backups/app-2026-09-21.dump

--jobs - тот флаг, что превращает час в пятнадцать минут на любой заметной базе; он загружает таблицы и строит индексы параллельно и работает с архивами custom и directory. На тарифе с одним-двумя vCPU выигрыша сверх -j 2 мало, потому что вы упираетесь в то же ограничение, что и всё остальное.

Полезные вариации:

  • Заменить существующую базу на месте: добавьте --clean --if-exists. Прочитайте это дважды, прежде чем запускать против рабочего имени. Флаг удаляет объекты перед пересозданием, и если в дампе не хватает таблицы, эта таблица теперь исчезла.
  • Восстановить одну таблицу: pg_restore -d app_restore -t orders app.dump. Индексы и ограничения, относящиеся к ней, автоматически не включаются, поэтому сначала проверьте pg_restore -l app.dump - это выводит оглавление, которое можно отредактировать и подать обратно через -L для точного выборочного восстановления.
  • Простой SQL-дамп (-Fp или всё, что создал pg_dumpall) вообще не восстанавливается через pg_restore. Это скрипт: psql -d app_restore -f app.sql. Добавьте -v ON_ERROR_STOP=1, иначе он весело пройдёт мимо ошибки и ничего вам не скажет.

Ожидайте предупреждений о владельцах и комментариях к расширениям даже при чистом восстановлении; при --no-owner это шум. Не шум - ненулевой код возврата или любая строка с ERROR:. Направьте вывод в файл и прочитайте его.

MongoDB: mongodump и mongorestore#

Эти инструменты больше не входят в пакет сервера: они поставляются как MongoDB Database Tools и устанавливаются отдельно. Проверьте mongodump --version заранее, а не в два часа ночи, когда они понадобятся.

bash
$ mongodump --uri="mongodb://app:PASSWORD@db.example.net:27017/?authSource=admin" \    --db=app --gzip --archive=/backups/app-$(date +%F).gz

--archive пишет один поток вместо дерева каталогов, что нужно, если файл будет загружаться или передаваться куда-то по конвейеру. --gzip сжимает его. authSource - это база, в которой создан пользователь, почти всегда admin, и ошибка тут даёт сбой аутентификации, похожий на неверный пароль.

Оговорка о согласованности - самое важное. На отдельном mongod mongodump читает коллекции одну за другой без снимка, поэтому дамп базы, в которую ведётся запись, не гарантирует согласованности между коллекциями. На наборе реплик --oplog записывает операции, произошедшие во время дампа, чтобы mongorestore --oplogReplay мог прокрутить их вперёд до единой точки:

bash
$ mongodump --uri="mongodb://...@host:27017/?authSource=admin" \    --oplog --gzip --archive=/backups/full-$(date +%F).gz

Если вы на отдельном экземпляре и данные важны, либо снимайте дамп в тихое окно, либо смиритесь, что согласованность между коллекциями не гарантируется, и проектируйте приложение так, чтобы это не имело значения.

Восстановление:

bash
$ mongorestore --uri="mongodb://app:PASSWORD@db.example.net:27017/?authSource=admin" \    --gzip --archive=/backups/app-2026-09-21.gz \    --nsFrom='app.*' --nsTo='app_restore.*'

Пара флагов --nsFrom и --nsTo - это аналог восстановления во временную базу в MongoDB: те же данные поступают под другим именем, рядом с боевыми, где можно пересчитать документы и направить на них копию приложения. Ещё стоит знать флаги: --drop удаляет каждую коллекцию перед восстановлением (разрушительно, то же предупреждение, что и для --clean выше), --nsInclude='app.orders' восстанавливает одну коллекцию, а --restoreDbUsersAndRoles возвращает пользователей и роли, если восстанавливаемая база их имела.

Проверка для Mongo - это однострочник в оболочке, а не ощущение:

javascript
db.getSiblingDB("app_restore").orders.countDocuments()db.getSiblingDB("app_restore").stats()

Сравните с боевой базой. Если числа близки, но не равны, так и выглядит несогласованный дамп, и это причина беспокоиться о вопросе с набором реплик, описанном выше. Если вы ещё выбираете между двумя системами, шире об этом написано в статье Postgres или MongoDB.

Слот базы данных, который шёл с вашим игровым тарифом или тарифом для приложений#

Игровой тариф включает один слот базы данных, а линейки для приложений и сайтов - два. Их создают в панели с генерируемыми хостом, пользователем и паролем, а администрируют через phpMyAdmin с одноразовым токеном входа, который истекает через шестьдесят секунд. Для backup используйте его вкладку Export: выберите всю базу, формат SQL и сжатие.

Два ограничения нужно знать, прежде чем полагаться на это. Экспорт через браузер годится до нескольких сотен мегабайт и неприятен сверх того, потому что загрузка формируется на лету, а оборванное соединение означает начинать заново. Импорт упирается в более жёсткий предел: размер загрузки, который принимает интерфейс. Дамп, который чисто экспортируется, может отказаться импортироваться, и обычное решение - разбить его или восстановить из оболочки. Опции этого диалога по одной разобраны в статье импорт и экспорт в phpMyAdmin.

Отдельная линейка хостинга баз данных устроена иначе: PostgreSQL и MongoDB, пароль суперпользователя генерируется для каждого сервера, доступ напрямую по хосту и порту тарифа. Это значит, что перечисленные выше консольные инструменты работают с ней ровно так, как написано, и это главная практическая причина перенести растущую базу со слота на собственный тариф.

Как вынести дамп за пределы машины#

Дамп, лежащий на том же хранилище, что и защищаемая им база, покрывает один случай: вы сами удалили свою таблицу. Он ничего не даёт против отказа диска, машины или аккаунта. Это тот же довод, что и в статье backup, которые действительно восстанавливаются, и к базам он применим ещё сильнее, потому что дамп невелик и оправданий нет.

Схема, которая работает в панели, - позволить двум системам наложиться друг на друга:

  1. Расписание записывает дамп в собственную папку сервера за несколько минут до задачи backup.
  2. Задача backup архивирует папку вместе с дампом, и архив хранится вне защищаемой машины.
  3. Раз в неделю вы скачиваете один из таких архивов куда-нибудь, никак не связанное с вашим хостом.

На VDS или где угодно, где есть cron, то же самое одной строкой и с ротацией:

bash
0 4 * * * pg_dump -Fc -f /backups/app-$(date +\%F).dump app && \  find /backups -name 'app-*.dump' -mtime +14 -delete

Обратите внимание на экранированный %: без экранирования cron обрезает команду на первом таком символе, и это классическая причина, по которой задача backup тихо не создаёт ничего. Проверяйте, что файл существует и имеет правдоподобный размер, на следующее утро, а не через три месяца. Порядок и время таких задач в панели описаны в статье задачи по расписанию, которые стоит завести.

Когда потерять час недопустимо#

Ночные дампы означают, что сбой в 23:00 стоит вам суток. Если это действительно недопустимо - магазин, бухгалтерская книга, всё, где строки - это деньги, - вам нужно непрерывное архивирование, и оно меняет то, что вам придётся запускать.

Для PostgreSQL это восстановление на момент времени: включите archive_mode = on и archive_command, копирующий каждый завершённый сегмент журнала упреждающей записи в безопасное место, периодически снимайте базовый backup через pg_basebackup, а для восстановления разверните базовый backup и дайте серверу проиграть журналы до выбранного вами recovery_target_time. Это позволяет вернуться на любую секунду между базовым backup и последним заархивированным сегментом. Ещё для этого нужна роль с атрибутом REPLICATION, место для сегментов и достаточная дисциплина, чтобы заметить, что архивирование остановилось: сбойный archive_command заполняет каталог данных недоставленными журналами, пока не кончится диск.

Для MongoDB эквивалент - набор реплик плюс регулярные дампы с --oplog, а практический нижний предел определяется глубиной окна oplog: если oplog хранит шесть часов операций, вы можете восстановиться на любую точку в пределах шести часов от последнего дампа и не дальше.

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

Как доказать, что восстановление работает#

Весь смысл разделов выше - в файле. Файл не является backup, пока он не стал базой данных снова.

  1. Создайте пустую базу рядом с настоящей или пространство имён с другим названием.
  2. Восстановите в неё той самой командой, которую вы использовали бы при инциденте. Засеките время.
  3. Посчитайте строки в трёх самых важных таблицах и сравните с боевой базой.
  4. Направьте на неё копию приложения, если можете, и откройте страницу с наибольшим числом связей.
  5. Удалите её.

Делайте это раз в квартал, и у вас есть backup. Пропускайте - и у вас есть файл. Полный порядок, включая то, как измерять получаемые числа и что обычно ломается, описан в статье как проверить восстановление до того, как оно понадобится. Если схема, по которой вы восстанавливаете, изменилась после снятия дампа, прочтите и миграции без простоя, потому что восстановление старых данных под новым кодом - это отдельная категория плохих дней.

FAQ#

Можно ли просто скопировать файлы базы, пока она работает?

Нет. Файлы согласованы вместе только в один момент времени, а копия, снимаемая несколько минут, захватывает несколько моментов. Используйте pg_dump или mongodump либо остановите базу перед копированием каталога. Снимок остановленной базы - вполне хороший backup; копия работающей - нет.

Как часто снимать дамп?

Раз в сутки - минимум для всего, что имеет настоящих пользователей, а раз в час дёшево для базы объёмом до нескольких гигабайт, потому что сжатый дамп небольшой базы занимает секунды. Подберите интервал под то, сколько работы вы готовы потерять, затем храните две недели дампов, чтобы проблему, появившуюся в прошлый вторник, ещё можно было устранить.

Включает ли backup сервера из панели базу данных?

Только если дамп базы лежит в папке сервера в момент backup. Архив содержит файлы, а база данных - это служба. Поэтому в расписании дамп снимается первым, а backup вторым.

Восстановится ли дамп новой версии на более старый сервер?

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

Как восстановить одну таблицу или коллекцию?

Для PostgreSQL - pg_restore -t tablename из дампа в формате custom после проверки содержимого через pg_restore -l. Для MongoDB - mongorestore --nsInclude='db.collection'. В обоих случаях сначала восстанавливайте во временную базу и переносите строки осознанно, потому что точечное восстановление ничего не знает об ограничениях и ссылках, указывающих на эту таблицу из других мест.

А что с ролями и паролями?

pg_dump их не включает; pg_dumpall --globals-only включает, и это относится к тому же заданию backup. В MongoDB пользователи живут в базе admin и возвращаются флагом --restoreDbUsersAndRoles. Восстановление, забывшее о них, даёт работающую базу, в которую ваше приложение не может войти. Кому и какие из них вообще стоит выдавать, описано в статье чек-лист безопасности баз данных.


Комментарии

Полностью анонимно: без аккаунта, без почты, без cookie. Мы храним имя, которое вы ввели, текст и время - больше ничего. Количество ссылок ограничено, разметка не отображается.

0/2000