پشتیبان‌گیری خودکار از PostgreSQL، و آزمودن این‌که واقعاً برمی‌گردد

· ۶ دقیقه مطالعه

پایگاه دادهخودکارسازی

پشتیبانی که هیچ‌وقت بازیابی‌اش را امتحان نکرده‌اید، پشتیبان نیست؛ امید است. این یادداشت هم گرفتن نسخه‌ی پشتیبان را پوشش می‌دهد و هم آزمودنش را.

اسکریپت

#!/usr/bin/env bash
# /usr/local/bin/pg-backup.sh
set -euo pipefail

DB=myapp
DIR=/var/backups/postgres
KEEP_DAYS=14
FILE="$DIR/$DB-$(date +%F-%H%M).dump"

mkdir -p "$DIR"
pg_dump --format=custom --file="$FILE.tmp" "$DB"
mv "$FILE.tmp" "$FILE"
find "$DIR" -name "$DB-*.dump" -mtime +"$KEEP_DAYS" -delete

سه چیز در این اسکریپت عمدی است. set -euo pipefail باعث می‌شود با اولین خطا متوقف شود و فایل ناقص را به جای پشتیبان سالم جا نزند. نوشتن در فایل .tmp و بعد mv یعنی هیچ‌وقت فایل نیمه‌کاره‌ای با نام نهایی وجود ندارد. و --format=custom فشرده است و اجازه می‌دهد موقع بازیابی فقط یک جدول را برگردانید.

تایمر systemd به جای cron

# /etc/systemd/system/pg-backup.service
[Unit]
Description=PostgreSQL backup

[Service]
Type=oneshot
User=postgres
ExecStart=/usr/local/bin/pg-backup.sh
# /etc/systemd/system/pg-backup.timer
[Unit]
Description=Nightly PostgreSQL backup

[Timer]
OnCalendar=*-*-* 03:30:00
Persistent=true
RandomizedDelaySec=10m

[Install]
WantedBy=timers.target
systemctl daemon-reload
systemctl enable --now pg-backup.timer
systemctl list-timers pg-backup.timer

برتری تایمر به cron دو چیز است: خروجی و خطاها در journal می‌مانند (journalctl -u pg-backup)، و با Persistent=true اگر سرور ساعت سه‌ونیم خاموش بوده باشد، بعد از روشن شدن اجرا می‌شود.

بازیابی آزمایشی

createdb restore_test
pg_restore --dbname=restore_test --no-owner /var/backups/postgres/myapp-2026-06-24-0330.dump
psql restore_test -c "select count(*) from users;"
dropdb restore_test

این را ماهی یک بار انجام دهید. اولین باری که امتحان کردم فهمیدم نقش‌های پایگاه داده در dump نیستند؛ pg_dump فقط یک پایگاه داده را می‌گیرد و نقش‌ها را باید جدا با pg_dumpall --globals-only ذخیره کرد.

یک نسخه بیرون از سرور

پشتیبانی که روی همان دیسک است از حذف اشتباهی محافظت می‌کند، نه از خرابی دیسک یا از دست رفتن سرور. آخر اسکریپت یک خط برای فرستادن فایل به جای دیگر اضافه کنید:

rsync -a --remove-source-files "$DIR/" backup@other-host:/srv/backups/myapp/