Ночной дамп базы данных в S3 - это одна строка cron, которая передаёт вывод инструмента дампа через pipe в загрузку: pg_dump -Fc | aws s3 cp - s3://db-dumps/app/$(date +%F).dump. Эта строка работает - и одновременно содержит самый частый способ, которым такие задания тихо ломаются: если дамп умирает на полпути, загрузка всё равно проходит успешно, и вы каждую ночь сохраняете обрезанный файл, пока однажды он вам не понадобится. В этой статье задание собирается как следует для PostgreSQL, MySQL и MongoDB: где его запускать, как не показывать пароли в списке процессов, как гарантировать, что хранятся только полные дампы, шифрование, срок хранения без правил жизненного цикла и проверка восстановления, которая покажет, работает ли всё это вообще.
Где запускать задание#
Инструмент дампа подключается к базе как любой клиент, поэтому задание может работать на любой машине, которая достаёт до хоста и порта базы и на которой установлены нужные клиентские инструменты. Варианты, примерно в порядке предпочтения:
- Небольшой Linux VDS или сервер, который у вас уже есть, с cron и клиентскими пакетами. Обычный ответ.
- Сервер приложения, если это полноценная машина, где можно установить
postgresql-clientи настроить задание cron. - Ваш собственный ПК или домашний сервер - у них есть преимущество: они находятся в другом месте, чем и база, и бакет.
Управляемые линейки баз данных часто не дают shell на самой машине с базой, и это нормально: дамп по сети - обычный способ. На RE:NODE к линейкам PostgreSQL, MySQL и MongoDB подключаются по хосту и порту тарифа со сгенерированными учётными данными, и у них есть собственные слоты backup в панели. Используйте их для быстрых откатов; дамп в S3 - это копия, которая переживёт удалённый сервер и даст вам переносимый файл, который можно загрузить где угодно.
Версия клиента имеет значение. pg_dump должен быть той же основной версии, что и сервер, или новее - старый pg_dump отказывается снимать дамп с более нового сервера. mysqldump из MySQL 8.4 чисто снимает дамп с сервера 8.4; mysqldump от MariaDB против MySQL 8.4 в основном работает, но это не тот инструмент, которому стоит доверять backup. mongodump входит в пакет MongoDB Database Tools, версии которого не совпадают с версиями сервера; любой свежий релиз поддерживает текущие серверы.
Учётные данные без утечек#
Пароли в командной строке видны любому пользователю машины через ps и оседают в истории shell. У каждого инструмента есть альтернатива через файл.
| Инструмент | Файл с учётными данными | Формат |
|---|---|---|
pg_dump | ~/.pgpass (режим 600) | host:port:dbname:user:password |
mysqldump | --defaults-extra-file=/etc/backup/my.cnf | секция [client] с user, password, host, port |
mongodump | --config=/etc/backup/mongo.yaml | ключи uri: и password: |
| AWS CLI | ~/.aws/credentials и ~/.aws/config | профиль с ключами и endpoint |
[client]host = db.example.netport = 3306user = backuppassword = s3cret-generated-value--defaults-extra-file должен быть первой опцией в командной строке, иначе mysqldump его проигнорирует. Сделайте каждый из этих файлов доступным для чтения только пользователю, от имени которого работает задание (chmod 600).
Где возможно, дайте дампу собственного пользователя базы. Пользователю для backup нужно чтение, а не запись: в PostgreSQL это роль pg_read_all_data (PostgreSQL 14 и новее); в MySQL - SELECT, SHOW VIEW, TRIGGER, EVENT и LOCK TABLES на базу плюс глобальный PROCESS, чтобы не было предупреждения о tablespace. Тогда утёкшие учётные данные для backup ничего не смогут изменить. Подробности для Postgres - в статье роли и права в PostgreSQL.
Со стороны бакета один раз настройте профиль AWS CLI с endpoint, path-style и настройками совместимости контрольных сумм, как описано в статье хранилище S3 через AWS CLI:
[profile store]region = us-east-1endpoint_url = https://s3.example.comrequest_checksum_calculation = when_requiredresponse_checksum_validation = when_requireds3 = addressing_style = pathPostgreSQL#
Используйте custom-формат (-Fc). Он сжат, и pg_restore может восстановить из него отдельную таблицу или схему, показать его содержимое и восстанавливать параллельно. Обычный SQL-дамп можно только проиграть целиком.
$ pg_dump -h db.example.net -p 5432 -U backup -d app -Fc --no-owner \ | aws s3 cp - "s3://db-dumps/pg/app/$(date -u +%F_%H%M).dump" --profile storepg_dump делает целостный снимок внутри одной транзакции, поэтому дамп отражает один момент времени, даже пока приложение продолжает писать. Пишущих он не блокирует. --no-owner исключает команды смены владельца, и файл можно восстановить в базу, пользователь которой называется иначе, - удобно, когда цель восстановления - новый сервер со сгенерированными учётными данными. Роли и другие объекты уровня кластера в файл pg_dump не попадают вовсе; если они нужны, их сохраняет pg_dumpall --globals-only, хотя на управляемой линейке единственного пользователя приложения обычно проще создать заново вручную. Все флаги разобраны в статье pg_dump и pg_restore.
MySQL#
$ mysqldump --defaults-extra-file=/etc/backup/my.cnf \ --single-transaction --routines --triggers --events --hex-blob \ --set-gtid-purged=OFF app \ | zstd -q -T0 \ | aws s3 cp - "s3://db-dumps/mysql/app/$(date -u +%F_%H%M).sql.zst" --profile storeЗачем нужен каждый флаг:
--single-transactionснимает таблицы InnoDB из одного целостного снимка, не блокируя их. Для таблиц MyISAM он ничего не делает: без блокировки они не будут целостными - ещё одна причина их не иметь.--routines --triggers --eventsвключают хранимые процедуры, триггеры и запланированные события. Триггеры включены по умолчанию, остальные два - нет, и дамп без них восстанавливает базу, в которой незаметно не хватает логики.--hex-blobзаписывает двоичные столбцы в шестнадцатеричном виде, что переживает любую путаницу с кодировками при обратной загрузке.--set-gtid-purged=OFFне пускает в дамп команды GTID, чтобы его можно было импортировать на сервер, который не входит в ту же схему репликации.
Учтите, что mysqlpump - параллельную альтернативу, которую советуют некоторые старые руководства, - удалили в MySQL 8.4. mysqldump остался, а для всего, что больше нескольких гигабайт, быстрее утилиты дампа из MySQL Shell. Восстановление и большие дампы разобраны в статье backup и восстановление через mysqldump.
MongoDB#
$ mongodump --config=/etc/backup/mongo.yaml --archive --gzip \ | aws s3 cp - "s3://db-dumps/mongo/app/$(date -u +%F_%H%M).archive.gz" --profile storeuri: mongodb://backup@db.example.net:27017/app?authSource=adminpassword: s3cret-generated-value--archive без имени файла пишет единый поток архива в stdout, а --gzip его сжимает. Восстановление читает тот же поток: aws s3 cp s3://... - | mongorestore --archive --gzip --drop. На одиночном сервере mongodump не делает снимок на момент времени - документы, записанные во время долгого дампа, могут попасть в него, а могут и нет. --oplog это исправляет, но работает только с replica set. Для большинства небольших приложений дамп в самый тихий час достаточно целостен; если у вас не так, приостановите запись или снимайте дамп с реплики. Остальное - в статье mongodump и mongorestore.
Valkey и SQL Server
Двум другим движкам нужен иной подход. Valkey держит данные в памяти и сам сохраняет их на диск, поэтому «дамп» - это копия его снимка. valkey-cli (или redis-cli, который говорит на том же протоколе) может получить его по сети: valkey-cli -h db.example.net -p 6379 --user default --pass "$PW" --rdb /tmp/dump.rdb просит сервер выдать свежий файл RDB и записывает его локально, готовым к загрузке. Большинство вообще не делает backup кэша - если его можно восстановить из настоящей базы, потеря обойдётся в несколько медленных минут, - но Valkey, в котором хранятся сессии или очереди, стоит копировать каждую ночь. --pass в командной строке виден в ps; redis-cli вместо этого читает пароль из переменной окружения REDISCLI_AUTH, а valkey-cli --help покажет, какую переменную учитывает ваша версия.
SQL Server устроен наоборот: BACKUP DATABASE записывает файл .bak на собственный диск сервера, а в Express нет SQL Server Agent, чтобы запускать это по расписанию. Обычная схема - вызов sqlcmd по расписанию с другой машины, чтобы запустить backup, а затем получение файла. Это описано в статье backup и восстановление SQL Server, а шаг загрузки в конце - тот же aws s3 cp, что и везде.
Pipe, который загружает полдампа#
Вот подробно та ловушка из вступления. В конвейере A | B shell сообщает код выхода B. Если pg_dump теряет соединение после 300 MB из дампа на 1 GB, он завершается с ошибкой, pipe закрывается, aws s3 cp - видит нормальный конец входных данных, загружает 300 MB и завершается с кодом 0. Задание «прошло успешно». И может так «проходить» каждую ночь.
set -o pipefail заставляет скрипт это заметить - конвейер теперь считается неудачным, если упал любой его этап, - но обрезанный объект уже лежит в бакете, и ничто в его имени об этом не говорит. Два надёжных подхода:
Загрузка во временный ключ и перенос при успехе.
TMP="s3://db-dumps/_incoming/app-$$.dump"FINAL="s3://db-dumps/pg/app/$(date -u +%F_%H%M).dump"if pg_dump ... -Fc | aws s3 cp - "$TMP" --profile store; then aws s3 mv "$TMP" "$FINAL" --profile storeelse aws s3 rm "$TMP" --profile store exit 1fiПри включённом pipefail условие if видит сбой любого из этапов. Неудачный запуск ничего не оставляет под итоговым префиксом, а всё, что застряло в _incoming/, - видимый признак проблемы. mv - это копирование на стороне сервера с последующим удалением, так что данные не загружаются дважды.
Сначала дамп на локальный диск. Запишите файл, проверьте код выхода, проверьте сам файл (pg_restore --list dump.file > /dev/null читает всё оглавление и падает на обрезанном файле custom-формата), затем загрузите. Это требует свободного локального места размером со сжатый дамп, но взамен даёт самую строгую проверку.
Для потоков больше примерно 50 GB AWS CLI также нужен --expected-size с оценкой размера в байтах, чтобы он выбрал достаточно крупные части и уложился в лимит multipart в 10 000 частей. Ниже этого порога значения по умолчанию подходят.
Шифрование#
Дамп базы данных - самый чувствительный файл, который у вас есть: все пользователи, все хеши, все адреса. Если ключи бакета когда-нибудь утекут, вместе с ними утекут и дампы. Шифруйте до загрузки, чтобы хранилище всегда держало только шифротекст.
Проще всего для этого инструмент age. Один раз сгенерируйте пару ключей на машине, которая не является машиной для backup, храните приватный ключ офлайн, а в задание кладите только публичный:
$ age-keygen -o backup-key.txt # on your own machine; keep this file safe$ pg_dump ... -Fc | age -r age1qz...publickey... \ | aws s3 cp - "s3://db-dumps/pg/app/$(date -u +%F).dump.age" --profile storeМашина для backup может шифровать, но не может расшифровывать, поэтому злоумышленник, захвативший её, не прочитает старые дампы из бакета. Для восстановления: aws s3 cp s3://.../file.dump.age - | age -d -i backup-key.txt | pg_restore .... gpg --symmetric тоже подходит, но требует пароль на машине для backup, и это свойство теряется. Что бы вы ни выбрали, храните ключ расшифровки там, где он у вас останется и после того, как сервера не станет, - в менеджере паролей, распечатанным в ящике стола, - иначе зашифрованные дампы превратятся в шум.
Срок хранения без правил жизненного цикла#
Называйте ключи так, чтобы они сортировались по дате, - pg/app/2026-10-08_0400.dump, - и удаляйте по имени. Не полагайтесь на фильтры вроде --min-age в инструментах синхронизации: они используют время изменения, которое не всегда означает «когда сделан этот backup».
Схема «дед-отец-сын» хранит неделю ежедневных, месяц еженедельных и год ежемесячных копий в трёх префиксах. По воскресеньям копируйте дамп этой ночи в weekly/, первого числа - в monthly/. Копирование на стороне сервера не требует повторной загрузки:
#!/usr/bin/env bashset -euo pipefailP="--profile store"B="s3://db-dumps/pg/app"TODAY=$(date -u +%F)LATEST=$(aws s3 ls "$B/" $P | awk '{print $4}' | grep "^$TODAY" | sort | tail -n1)[ -n "$LATEST" ] || exit 1if [ "$(date -u +%u)" = 7 ]; then aws s3 cp "$B/$LATEST" "$B/weekly/$LATEST" $P; fiif [ "$(date -u +%d)" = 01 ]; then aws s3 cp "$B/$LATEST" "$B/monthly/$LATEST" $P; fiprune() { # prune <prefix> <days> local cutoff; cutoff=$(date -u -d "$2 days ago" +%F) { aws s3 ls "$1/" $P || true; } | awk '{print $4}' | while read -r k; do if [ -n "$k" ] && [[ "${k:0:10}" < "$cutoff" ]]; then aws s3 rm "$1/$k" $P; fi done}prune "$B" 7prune "$B/weekly" 35prune "$B/monthly" 366aws s3 ls по префиксу выводит объекты в четвёртом столбце, а папки - строками PRE, которые отсекаются проверкой на пустоту. Ещё он завершается с ошибкой, если в префиксе пока ничего нет, - в первую неделю, пока не появилась ни одна еженедельная копия, - поэтому этот вызов обёрнут в || true; без этого pipefail остановил бы скрипт при первом же запуске. Запускайте дамп в 04:00, а этот скрипт в 04:30 из cron и добавьте в конец каждого сигнал для dead man's switch, чтобы узнать о той ночи, когда он не запустится. Синтаксис расписания разобран в статье cron-выражения простыми словами.
Размер бакета считайте по политике. Семь ежедневных, пять еженедельных и двенадцать ежемесячных - это 24 дампа; при сжатом дампе на 400 MB это примерно 10 GB, и с ростом базы объём растёт пропорционально.
Проверка восстановления#
Backup, из которого никто не восстанавливался, - это гипотеза. Раз в месяц или после любого изменения задания:
- Скачайте последний дамп и расшифруйте его.
- Восстановите его во временную базу - идеально подходит локальный контейнер Docker:
docker run --rm -e POSTGRES_PASSWORD=x -p 5433:5432 postgres:17. - Выполните запрос, который работает только на реальных данных: количество строк в главных таблицах, время самого свежего заказа.
- Запишите, сколько это заняло. Это и есть ваше реальное время восстановления, и обычно оно больше, чем люди предполагают.
Статьи backup и восстановление баз данных и проверка восстановления до того, как оно понадобится превращают это в рутину.
FAQ#
Как часто снимать дамп базы?
Для большинства небольших приложений хватает раза в день. Интервал - это максимум данных, которые вы можете потерять, поэтому магазину, который весь день принимает заказы, могут понадобиться дампы каждые несколько часов плюс backup панели между ними. Дальше следующий шаг - непрерывное архивирование (WAL для Postgres, binlog для MySQL), но это уже другое задание.
Нужно ли сжимать дампы перед загрузкой?
Да, если формат уже не сжат. Custom-формат PostgreSQL сжат; mongodump --gzip - тоже. Обычный mysqldump - это текст, и он уменьшается в пять-десять раз с zstd или gzip.
Можно ли снимать дамп, пока приложение работает?
Да. pg_dump и mysqldump --single-transaction читают целостный снимок, не блокируя запись. Всё равно запускайте их в тихий час: дамп читает каждую строку и конкурирует с настоящими запросами за диск и CPU.
Достаточно ли одной копии в S3?
Это хорошая вторая копия, но не единственная. Хранилище RE:NODE, например, держит одну копию на NVMe в одной площадке без репликации. Сохраняйте и backup панели, а раз в месяц скачивайте дамп в совсем другое место.
Как узнать, что задание всё ещё работает?
Пусть оно в конце каждого успешного запуска отправляет сигнал сервису мониторинга, а тот предупреждает, если сигнала нет. Иногда проверяйте и размер последнего дампа: внезапное падение до нескольких килобайт означает, что что-то не так, даже если каждый шаг «прошёл успешно».




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