İçeriğe geç
academia.sh

Ders 06 / 25

Ölü Satır Temizliği

Silme ve güncellemenin geride bıraktığı ölü sürümler, silinen yerin dosyadan geri gelmemesi, boşluğun neden her zaman yeniden kullanılamadığı, tam yeniden yazmanın maliyeti ve şişme oranının temizlik sıklığı ile açık işlem süresine bağlılığı.

İçindekiler

Önceki ders, bir sürümün ancak hiçbir açık anlık görüntü tarafından görülmediğinde kaldırılabilir hâle geldiğini gösterdi. Kaldırılabilir olmak ile kaldırılmış olmak ayrı şeylerdir. Sürümleri toplayan ayrı bir süreç vardır ve bu süreç, üretilen ölü sürümlere yetişemezse tablo, satır sayısı hiç artmasa bile büyümeye devam eder.

Bu dersin soruları ölçülebilir: silinen satırların yeri nereye gider, o yer ne zaman yeniden kullanılır, kullanılmadığında dosya ne kadar büyür ve bu büyümeyi geri almanın bedeli nedir?

Ölü Satır ve Şişme

Ölü satır (dead tuple), artık hiçbir işlemin görmediği bir satır sürümüdür. İki kaynaktan doğar: silinen satırın kendisi ve güncellenen satırın eski sürümü. Önceki dersteki model gereği güncelleme de bir ölü sürüm bırakır, çünkü fiziksel olarak yeni bir sürüm yazılır.

Şişme (bloat), bir tablonun ya da dizinin kapladığı yerin, içindeki canlı veriyi tutmak için gereken yerden ne kadar büyük olduğudur. Ölçüsü bir orandır: kaplanan sayfa sayısının, aynı verinin sıkıştırılmış hâlde kaplayacağı sayfa sayısına bölümü.

Şişme kendi başına bir hata değildir; bir miktarı kaçınılmazdır ve yararlıdır, çünkü boşluk yeni satırlar için yer bırakır. Sorun, oranın büyümeyi sürdürmesidir. Şişmiş bir tablonun taranması daha çok sayfa okur, tampon havuzunda daha çok yer tutar ve yedekleri büyütür — hepsi aynı veri için.

Silinen Yer Nereye Gider

Aşağıdaki koşum dört aşamayı aynı üç ölçüyle izler: satır sayısı, sayfa sayısı ve sayfa içi doluluk oranı. Dosya boyutu her aşamada ayrıca yazdırılır.

rm -f s.db
cat > durum.sql <<'EOF'
SELECT (SELECT count(*) FROM odunc) AS satir,
       (SELECT page_count FROM pragma_page_count) AS sayfa,
       (SELECT round(100.0*sum(payload)/sum(pgsize),1) FROM dbstat WHERE name='odunc') AS doluluk;
EOF
sqlite3 s.db <<'SQL'
.mode column
.headers on
CREATE TABLE odunc(odunc_id INTEGER PRIMARY KEY, kitap_id INT, uye_id INT,
                   alis_tarihi TEXT, iade_tarihi TEXT);
INSERT INTO odunc SELECT n, 1+(n*7)%200000, 1+(n*13)%120000,
       date('2018-01-01','+'||((n*37)%2437)||' days'), NULL
  FROM (WITH RECURSIVE s(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM s WHERE n<400000) SELECT n FROM s);
.print '--- baslangic ---'
.read durum.sql
.shell echo "dosya: $(wc -c < s.db)"
DELETE FROM odunc WHERE alis_tarihi < '2020-01-01';
.print '--- 119822 satir silindi ---'
.read durum.sql
.shell echo "dosya: $(wc -c < s.db)"
INSERT INTO odunc SELECT 500000+n, 1+(n*7)%200000, 1+(n*13)%120000, '2025-01-01', NULL
  FROM (WITH RECURSIVE s(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM s WHERE n<100000) SELECT n FROM s);
.print '--- 100000 yeni satir (artan anahtarla) ---'
.read durum.sql
.shell echo "dosya: $(wc -c < s.db)"
VACUUM;
.print '--- VACUUM sonrasi ---'
.read durum.sql
.shell echo "dosya: $(wc -c < s.db)"
SQL
--- baslangic ---
satir   sayfa  doluluk
------  -----  -------
400000  2723   77.3   
dosya:  11153408
--- 119822 satir silindi ---
satir   sayfa  doluluk
------  -----  -------
280178  2723   54.2   
dosya:  11153408
--- 100000 yeni satir (artan anahtarla) ---
satir   sayfa  doluluk
------  -----  -------
380178  3403   58.8   
dosya:  13938688
--- VACUUM sonrasi ---
satir   sayfa  doluluk
------  -----  -------
380178  2589   77.3   
dosya:  10604544

İkinci aşama ilk sonucu veriyor. Satırların yaklaşık üçte biri silindi; sayfa sayısı bir tane bile azalmadı, dosya boyutu bir bayt bile küçülmedi. Değişen tek şey doluluk: %77,3’ten %54,2’ye düştü. Silme, sayfaları serbest bırakmadı — sayfaların içini boşalttı.

