Backup bor, tiklash-chi? pg_dump va pg_restore'ni sinash
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_dumpallaqachon xato bilan chiqib ketgan,| gzipesa buni yashirgan; pg_restorefaylni o'qimaydi: input file appears to be a text format dump. Please use psql.;- tiklash o'tadi, lekin ekran
role "app" does not existxatolariga 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#
$ 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
20Shell 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.
| Format | Flag | Ichida nima | Nima bilan tiklanadi |
|---|---|---|---|
| plain | -Fp (standart) | SQL matn | psql |
| custom | -Fc | bitta siqilgan arxiv | pg_restore, -j bilan parallel |
| directory | -Fd | papka, har jadval alohida faylda | pg_restore, -j bilan parallel; dump'ning o'zi ham parallel |
| tar | -Ft | tar arxiv, siqilmagan | pg_restore, parallelsiz |
Formatni adashtirish — eng birinchi uchraydigan xato:
$ 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:
$ 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.13Versiyalar 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#
docker compose exec -T db pg_dump -U app -d app -Fc > app.dumpBu qatorda uchta qaror bor:
pg_dumpdbkonteynerining ichida ishlaydi, shuning uchun uning versiyasi serverniki bilan bir xil. Host'dagi eskipg_dumpyangi 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.
-TTTY ajratmaydi. Compose v2 stdout faylga yo'naltirilganini o'zi sezadi, shuning uchun dump-Tsiz ham ishlaydi. Teskari yo'nalishda esa, interaktiv terminaldandocker compose exec db psql -U app -d app < app.sqlqilsangiz, Compose TTY ochmoqchi bo'ladi vathe input device is not a TTYbilan yiqiladi. Skriptda doim-Tyozing — shunda uning xulqi qayerdan ishga tushirilganiga bog'liq bo'lmaydi.
Tungi skript to'liq ko'rinishda:
#!/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
.partfayliga yoziladi va faqat muvaffaqiyatdan keyin nomi o'zgartiriladi. Bir fayl tizimi ichidamvatomar, shuning uchun yarim yozilgan fayl hech qachon tayyor nusxadek ko'rinmaydi. Yiqilishda.partnitraptozalaydi. set -etufayli eski nusxalarni o'chiradiganfindfaqat yangi dump muvaffaqiyatli bo'lganda ishlaydi. Yiqilgan backup yaxshi nusxalarni o'zi bilan olib ketmaydi.-mtime +14yoshi 14 kundan oshgan fayllarni tanlaydi.findkunlarni 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:
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:
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):
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: 5Ma'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?
#!/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$ restore-test.sh
/srv/backups/app-2026-09-21.dump: orders=50000, oxirgi buyurtma: 2026-09-21 08:53:47.372017+00
$ echo $?
0Bo'sh fayl bilan esa skript shovqin ko'taradi:
$ restore-test.sh
pg_restore: error: input file is too short (read 0, expected 5)
$ echo $?
1Qatorlarning 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 konteyneridaappvareportingrollari yo'q. To'liq mashq uchun avvaldocker exec -i "$name" psql -U postgres < globals.sqlqiling va bu ikki flagni olib tashlang — shunda egalik va huquqlar ham sinovdan o'tadi.--exit-on-error— standart holdapg_restorexatolarni 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. trapkonteynerni 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.-dsizpg_restoreSQL matnni stdout'ga chiqaradi: arxiv ichiga qarashning oson yo'li.-C— bazani arxivdagi nom bilan o'zi yaratadi; bunda-d postgresfaqat dastlabki ulanish uchun.--clean --if-exists— obyektlarni qayta yaratishdan oldin o'chiradi;--if-existsyo'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 konteynergadocker compose cp app.dump db:/tmp/app.dumpbilan olib kiring.-t orders— faqat shu jadval va uning ma'lumoti. Indeks va constraint'lar kelmaydi: sinovdaordersning primary key'i tiklanmadi.-lva-L— mundarijani faylga chiqarib, keraksiz qatorlarni o'chirib, faqat qolganini tiklash.
Qayerda va qancha saqlash#
- Baza bilan bir diskdagi nusxa
DROP TABLEdan 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_dumpo'qish va yozishni to'smaydi: izchil suratni bitta tranzaksiya snapshot'i orqali oladi. Lekin jadvallargaACCESS SHAREqulfini qo'yadi. Dump davomidaACCESS EXCLUSIVEtalab qiladigan buyruqlar —ALTER TABLEning 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#
pg_dumpni ataylab buzing (-Uga noto'g'ri nom yozing): skript noldan farqli kod bilan chiqishi, eski nusxalar esa joyida qolishi kerak.restore-test.shhar backup'dan keyin ishlaydi va uning kodi kuzatiladi.- Oyiga bir marta to'liq mashq qiling:
globals.sql, keyin--no-ownersizpg_restore. Tiklash vaqtini yozib qo'ying. - 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:
pg_dump, pg_restore va psql buyruqlari qo'l xotirasiga o'tishi uchun: