~/blog / postgres-backup-tiklashback

Backup bor, tiklash-chi? pg_dump va pg_restore'ni sinash

10 daqiqa o'qish

Serverda cron har kecha pg_dump ni ishga tushiradi, /srv/backups da har kun uchun yangi fayl paydo bo'ladi, monitoring jim. Bir kuni kimdir WHERE siz DELETE yuboradi, va siz shu fayllardan birini birinchi marta ochasiz. Aynan shu paytda odatda uch xil syurpriz chiqadi:

  • fayl 20 bayt: pg_dump allaqachon xato bilan chiqib ketgan, | gzip esa buni yashirgan;
  • pg_restore faylni o'qimaydi: input file appears to be a text format dump. Please use psql.;
  • tiklash o'tadi, lekin ekran role "app" does not exist xatolariga to'ladi va buyruq 1 kodi bilan tugaydi.

Uchalasi ham backup olingan kuni emas, tiklash kerak bo'lgan kuni ochiladi. Bu maqolaning g'oyasi bitta: tiklab ko'rilmagan backup — backup emas, umid. Quyida dump olamiz, formatni to'g'ri tanlaymiz va har bir nusxani toza konteynerga tiklab isbotlaymiz. Chiqishlar postgres:16-alpine (16.13), Docker Engine 29 va Compose v2.40 da haqiqatan ishga tushirib olingan; eski versiyalarning xatolari — postgres:15-alpine va postgres:16.9-alpine dan.

Birinchi tuzoq: quvur xatoni yutadi#

console
$ docker compose exec -T db pg_dump -U ap -d app | gzip > app.sql.gz
pg_dump: error: connection to server on socket "/var/run/postgresql/.s.PGSQL.5432" failed: FATAL:  role "ap" does not exist
$ echo $?
0
$ stat -c %s app.sql.gz
20

Shell quvur (pipe) natijasi sifatida oxirgi buyruqning kodini qaytaradi. gzip bo'sh kirishni muvaffaqiyatli siqdi — 20 baytlik to'g'ri gzip fayl va 0 kodi. Cron hech kimga xabar bermaydi, fayl esa har kecha paydo bo'laveradi. set -o pipefail bilan quvurdagi biror buyruq yiqilsa, butun quvur noldan farqli kod qaytaradi va skript to'xtaydi.

Ikki format: SQL matn va arxiv#

Standart holda pg_dump oddiy SQL matn yozadi (-Fp). Uni psql tiklaydi, ichini ko'z bilan o'qish va grep qilish mumkin. Ishlab chiqarish uchun odatda custom format (-Fc) olinadi: bitta siqilgan arxiv fayl. Uni faqat pg_restore o'qiydi, lekin evaziga qarorlar tiklash paytiga qoladi: qaysi jadvallar, nechta parallel oqim, avval o'chirish kerakmi (--clean). SQL matnda bularning hammasi dump olingan paytda hal bo'lib bo'lgan.

FormatFlagIchida nimaNima bilan tiklanadi
plain-Fp (standart)SQL matnpsql
custom-Fcbitta siqilgan arxivpg_restore, -j bilan parallel
directory-Fdpapka, har jadval alohida fayldapg_restore, -j bilan parallel; dump'ning o'zi ham parallel
tar-Fttar arxiv, siqilmaganpg_restore, parallelsiz

Formatni adashtirish — eng birinchi uchraydigan xato:

console
$ pg_restore -d app app.sql
pg_restore: error: input file appears to be a text format dump. Please use psql.

Custom arxivning ichini pg_restore -l ko'rsatadi. Sarlavhada format, siqish usuli va versiyalar bor:

console
$ pg_restore -l app.dump | head -11
;
; Archive created at 2026-09-21 08:54:26 UTC
;     dbname: app
;     TOC Entries: 20
;     Compression: gzip
;     Dump Version: 1.15-0
;     Format: CUSTOM
;     Integer: 4 bytes
;     Offset: 8 bytes
;     Dumped from database version: 16.13
;     Dumped by pg_dump version: 16.13

Versiyalar ham muhim. Dump Version: 1.15 — arxiv formatining versiyasi, uni 16-versiyadagi pg_dump yozgan. 15-versiyadagi pg_restore bu faylni ochmaydi: pg_restore: error: unsupported version (1.15) in file header. Qoida oddiy: bir xil yoki yangiroq major versiya bilan tiklang. SQL matn ham bundan xoli emas: 2025-yil avgustidagi xavfsizlik relizlaridan (16.10, 17.6 va boshqa tarmoqlardagi mos versiyalar) beri dump \restrict qatori bilan boshlanadi. Eski psql uni tanimaydi: invalid command \restrict.