Bu davranışın nedeni doğrudandır. Silinen satırlar dosyanın her yerine dağılmıştır; bir sayfanın tamamen boşalması ancak o sayfadaki bütün satırların silinmesiyle olur. Kısmen boşalmış bir sayfa dosyadan çıkarılamaz, çünkü Fiziksel Depolama Düzeni dersinde ölçülen ilişki gereği dosya, sayfaların art arda dizilmesinden ibarettir; ortadaki bir sayfayı çıkarmak sonrasındaki her şeyin yerini değiştirmek demektir.

Boşluk Neden Her Zaman Yeniden Kullanılmaz

Üçüncü aşama daha ilginç bir sonuç veriyor. Tabloda 119.822 satırlık boşluk varken 100.000 satır eklendi ve dosya küçülmek bir yana büyüdü: 2.723 sayfadan 3.403 sayfaya.

Nedeni anahtar seçimidir. odunc_id sürekli artan bir değerdir ve yeni satırlar ağacın en sağ ucuna eklenir. Silinen satırların bıraktığı boşluklar ise ağacın ortalarındadır — yeni anahtarlar oraya ait değildir. Motor, sıralı bir yapıda bir anahtarı ait olmadığı yere koyamaz.

Bu, şema tasarımıyla depolama arasındaki en somut bağlardan biridir. Sürekli artan bir anahtarla eklenen ve eski kayıtları tarihe göre silinen bir tabloda boşluk kendiliğinden kapanmaz: yeni veri hep sona yazılır, boşluk hep ortada kalır. Aynı tablo rastgele anahtarlarla doldurulsaydı boşlukların bir kısmı doğal olarak kullanılırdı — bu kez de sayfa bölünmeleri artardı. Bölümleme dersi bu soruna yapısal bir cevap getirecek: eski veriyi silmek yerine, eski bölümün tamamını kaldırmak.

Boşluğun Geri Kazanılması

Dördüncü aşama VACUUM çalıştırıyor ve tablo 3.403 sayfadan 2.589 sayfaya iniyor; doluluk %77,3’e, yani ilk günkü değerine dönüyor. Şişme oranı 3403 / 2589 ≈ 1,31 idi; yeniden yazma onu 1,00’e indirdi.

Bu işlemin ne yaptığını bilmek, ne zaman kullanılacağını bilmekten önce gelir: tablo baştan sona okunur ve yeni bir yere sıkıştırılmış olarak yazılır, sonra eskisinin yeri bırakılır. Üç bedeli vardır.

Kilit. Tam yeniden yazma sırasında tablo (çoğu motorda) değiştirilemez, bazılarında hiç okunamaz. Çalışan bir sistemde bu, planlanmış bir kesinti demektir.

Geçici alan. Yeni kopya yazılırken eski kopya hâlâ yerindedir; işlem sırasında tablonun boyutu kadar ek yer gerekir. Diski dolmak üzere olan bir sunucuda yer açmak için yeniden yazma çalıştırmak, elde olmayan bir alanı gerektirdiği için işe yaramaz.

Süre. Maliyet tablonun tamamıyla orantılıdır, geri kazanılacak boşlukla değil.

Bu yüzden motorlar iki hafifletilmiş yol sunar: boşluğu kilitlemeden yeniden kullanılabilir hâle getiren arka plan temizliği ve tabloyu çalışır durumda tutarak yeni bir kopya kuran yeniden düzenleme. Adları ve sınırları motora özgüdür; tam yeniden yazma, kural olarak son çaredir.

Temizliğin Dengesi

Asıl yönetim sorusu “ne zaman yeniden yazmalı” değil, “şişmenin belli bir oranın üstüne çıkmasını ne engelliyor” sorusudur. Aşağıdaki model iki değişkeni ayrı ayrı değiştirir: temizliğin çalışma sıklığı ve açık bir işlemin süresi. Gerçek bir temizlik sürecinin kendisi değildir; ölçüde tuttuğu şey, ayrılan en yüksek yerin canlı veriye oranıdır.

cat > temizlik.mjs <<'JS'
// Model: olu surumlerin birikmesi ile temizligin dengesi. Gercek motorun kendisi degil.
// Her adimda GUNCELLEME satir guncellenir; her guncelleme bir olu surum birakir.
// Temizlik ARALIK adimda bir calisir ve yalnizca en eski acik anlik goruntuden
// daha eski olu surumleri toplar. Ayrilan yer isletim sistemine geri verilmez.
function calistir({ aralik, uzunIslem = null, adim = 600 }) {
  const CANLI = 100000, GUNCELLEME = 2000;
  let olu = [];                                   // her ogesi: olusturuldugu adim
  let ayrilan = CANLI, enYuksek = CANLI;
  for (let i = 1; i <= adim; i++) {
    for (let k = 0; k < GUNCELLEME; k++) olu.push(i);
    ayrilan = Math.max(ayrilan, CANLI + olu.length);
    enYuksek = Math.max(enYuksek, ayrilan);
    if (i % aralik === 0) {
      const sinir = (uzunIslem && i >= uzunIslem.bas && i <= uzunIslem.bit) ? uzunIslem.bas : i;
      olu = olu.filter((d) => d >= sinir);         // acik anlik goruntuden eskiler toplanir
    }
  }
  return { kalan: olu.length, sisme: enYuksek / CANLI };
}
console.log('— temizlik sıklığı —');
console.log('aralık (adım)   kalan ölü sürüm   şişme oranı');
for (const a of [1, 5, 25, 100, 600])
  { const s = calistir({ aralik: a });
    console.log(String(a).padStart(13), String(s.kalan).padStart(17), (s.sisme.toFixed(2) + '×').padStart(14)); }
