Ders 16 / 25
Yedekleme Türleri
Mantıksal, fiziksel ve artımlı yedeğin ölçülen maliyetleri: dosya boyutu ve alma süresi karşılaştırması, geri yüklenen kopyanın kaynakla eşitliğinin içerik karmasıyla kanıtlanması, su işareti tabanlı artımlı yedeğin sınırı.
İçindekiler
Önceki ders satır düzeyi güvenliği kurarken bir noktayı açık bıraktı: bütün o koruma
çalışan sisteme aitti. Yedek dosyası politikasız bir kopyadır, ve daha temel bir şeydir —
veritabanının var olmayı sürdürmesinin tek güvencesidir. Disk arızası, yanlış yazılmış bir
DELETE, hatalı bir şema göçü ya da silinen bir dizin: hiçbiri yetki çizelgesiyle
önlenmez.
Bu ders yedeğin ne olduğunu değil, hangi biçimlerde alındığını ve her biçimin ne kadara mal olduğunu ele alıyor. Kütüphane veritabanı üzerinde üç biçim ölçülecek ve geri yüklenen kopyanın kaynakla eşit olduğu sözle değil sorguyla gösterilecek.
Mantıksal ve Fiziksel Yedek
Mantıksal yedek (logical backup) veritabanının içeriğini yeniden üretecek deyimlerden
oluşur: tablo tanımları ve satırları anlatan CREATE ve INSERT deyimleri. Metin
dosyasıdır, okunabilir, düzenlenebilir ve farklı bir motora ya da farklı bir sürüme
yüklenebilir.
Fiziksel yedek (physical backup) veri dosyalarının kendisinin kopyasıdır. Sayfa düzeyinde birebir kopyadır; anlamını yalnız aynı motor okur. Buna karşılık alınması ucuzdur, çünkü hiçbir şey yeniden üretilmez — baytlar taşınır.
İkisi arasındaki seçim bir zevk meselesi değildir; ölçülebilir bir ödünleşimdir.
Üç Biçimin Ölçümü
Aşağıdaki blok kütüphane veritabanını kurar, ardından dört yedekleme yolunu aynı veri üzerinde ölçer. Süre ve boyut değerleri makineye, dosya sistemine ve sıkıştırma kütüphanesine bağlıdır; sizde farklı sayılar çıkacaktır. Anlamlı olan mutlak değerler değil, sütunlar arasındaki oranlardır.
cat > kur.sql <<'SQL' CREATE TABLE sube(id INTEGER PRIMARY KEY, ad TEXT NOT NULL); CREATE TABLE uye(id INTEGER PRIMARY KEY, ad TEXT NOT NULL, sube_id INT NOT NULL); CREATE TABLE kitap(id INTEGER PRIMARY KEY, baslik TEXT NOT NULL, sube_id INT NOT NULL); CREATE TABLE odunc(id INTEGER PRIMARY KEY, kitap_id INT NOT NULL, uye_id INT NOT NULL, sube_id INT NOT NULL, alis TEXT NOT NULL, iade TEXT); INSERT INTO sube VALUES (1,'Merkez'),(2,'Sahil'),(3,'Tepe'); WITH RECURSIVE s(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM s WHERE n<20000) INSERT INTO uye(id,ad,sube_id) SELECT n,'Uye-'||n,(n%3)+1 FROM s; WITH RECURSIVE s(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM s WHERE n<50000) INSERT INTO kitap(id,baslik,sube_id) SELECT n,'Kitap-'||n,(n%3)+1 FROM s; WITH RECURSIVE s(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM s WHERE n<300000) INSERT INTO odunc(id,kitap_id,uye_id,sube_id,alis,iade) SELECT n,(n%50000)+1,(n%20000)+1,(n%3)+1, date('2023-01-01','+'||(n%900)||' day'), CASE WHEN n%4=0 THEN NULL ELSE date('2023-01-01','+'||((n%900)+14)||' day') END FROM s; CREATE INDEX odunc_uye ON odunc(uye_id); CREATE INDEX odunc_sube_alis ON odunc(sube_id, alis); SQL rm -f kutuphane.db sqlite3 kutuphane.db < kur.sql cat > olc.mjs <<'EOF' import { execSync } from "node:child_process"; import { statSync, rmSync } from "node:fs"; const olc = (ad, komut, cikti) => { rmSync(cikti, { force: true }); const t0 = process.hrtime.bigint(); execSync(komut); const ms = Number(process.hrtime.bigint() - t0) / 1e6; return { ad, ms, bayt: statSync(cikti).size }; }; const kaynak = statSync("kutuphane.db").size; const olcumler = [ olc("mantiksal (.dump)", "sqlite3 kutuphane.db .dump > mantiksal.sql", "mantiksal.sql"), olc("mantiksal + sikistirma", "sqlite3 kutuphane.db .dump | gzip -9 > mantiksal.sql.gz", "mantiksal.sql.gz"), olc("fiziksel (dosya kopyasi)", "cp kutuphane.db fiziksel.db", "fiziksel.db"), olc("fiziksel (tutarli anlik goruntu)", "sqlite3 kutuphane.db \"VACUUM INTO 'anlik.db'\"", "anlik.db"), ]; console.log("kaynak veritabani: " + kaynak.toLocaleString("tr-TR") + " bayt\n"); console.log("yedek bicimi | bayt | kaynagin % | sure (ms)"); console.log("---------------------------------|----------|------------|----------"); for (const o of olcumler) { console.log( o.ad.padEnd(32) + " | " + String(o.bayt).padStart(8) + " | " + (100 * o.bayt / kaynak).toFixed(1).padStart(10) + " | " + o.ms.toFixed(0).padStart(8) ); } EOF node olc.mjs
kaynak veritabani: 21.770.240 bayt yedek bicimi | bayt | kaynagin % | sure (ms) ---------------------------------|----------|------------|---------- mantiksal (.dump) | 24561891 | 112.8 | 210 mantiksal + sikistirma | 3983716 | 18.3 | 803 fiziksel (dosya kopyasi) | 21770240 | 100.0 | 9 fiziksel (tutarli anlik goruntu) | 21770240 | 100.0 | 58
Dört satır dört ayrı karar anlatıyor.
Mantıksal yedek kaynaktan büyük çıktı. Sezgiye aykırı görünür ama nedeni açıktır: sayıların ve tarihlerin ikili gösterimi metne çevrildiğinde şişer, her satır için ekleme deyiminin metni yeniden yazılır. Buna karşılık dizinler dosyaya yazılmaz — yalnız tanımları yazılır — bu da kısmen dengeler.
Sıkıştırılmış mantıksal yedek kaynağın beşte birinin altına indi. Metin yedeği son derece sıkıştırılabilirdir, çünkü aynı deyim kalıbı satır sayısı kadar tekrarlanır. Bunun bedeli süredir: sıkıştırma bu ölçümde yedek alma süresini birkaç katına çıkardı.
Dosya kopyası, mantıksal yedekten belirgin biçimde hızlıdır. Aradaki fark bir ölçek sorusudur: mantıksal yedek her satırı okur, metne çevirir ve yazar; dosya kopyası yalnız baytları taşır.
Fiziksel Yedeğin Tutarlılık Sorunu
Yukarıdaki tabloda iki fiziksel satır var ve boyutları aynı. Aralarındaki fark boyutta değil, güvenilirliktedir.
Çalışan bir veritabanının dosyasını doğrudan kopyalamak tutarsız kopya üretebilir. Kopyalama sürerken motor dosyanın başına ve sonuna yazmayı sürdürür; kopyanın başı bir ana, sonu başka bir ana aittir. Ortaya çıkan dosya hiçbir zaman var olmamış bir duruma karşılık gelir ve açıldığında bozuk görünür — ya da daha kötüsü, açılır ve yanlış veri verir.
Bu yüzden fiziksel yedek ya veritabanı durdurulmuşken alınır, ya da motorun kendi tutarlı anlık görüntü aracıyla alınır. Tablodaki dördüncü satır bu araçtır: motor, tek bir tutarlı noktadan okuyarak yeni bir dosya üretir. Ölçümde ham dosya kopyasının birkaç katı sürmesinin nedeni budur; ödenen fark tutarlılığın bedelidir.
Aracın adı ve çağrılış biçimi motora göre değişir. Değişmeyen kural: çalışan bir
veritabanının dosyası cp ile yedeklenmez.
Artımlı Yedek
Tam yedek her seferinde bütün veriyi taşır. Veritabanı büyüdükçe bu, hem sürede hem saklama alanında dayanılmaz olur. Artımlı yedek (incremental backup), son yedekten bu yana değişen kısmı taşır.
Değişenin ne olduğunu belirlemenin en yalın yolu bir su işaretidir (watermark): son yedekte yer alan en büyük birincil anahtar değeri. Aşağıdaki blok tam yedeği alıyor, sonra altı bin yeni ödünç kaydı ekliyor, ardından yalnız su işaretinden sonraki satırları çıkarıyor ve iki dosyayı karşılaştırıyor.
Blok aynı zamanda geri yüklemeyi de yapıyor. Geri yükleme yıkıcı bir işlemdir: hedef
veritabanının üzerine yazar. Buradaki rm -f ve geri yükleme komutları yalnız bu blokta
üretilen geçici dosyalar üzerinde çalışır; gerçek bir kurulumda geri yükleme hedefi her
zaman yeni ve boş bir veritabanıdır, mevcut olan değil.
cat > kur.sql <<'SQL' CREATE TABLE sube(id INTEGER PRIMARY KEY, ad TEXT NOT NULL); CREATE TABLE uye(id INTEGER PRIMARY KEY, ad TEXT NOT NULL, sube_id INT NOT NULL); CREATE TABLE kitap(id INTEGER PRIMARY KEY, baslik TEXT NOT NULL, sube_id INT NOT NULL); CREATE TABLE odunc(id INTEGER PRIMARY KEY, kitap_id INT NOT NULL, uye_id INT NOT NULL, sube_id INT NOT NULL, alis TEXT NOT NULL, iade TEXT); INSERT INTO sube VALUES (1,'Merkez'),(2,'Sahil'),(3,'Tepe'); WITH RECURSIVE s(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM s WHERE n<20000) INSERT INTO uye(id,ad,sube_id) SELECT n,'Uye-'||n,(n%3)+1 FROM s; WITH RECURSIVE s(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM s WHERE n<50000) INSERT INTO kitap(id,baslik,sube_id) SELECT n,'Kitap-'||n,(n%3)+1 FROM s; WITH RECURSIVE s(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM s WHERE n<300000) INSERT INTO odunc(id,kitap_id,uye_id,sube_id,alis,iade) SELECT n,(n%50000)+1,(n%20000)+1,(n%3)+1, date('2023-01-01','+'||(n%900)||' day'), CASE WHEN n%4=0 THEN NULL ELSE date('2023-01-01','+'||((n%900)+14)||' day') END FROM s; CREATE INDEX odunc_uye ON odunc(uye_id); CREATE INDEX odunc_sube_alis ON odunc(sube_id, alis); SQL rm -f kutuphane.db geri.db tam.sql artimli.sql sqlite3 kutuphane.db < kur.sql sqlite3 kutuphane.db .dump > tam.sql sqlite3 kutuphane.db "WITH RECURSIVE s(n) AS (SELECT 300001 UNION ALL SELECT n+1 FROM s WHERE n<306000) INSERT INTO odunc(id,kitap_id,uye_id,sube_id,alis,iade) SELECT n,(n%50000)+1,(n%20000)+1,(n%3)+1,date('2025-06-01','+'||(n%30)||' day'),NULL FROM s;" sqlite3 kutuphane.db <<'ART' > artimli.sql .mode insert odunc SELECT * FROM odunc WHERE id > 300000 ORDER BY id; ART sqlite3 geri.db < tam.sql sqlite3 geri.db < artimli.sql echo "tam yedek : $(wc -c < tam.sql | tr -d ' ') bayt" echo "artimli yedek : $(wc -c < artimli.sql | tr -d ' ') bayt" echo "kaynak satir : $(sqlite3 kutuphane.db 'SELECT COUNT(*) FROM odunc;')" echo "geri satir : $(sqlite3 geri.db 'SELECT COUNT(*) FROM odunc;')" echo "kaynak karma : $(sqlite3 kutuphane.db '.sha3sum --schema --sha3-256')" echo "geri karma : $(sqlite3 geri.db '.sha3sum --schema --sha3-256')"
tam yedek : 24561891 bayt artimli yedek : 381792 bayt kaynak satir : 306000 geri satir : 306000 kaynak karma : d97019d7e61335911bef2079db99030dd2f5e9938d4e24a769d85e56edef1eb6 geri karma : d97019d7e61335911bef2079db99030dd2f5e9938d4e24a769d85e56edef1eb6
Artımlı yedek tam yedeğin yüzde iki kadarı. Günlük tam yedek yerine haftalık tam ve günlük artımlı almanın nedeni bu orandır.
Eşitliğin Kanıtı
Son iki satır bu dersin asıl noktasıdır. Karma değeri, veritabanının içeriğinden hesaplanır: tablo adları, sütun tanımları ve satırların değerleri. Dosyanın fiziksel düzeninden, boş sayfalarından ya da satırların diskteki sırasından etkilenmez.
İki karmanın eşit çıkması, geri yüklenen veritabanının kaynakla mantıksal olarak aynı olduğunu gösterir. Satır saymak bunu göstermez — sayılar tutup değerler bozuk olabilir. Gözle bakmak hiç göstermez.
Bu alışkanlığın adı vardır ve sonraki derslerin ikisi buna ayrılmıştır: bir yedeğin geçerliliği, alındığı anda değil geri yüklendiği anda bilinir. “Yedek alıyoruz” cümlesi bir güvence değildir; “yedeği geri yükleyip karmasını karşılaştırıyoruz” cümlesi bir güvencedir.
Su İşaretinin Kaçırdıkları
Yukarıdaki artımlı yedek yalnız eklenen satırları yakalar. Su işareti birincil anahtar üzerinde çalışır ve birincil anahtar artan değerler üretir; dolayısıyla:
- Su işaretinden önceki bir satır güncellenmişse yakalanmaz.
- Su işaretinden önceki bir satır silinmişse yakalanmaz.
- Şema değişmişse yakalanmaz.
Kütüphane örneğinde bu üç boşluk somuttur. Bir kitabın iadesi, eski bir ödünç satırının
iade sütununu günceller — su işaretinden önceki bir satırdır ve artımlı yedeğe girmez.
Tam yedek ile artımlı yedeği birleştirerek geri yüklenen veritabanında o kitap hâlâ
ödünçte görünür.
Değişim zamanı taşıyan bir sütun eklemek güncellemeleri yakalar ama silmeleri yakalamaz — silinen satırın değişim zamanı da silinmiştir. Silmeleri yakalamanın yolu, satırların değil işlemlerin kaydını tutmaktır. Bu kayıt zaten vardır: motorun kalıcılık için yazdığı günlük.
Yedek Planının Üç Sayısı
Bir yedekleme planı üç sayıyla anlatılır.
Kurtarma noktası hedefi (recovery point objective), kabul edilen en fazla veri kaybı süresidir. Günde bir yedek alınıyorsa bu sayı yirmi dört saattir: arıza yedekten hemen önce olursa bir günlük ödünç kaydı kaybolur.
Kurtarma süresi hedefi (recovery time objective), sistemin yeniden çalışır hâle gelmesi için kabul edilen en uzun süredir. Bu sayı yedeğin boyutuyla değil, geri yükleme süresiyle ilgilidir — ve yukarıdaki ölçümde geri yükleme, yedek almadan uzun sürdü. Mantıksal yedeğin ucuz saklanması, pahalı geri yüklenmesiyle ödenir.
Saklama süresi (retention), yedeklerin ne kadar tutulacağıdır. Yalnız en son yedeği tutmak tehlikelidir: bozulma fark edilmeden yedeklenmişse, tek yedek de bozuktur. Yedekler farklı zamanlardan ve farklı ortamlardan oluşan bir küme olarak tutulur; en az bir kopyası veritabanının bulunduğu makineden ayrı bir yerde durur.
Özet
- Mantıksal yedek deyimlerden oluşur, taşınabilirdir ve iyi sıkışır; fiziksel yedek dosyaların kopyasıdır, alınması hızlıdır ve aynı motora bağlıdır.
- Mantıksal yedek kaynaktan büyük olabilir; sıkıştırma boyutu birkaç kata düşürür, bedeli süredir.
- Çalışan bir veritabanının dosyası doğrudan kopyalanamaz; tutarlı fiziksel yedek ya durdurulmuş sistemden ya motorun anlık görüntü aracıyla alınır.
- Artımlı yedek tam yedeğin küçük bir bölümü kadar yer tutar; su işareti tabanlı artımlı yedek eklemeleri yakalar, güncelleme ve silmeleri kaçırır.
- Geri yüklenen kopyanın kaynakla eşitliği içerik karmasıyla kanıtlanır; satır saymak yeterli değildir.
- Yedek planı kurtarma noktası hedefi, kurtarma süresi hedefi ve saklama süresiyle tanımlanır.
Sonraki Adım
Su işareti tabanlı artımlı yedeğin üç boşluğu — güncellemeler, silmeler ve şema değişiklikleri — hepsi aynı nedenden kaynaklanıyordu: satırların son hâline bakmak, aralarında ne olduğunu anlatmaz. Aradaki her değişikliği sırayla kaydeden bir kaynak zaten var; motorun kalıcılık için yazdığı günlük. Sonraki ders bu günlüğün yedekle birlikte kullanılmasını ele alıyor: bir tam yedeğin üzerine günlük kayıtlarını belirli bir ana kadar uygulayarak veritabanını dün öğlen on birdeki hâline döndürmek.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.