SQL matnni psql -v ON_ERROR_STOP=1 --single-transaction bilan tiklang. Birinchi xatoda psql 3 kodi bilan to'xtaydi, tranzaksiya esa hammasini orqaga qaytaradi — yarim tiklangan baza qolmaydi. Busiz psql xatolarni ekranga chiqaradi-yu, keyingi qatorlarni bajarishda davom etadi va oxirida 0 qaytaradi.

Compose konteyneridan dump olish#

bash
docker compose exec -T db pg_dump -U app -d app -Fc > app.dump

Bu qatorda uchta qaror bor:

  • pg_dump db konteynerining ichida ishlaydi, shuning uchun uning versiyasi serverniki bilan bir xil. Host'dagi eski pg_dump yangi serverga ishlamaydi: pg_dump: error: aborting because of server version mismatch, keyingi qatorda esa ikkala versiya ko'rsatiladi.
  • Konteyner ichida ulanish Unix socket orqali boradi. Rasmiy image lokal socket ulanishlarini parolsiz qabul qiladi, shuning uchun skriptga parol yozish shart emas.
  • -T TTY ajratmaydi. Compose v2 stdout faylga yo'naltirilganini o'zi sezadi, shuning uchun dump -T siz ham ishlaydi. Teskari yo'nalishda esa, interaktiv terminaldan docker compose exec db psql -U app -d app < app.sql qilsangiz, Compose TTY ochmoqchi bo'ladi va the input device is not a TTY bilan yiqiladi. Skriptda doim -T yozing — shunda uning xulqi qayerdan ishga tushirilganiga bog'liq bo'lmaydi.

Tungi skript to'liq ko'rinishda:

bash
#!/usr/bin/env bash
# /usr/local/bin/pg-backup.sh
set -euo pipefail
umask 077                     # dump'da butun baza bor: faqat egasi o'qisin

cd /srv/app                   # compose.yaml shu katalogda
day=$(date +%F)
out=/srv/backups/app-$day.dump
trap 'rm -f "$out.part"' EXIT

docker compose exec -T db pg_dump -U app -d app -Fc > "$out.part"
mv "$out.part" "$out"
docker compose exec -T db pg_dumpall -U app --globals-only > "/srv/backups/globals-$day.sql"

find /srv/backups -name 'app-*.dump' -mtime +14 -delete
find /srv/backups -name 'globals-*.sql' -mtime +14 -delete
  • Dump avval .part fayliga yoziladi va faqat muvaffaqiyatdan keyin nomi o'zgartiriladi. Bir fayl tizimi ichida mv atomar, shuning uchun yarim yozilgan fayl hech qachon tayyor nusxadek ko'rinmaydi. Yiqilishda .part ni trap tozalaydi.
  • set -e tufayli eski nusxalarni o'chiradigan find faqat yangi dump muvaffaqiyatli bo'lganda ishlaydi. Yiqilgan backup yaxshi nusxalarni o'zi bilan olib ketmaydi.
  • -mtime +14 yoshi 14 kundan oshgan fayllarni tanlaydi. find kunlarni butun songa yaxlitlab sanaydi, shuning uchun amalda 15-kundan boshlab o'chadi.

Cron qatori: 15 3 * * * /usr/local/bin/pg-backup.sh >> /var/log/pg-backup.log 2>&1. Sanani skript ichida hisoblashning yana bir sababi bor: crontab qatorida % yangi qatorga aylanadi, u yerda date +\%F deb yozish kerak bo'lardi.

Muqobil yo'l: bir martalik servis#

Dump'ni compose'ning o'zida alohida servis sifatida ham olish mumkin. Bunda pg_dump bazaga tarmoq orqali ulanadi, va unga parol kerak — aks holda fe_sendauth: no password supplied:

yaml
services:
  backup:
    image: postgres:16-alpine
    profiles: ["backup"]
    command: ["sh", "-c", "pg_dump -h db -U app -d app -Fc -f /backups/app-$$(date +%F).dump"]
    environment:
      PGPASSWORD: ${POSTGRES_PASSWORD}
    restart: "no"
    depends_on:
      db:
        condition: service_healthy
    volumes:
      - backups:/backups
volumes:
  backups:

profiles tufayli docker compose up -d bu servisni ko'tarmaydi, docker compose run --rm backup esa uni bir marta ishga tushiradi. $$ — Compose uchun literal $: bittalik $ dan keyin nom kelsa ($HOME), Compose uni fayl o'qilayotganda host'dagi qiymat bilan almashtirib yuboradi. restart: "no" standart qiymat, lekin yozib qo'yilgani niyatni ko'rsatadi: backup tugaydigan ish, uni qayta-qayta ishga tushirish kerak emas.

Rollar dump'ga kirmaydi#