console.log('— aynı temizlik (aralık 5), açık bir uzun işlemle —');
for (const uzunluk of [0, 100, 300, 600]) {
  const s = calistir({ aralik: 5, uzunIslem: uzunluk ? { bas: 1, bit: uzunluk } : null });
  console.log(`uzun işlem ${String(uzunluk).padStart(3)} adım →`, String(s.kalan).padStart(7),
              'ölü sürüm,', (s.sisme.toFixed(2) + '×').padStart(6), 'şişme');
}
JS
node temizlik.mjs
— temizlik sıklığı —
aralık (adım)   kalan ölü sürüm   şişme oranı
            1              2000          1.04×
            5              2000          1.12×
           25              2000          1.52×
          100              2000          3.02×
          600              2000         13.00×
— aynı temizlik (aralık 5), açık bir uzun işlemle —
uzun işlem   0 adım →    2000 ölü sürüm,  1.12× şişme
uzun işlem 100 adım →    2000 ölü sürüm,  3.10× şişme
uzun işlem 300 adım →    2000 ölü sürüm,  7.10× şişme
uzun işlem 600 adım → 1200000 ölü sürüm, 13.00× şişme

Birinci tablo beklendiği gibi: temizlik ne kadar seyrek çalışırsa ölü sürümler o kadar birikir ve ayrılan en yüksek yer o kadar büyür. Her adımda çalışan bir temizlik şişmeyi 1,04’te tutuyor; altı yüz adımda bir çalışan ise 13,00’e çıkarıyor. Sona kalan ölü sürüm sayısının hepsinde aynı olması yanıltmasın — ölçülen şey son durum değil, süreç boyunca ulaşılan en yüksek yerdir; yer bir kez ayrıldıktan sonra geri verilmez.

İkinci tablo asıl uyarıyı veriyor. Temizlik sıklığı hiç değişmedi — her beş adımda bir çalışıyor — ama üç yüz adım açık kalan tek bir işlem şişmeyi 1,12’den 7,10’a çıkardı. Temizlik çalışıyor, ancak toplayacak bir şey bulamıyor: o işlemin anlık görüntüsünden sonra üretilen bütün ölü sürümler hâlâ görünebilir durumda.

Sonuç iki cümlede toplanır. Şişmenin üst sınırını belirleyen şey, temizliğin çalışma sıklığı ile en eski açık işlemin yaşından hangisi büyükse odur. Bu yüzden şişme sorunlarının araştırılması, temizlik ayarlarından değil, uzun süredir açık kalan işlemlerin listesinden başlar.

Özet

  • Ölü satır, artık hiçbir işlemin görmediği bir satır sürümüdür; hem silme hem güncelleme üretir. Şişme, kaplanan yerin gerekli yere oranıdır.
  • Ölçümde satırların üçte biri silindiğinde sayfa sayısı ve dosya boyutu hiç değişmedi; yalnız sayfa içi doluluk %77,3’ten %54,2’ye düştü.
  • Sürekli artan anahtarla eklenen yeni satırlar ortadaki boşlukları kullanamaz; 119.822 satırlık boşluk varken 100.000 satır eklendiğinde dosya 2.723 sayfadan 3.403 sayfaya çıktı.
  • Tam yeniden yazma doluluğu ilk günkü değere döndürdü (2.589 sayfa), ancak kilit, tablo boyutu kadar geçici alan ve tablo büyüklüğüyle orantılı süre gerektirir.
  • Modelde şişme oranını belirleyen iki etken ayrıştı: temizliğin sıklığı ve en eski açık işlemin yaşı; üç yüz adım açık kalan tek bir işlem şişmeyi 1,12’den 7,10’a çıkardı.

Sonraki Adım

Bu kursta şimdiye kadar sorulan her soru bir sayıyla yanıtlandı: sayfa sayısı, doluluk, çerçeve sayısı, isabet oranı. Bu sayılar ortada durmuyor; motor kendi yapısı hakkındaki bilgiyi sıradan tablolarda tutar ve sıradan sorgularla verir. Sonraki ders bu bilgiyi ele alır: tabloların, sütunların, dizinlerin ve kısıtların tanımının nerede saklandığı, oradan hangi yönetim sorularının yanıtlanabileceği ve şemayı sorgulayarak yazılan denetimlerin elle tutulan listelerden neden üstün olduğu.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat