Yedeğiniz sıfır baytlık bir dosya ve kimse fark etmedi
Yedekler sessizce düşer: container'da eksik araç, yetki sorunu, dolan disk. Boyutu ve satır sayısını kontrol edin, geri yüklemeyi programa bağlayın.
Veriyle ilgili bir şey ters gider, bir migration kolonu düşürür ya da bir iş silmemesi gereken satırları siler ve biri yedek dizinini açar. Dosyalar oradadır, her gece bir tane, adları doğru, aylar öncesine kadar gider. En yenisi yirmi bayttır. Bir öncekisi de öyle. Son gerçek yedek beş hafta öncesindendir, yani birinin container imajını yeniden kurduğu tarihten.
Yedek neden hiç ses çıkarmadan düşer
Bir yedek işinin yarı yarıya başarılı olma yolu, temiz bir şekilde düşme yolundan fazladır. Sürekli karşıma çıkanlar şunlar.
- Araç imajda yok. Uygulama container'ı veritabanı istemci kütüphanesini taşır, komut satırı dump aracını değil. Yeniden kurulan imaj da elle kurulmuş ne varsa sessizce kaybeder. İş artık "command not found" ile düşer, kimsenin okumadığı bir log'a.
- Pipeline çıkış kodunu yutar.
pg_dump ... | gzip > dosyasıkıştırıcının durumunu döner. Sıkıştırıcı boş akışı neşeyle sıkıştırır ve sıfır döner, betik başarı görür ve yirmi baytlık bir dosya yazar. - Kullanıcının yetkisi yok. Bir tabloyu okuyamayan uygulama kullanıcısıyla alınan dump yarı yolda durur. Parametrelere göre elinize boyutu makul görünen, ama ihtiyacınız olacak tablonun içinde olmadığı kısmi bir dosya geçer.
- Yazma sırasında disk doldu. Dosya vardır, kesiktir ve yedek anında değil geri yükleme anında patlar. Bu, dolan diskin servisi düşürmesi ile aynı sınıftan bir sorun, farkı hasarın dosyaya ihtiyacınız olana kadar görünmemesi.
- Çıktı yolu container'ın içinde. Her şey çalışır, dosya yazılır ve bir sonraki restart'ta yok olur.
Buna bir de sürüm farkını ekleyin. Sunucu yükseltildikten sonra eski istemciyle alınan dump "server version mismatch" diyerek durur ve cron'un ortamında o mesajı okuyacak kimse yoktur. Cron'un PATH'i giriş kabuğununkiyle aynı da değildir, yani elle çalıştırdığınızda sorunsuz koşan komut cron altında bulunamayabilir.
Bunların hepsi bir dosya, bir zaman damgası ve kurduğunuz panelde yeşil bir satır üretir. Dürüst olan tek sinyal dosyanın içeriğidir.
Otuz saniyede nasıl görülür
Zaman içindeki boyut serisi neredeyse her şeyi söyler. Sağlıklı bir yedek yavaş ve öngörülebilir şekilde büyür:
stat -c '%n %s' /backups/*.dump | tail -7/backups/app_20260501.dump 184238336
/backups/app_20260502.dump 184501760
/backups/app_20260503.dump 184703488
/backups/app_20260504.dump 20
/backups/app_20260505.dump 20
/backups/app_20260506.dump 20
/backups/app_20260507.dump 20Üst üste aynı ve minik üç sayı, işin dördünden beri düştüğü anlamına gelir. Sonra en yeni dosyanın geri yüklemeden okunabilir olduğunu kanıtlayın:
pg_restore -l /backups/app_20260507.dump | head -5
# pg_restore: error: did not find magic string in file headerBu mesaj, bir aylık yeşil panel satırından daha kıymetlidir.
Çözüm: yalan söylemeyi reddeden bir iş
Betik kendi işini denetlemek zorunda. Sırayla dört kontrol, her biri gürültüyle patlıyor:
set -euo pipefail
DB=app
OUT="/backups/${DB}_$(date -u +%Y%m%d).dump"
FLOOR=$((150 * 1024 * 1024)) # bundan küçükse gerçek dump değildir
fail() { logger -t backup "FAILED ${DB}: $*"; notify "yedek ${DB} dustu: $*"; exit 1; }
command -v pg_dump >/dev/null || fail "pg_dump bu imajda kurulu degil"
pg_dump -Fc -Z 6 -O -x -d "$DB" -f "$OUT" || fail "pg_dump $? ile cikti"
size=$(stat -c %s "$OUT")
[ "$size" -ge "$FLOOR" ] || fail "dump ${size} bayt, alt sinir ${FLOOR}"
pg_restore -l "$OUT" >/dev/null || fail "dump basligi okunamiyor"
rows=$(psql -At -d "$DB" -c "SELECT count(*) FROM orders")
prev=$(tail -1 /backups/manifest.tsv | cut -f3 || echo 0)
[ "$rows" -ge $(( prev * 95 / 100 )) ] || fail "orders ${prev} iken ${rows} oldu"
printf '%s\t%s\t%s\n' "$(date -u +%F)" "$size" "$rows" >> /backups/manifest.tsvSıkıştırıcıya pipe etmek yerine doğrudan dosya formatına yazmak pipeline sorununu tamamen ortadan kaldırır. Pipe'ın kaçınılmaz olduğu yerde set -o pipefail ve PIPESTATUS kontrolü asgari şarttır. Devam etmeden önce çıkış kodunu okuma alışkanlığı da yeniden başlatmadan önce build çıkış kodunu kontrol etmek yazısında savunduğumun aynısı.
Satır sayısı kontrolü herkesin atladığı ve ilginç arızaları yakalayan kısımdır. Boyutu doğru olan ama bir tabloyu kaybetmiş bir dump, kurtarmayı mahveden dump'tır; sayıları bir önceki koşuyla karşılaştırmak bunu ertesi sabah bulur.
Eski yedeklerin temizliğini de aynı betiğe koyun ama sırayı bozmayın. Yeni dosya kontrollerden geçmeden eskisini silen bir iş, kötü bir günde elinizde hiçbir şey bırakmaz. Doğru sıra şu: al, doğrula, dışarı kopyala, ancak ondan sonra saklama süresi dolanları sil.
Sonra bir kopyayı makineden çıkarın. Üç kopya, iki farklı ortam, biri başka yerde: eski kural hâlâ geçerli. Dışarıdaki kopyanın kimlik bilgileri de aynı sunucuda durmamalı, çünkü sunucuyu ele geçiren biri o anahtarla yedekleri de silebilir; hedef tarafta yazma izni verip silme izni vermemek beş dakikalık bir ayardır. Önemli ayrıntı ise yüklemenin değil varışın doğrulanması:
scp -q "$OUT" "$OFFSITE:/vault/" || fail "disaridaki kopya alinamadi"
remote_size=$(ssh "$OFFSITE" "stat -c %s /vault/$(basename "$OUT")")
[ "$remote_size" = "$size" ] || fail "uzak boyut ${remote_size}, yerel ${size}"Çalıştığını nasıl doğrularsınız: programa bağlı geri yükleme
Yedek bir iddiadır. Geri yükleme kanıttır. Haftada bir kez, atılacak bir veritabanına, süre tutarak:
set -euo pipefail
LATEST=$(ls -t /backups/*.dump | head -1)
SCRATCH="drill_$(date -u +%s)"
createdb "$SCRATCH"
trap 'dropdb "$SCRATCH" || true' EXIT
start=$(date +%s)
pg_restore -j 4 -O -x -d "$SCRATCH" "$LATEST"
echo "geri yukleme $(( $(date +%s) - start )) saniye surdu"
psql -At -d "$SCRATCH" -c "
SELECT 'orders', count(*) FROM orders
UNION ALL SELECT 'users', count(*) FROM users
UNION ALL SELECT 'newest', max(created_at)::text FROM orders;"geri yukleme 214 saniye surdu
orders|1284401
users|48219
newest|2026-05-06 23:58:11+00Mesele tam olarak bu dört satır. Dosya geri yükleniyor, tablolar dolu, en yeni kayıt beş hafta öncesinden değil dün geceden ve kurtarma bilinmeyen bir süre değil üç buçuk dakika sürüyor. Süreyi her hafta kaydedin, çünkü olay anında söz vereceğiniz sayı odur. Tatbikatın çıktısını da manifest dosyasına yazın: böylece "son doğrulanmış geri yükleme ne zamandı" sorusunun cevabı kimsenin hafızasında değil, bakabileceğiniz bir yerde durur.
Son parça bir saat sürer ve hiç yapılmaz: prosedürü yazın. Gerçek parametreleriyle komutlar, kimlik bilgilerinin nerede durduğu, ne kadar sürdüğü, eksiksizliğin nasıl doğrulanacağı ve iş sürerken kime haber verileceği. İçinde "şu kişiye sorun" yazan bir runbook runbook değildir, çünkü o kişi tam da o gün ulaşılamayacak kişidir.
Nelere dikkat etmeli
- Boyut alt sınırı veri büyüdükçe işe yaramaz hale gelir. İki yıl önce koyduğunuz sabite göre değil, bir önceki koşuya göre toleranslı karşılaştırın.
- Şifreli yedeklerin anahtarı da test edilmeli. Çözmeyi atlayan bir tatbikat, gerçekte kullanacağınız kopya hakkında hiçbir şey kanıtlamaz.
- Geri yüklemeyi üretimle aynı sunucuda yapmak aynı disk ve belleği paylaşır; canlıyı yavaşlatan tatbikat bir ay içinde kapatılır. Ayrı bir makine ya da sakin bir saat kullanın.
- Sessiz işin bir kalp atışına ihtiyacı var. Betik haber veremeden düşerse hiçbir şey yayınlanmaz, bu yüzden beklenen başarı sinyali gelmediğinde de alarm üretin. O alarmın da okunmaya değer log'lar yazısındaki disipline ihtiyacı var: cümle değil sebep kodu.
Bütün bunların arkasındaki örüntü şu: yedek sistemi kendi hakkında rapor veriyor ve kendi hakkında verilen rapor elinizdeki en zayıf kanıt. Boyut, satır sayısı ve süresi tutulmuş bir geri yükleme ise işin kendi hakkındaki fikrine bağlı olmayan dış gerçeklerdir. O bir saati ayırın, betiği gürültüyle patlayacak hale getirin ve tatbikatı hafızaya değil takvime bağlayın. İhtiyacınız olduğu gün önemli olan tek soru, son doğrulanmış geri yüklemenin ne kadar yeni olduğudur.
Sorular ve cevaplar
- Dump düşmesine rağmen yedek betiğim neden başarılı diyor?
- Çünkü shell pipeline'ı son komutun çıkış kodunu döner. Dump bir sıkıştırıcıya pipe ediliyorsa, sıkıştırıcı hiçliği sıkıştırmayı başarır ve betik sıfır görür. pipefail açın ya da PIPESTATUS dizisine bakın, üstüne dosyanın boyutunu kontrol etmeden ona yedek demeyin.
- Geri yüklemeyi ne sıklıkla test etmeliyim?
- Prosedür sıkıcı hale gelecek kadar sık, çoğu sistem için bu haftada bir ve otomatik demektir. Haftalık bir tatbikat format sorunlarını, sürüm farklarını ve yetki eksiklerini olaydan çok önce yakalar. Ölçülen süreyi saklayın, çünkü gerçek kurtarma süreniz plandaki değil odur.
- Aynı sunucudaki yedek, yedek sayılır mı?
- Sizi hatalı bir migration'a veya silinmiş bir tabloya karşı korur, başka hiçbir şeye karşı korumaz. Bozulan disk, kaybolan sunucu veya ele geçirilen hesap veritabanını ve yedeği birlikte götürür. En az bir kopyayı farklı donanımda ve farklı kimlik bilgileriyle tutun, üstelik yüklemenin çalıştığını varsaymak yerine kopyanın karşı tarafa ulaştığını doğrulayın.
- Geri yükleme runbook'unda ne olmalı?
- Gerçek parametreleriyle komutların tamamı, kimlik bilgilerinin nerede durduğu, tatbikattan ölçülmüş beklenen süre, verinin eksiksiz olduğunun nasıl doğrulanacağı ve iş sürerken kime haber verileceği. İçinde kimlik bilgilerinin kendisi ya da 'şu kişiye sorun' adımı olmamalı, çünkü o kişi tam da ulaşılamayan kişi olabilir.