pg_dump bitta bazani oladi. Rollar va tablespace'lar esa butun klasterga tegishli global obyektlar, ular hech bir bazaning dump'iga tushmaydi. Ularni skriptdagi ikkinchi buyruq — pg_dumpall --globals-only oladi:

sql
CREATE ROLE app;
ALTER ROLE app WITH SUPERUSER INHERIT CREATEROLE CREATEDB LOGIN REPLICATION BYPASSRLS PASSWORD 'SCRAM-SHA-256$4096:…';
CREATE ROLE reporting;
ALTER ROLE reporting WITH NOSUPERUSER INHERIT NOCREATEROLE NOCREATEDB LOGIN NOREPLICATION NOBYPASSRLS PASSWORD 'SCRAM-SHA-256$4096:…';

Faylda parol xeshlari bor — uni sir kabi saqlang. Xeshlarni o'qish uchun superuser huquqi kerak; rasmiy image'da POSTGRES_USER aynan superuser bo'ladi.

Rollarsiz toza klasterga tiklash maqola boshidagi uchinchi syurprizni beradi (chiqishdan tanlangan qatorlar):

text
pg_restore: error: could not execute query: ERROR:  role "app" does not exist
Command was: ALTER TABLE public.customers OWNER TO app;

pg_restore: error: could not execute query: ERROR:  role "reporting" does not exist
Command was: GRANT SELECT ON TABLE public.orders TO reporting;

pg_restore: warning: errors ignored on restore: 5

Ma'lumotlar tiklangan, lekin egalik va huquqlar yo'q, kod esa 1. Haqiqiy tiklashda tartib shunday: avval globals.sql ni psql bilan yuklaysiz, keyin pg_restore.

Isbot: toza konteynerga tiklash#

Endi asosiy qism. Quyidagi skript eng oxirgi dump'ni yangi, bo'sh konteynerga tiklaydi va bitta savol beradi: ma'lumot bormi va u yangimi?

bash
#!/usr/bin/env bash
# /usr/local/bin/restore-test.sh
set -euo pipefail

dump=$(ls -t /srv/backups/app-*.dump | head -n 1)
name=restore-test-$$

docker run -d --name "$name" -e POSTGRES_PASSWORD=test postgres:16-alpine > /dev/null
trap 'docker rm -f "$name" > /dev/null' EXIT

for _ in $(seq 60); do
  docker exec "$name" pg_isready -h 127.0.0.1 -U postgres > /dev/null 2>&1 && break
  sleep 1
done

docker exec "$name" createdb -U postgres app
docker exec -i "$name" pg_restore -U postgres -d app --no-owner --no-privileges --exit-on-error < "$dump"

rows=$(docker exec "$name" psql -U postgres -d app -Atc 'select count(*) from orders')
last=$(docker exec "$name" psql -U postgres -d app -Atc 'select max(created_at) from orders')
echo "$dump: orders=$rows, oxirgi buyurtma: $last"
test "$rows" -gt 0
console
$ restore-test.sh
/srv/backups/app-2026-09-21.dump: orders=50000, oxirgi buyurtma: 2026-09-21 08:53:47.372017+00
$ echo $?
0

Bo'sh fayl bilan esa skript shovqin ko'taradi:

console
$ restore-test.sh
pg_restore: error: input file is too short (read 0, expected 5)
$ echo $?
1

Qatorlarning sababi:

  • pg_isready -h 127.0.0.1 — birinchi ishga tushishda image vaqtinchalik serverni faqat Unix socket'da ko'taradi. TCP tekshiruv asosiy server tayyor bo'lishini kutadi. Batafsil — depends_on haqidagi maqolada.
  • --no-owner --no-privileges — sinov konteynerida app va reporting rollari yo'q. To'liq mashq uchun avval docker exec -i "$name" psql -U postgres < globals.sql qiling va bu ikki flagni olib tashlang — shunda egalik va huquqlar ham sinovdan o'tadi.
  • --exit-on-error — standart holda pg_restore xatolarni o'tkazib yuborib, hisobini oxirida beradi. Sinovda birinchi xatoning o'zi natija.
  • Oxirgi so'rov faylning shunchaki o'qilishini emas, ma'lumot yangiligini ham tekshiradi. Har kuni o'sadigan jadvalni tanlang: max(created_at) bir hafta oldingi sana bo'lsa, backup "ishlagan", lekin eski narsani saqlagan.
  • trap konteynerni xato bo'lganda ham o'chiradi.

Skriptning chiqish kodi — hukm. Uni backup'dan keyin cron'da yoki haftalik CI ishida ishga tushiring va noldan farqli kodga ogohlantirish bog'lang. time restore-test.sh bilan vaqtini ham o'lchang: bu raqam — nosozlik kunidagi haqiqiy tiklanish vaqtingiz.

pg_restore'ning kerakli flaglari#

  • -d app — shu bazaga tiklaydi. -d siz pg_restore SQL matnni stdout'ga chiqaradi: arxiv ichiga qarashning oson yo'li.
  • -C — bazani arxivdagi nom bilan o'zi yaratadi; bunda -d postgres faqat dastlabki ulanish uchun.
  • --clean --if-exists — obyektlarni qayta yaratishdan oldin o'chiradi; --if-exists yo'q obyektlar uchun xato bermaydi.
  • -j 4 — to'rt oqimda parallel tiklaydi, lekin faqat fayldan. Stdin'dan berilsa: parallel restore from standard input is not supported. Faylni konteynerga docker compose cp app.dump db:/tmp/app.dump bilan olib kiring.
  • -t orders — faqat shu jadval va uning ma'lumoti. Indeks va constraint'lar kelmaydi: sinovda orders ning primary key'i tiklanmadi.
  • -l va -L — mundarijani faylga chiqarib, keraksiz qatorlarni o'chirib, faqat qolganini tiklash.

Qayerda va qancha saqlash#

  • Baza bilan bir diskdagi nusxa DROP TABLE dan saqlaydi, lekin disk yoki server yo'qolishidan emas. Kamida bitta nusxa boshqa joyda bo'lsin: boshqa serverda yoki object storage'da (rclone copy, aws s3 cp). Tashqariga chiqadigan nusxani shifrlang.
  • Saqlash muddati biznes savoli: xato qancha kechikib sezilishi mumkin? Buzilgan ma'lumot uch haftadan keyin topilsa, 14 kunlik nusxalarning hammasida u allaqachon buzilgan. Shuning uchun kunliklardan tashqari haftalik va oylik nusxalar ham uzoqroq saqlanadi.
  • pg_dump — bir lahzalik surat. Undan keyin yozilgan hamma narsa keyingi dump'gacha himoyasiz. Bir kunlik yo'qotish qabul qilinmasa, WAL arxivlash va nuqtali tiklash (PITR) kerak — pgBackRest yoki WAL-G kabi vositalar. Bu alohida mavzu.
  • pg_dump o'qish va yozishni to'smaydi: izchil suratni bitta tranzaksiya snapshot'i orqali oladi. Lekin jadvallarga ACCESS SHARE qulfini qo'yadi. Dump davomida ACCESS EXCLUSIVE talab qiladigan buyruqlar — ALTER TABLE ning ko'p turlari, DROP TABLE, TRUNCATE — kutib qoladi, ularning ortida esa shu jadvalga kelgan yangi so'rovlar ham navbatga turadi. Migratsiyalarni backup vaqtiga qo'ymang.

Tekshirish ro'yxati#

  1. pg_dump ni ataylab buzing (-U ga noto'g'ri nom yozing): skript noldan farqli kod bilan chiqishi, eski nusxalar esa joyida qolishi kerak.
  2. restore-test.sh har backup'dan keyin ishlaydi va uning kodi kuzatiladi.
  3. Oyiga bir marta to'liq mashq qiling: globals.sql, keyin --no-owner siz pg_restore. Tiklash vaqtini yozib qo'ying.
  4. Boshqa joydagi nusxani ham o'sha yerdan yuklab olib tiklang — faqat mahalliysini emas.

Amaliyot#

Birinchi ikkitasida zaxira nusxa — bazaning sog'lom bo'lishini kutadigan bir martalik servis. Uchinchisida volume'siz baza har down da yo'qoladi. To'rtinchisida major versiya: eski ma'lumot papkasi yangi Postgres'ga o'tmaydi, dump va tiklash esa o'tadi. Tuzating va server tekshiruvidan o'tkazing:

labqiyinZaxira nusxa — alohida, bir martalik servis`backup` `restart: always` bilan har 10 soniyada `pg_dump` qilyapti va bazani bo'g'yapti.mashqni ochish labqiyinBaza, migratsiya, zaxira, APITo'rtta servis, ikkitasi bir martalik. Tartib va restart siyosatlari farq qiladi.mashqni ochish labosonHar `docker compose down` da baza bo'shaydiMariaDB ma'lumotlari konteyner ichida — konteyner ketsa, ular ham ketadi.mashqni ochish laboson`postgres:latest` bir kun yangi major versiyaga sakraydiMajor versiya o'zgarsa, eski ma'lumot papkasi bilan yangi Postgres ishga tushmaydi.mashqni ochish

pg_dump, pg_restore va psql buyruqlari qo'l xotirasiga o'tishi uchun:

buyruq mashqi10-bosqich · 16 vazifaMa'lumotlar bazasi operatsiyalariPostgres va Redis bilan kundalik ish: zaxira, tiklash, hajm, ulanishlar, sekin so'rovlar.bosqichni boshlash