Ders 02 / 25
Fiziksel Depolama Düzeni
Sayfanın motorun en küçük okuma ve yazma birimi olması, sayfa boyutu ile sayfa sayısının dosya boyutunu tam olarak belirlemesi, sayfa içi doluluk ve sayfa başına satır sayısının ölçülmesi, taşma sayfaları ve tablo alanı kavramı.
İçindekiler
Önceki ders sayfayı bellekteki bir birim olarak kullandı: tampon havuzuna giren, atılan, isabet ya da ıska üreten bir şey. Sayfanın kendisinin ne olduğu sorulmadı. Neden satır satır değil de sayfa sayfa okunuyor, bir sayfaya kaç satır giriyor, bir satır sayfaya sığmazsa ne oluyor?
Bu dersin sorusu verinin diskteki hâlidir. Ölçülecek üç ilişki vardır: sayfa boyutu ile dosya boyutu arasındaki ilişki, sayfa içindeki doluluk ve tablo ile dizinin toplam yer içindeki payı. Üçü de sayılabilir; hiçbiri tahmine bırakılmaz.
Sayfa Neden Vardır
Sayfa (page), motorun disk ile bellek arasında taşıdığı en küçük birimdir. Tek bir satır okunmak istendiğinde bile o satırın bulunduğu sayfanın tamamı okunur.
Nedeni donanımdadır. Bilgisayarlar Nasıl Çalışır kursundaki bellek hiyerarşisi tartışmasının aynısıdır bu: disk erişiminin sabit bir maliyeti vardır ve bu maliyet, okunan bayt sayısıyla değil, erişim sayısıyla ölçülür. Yüz baytlık bir satırı okumak için yapılan iş ile dört kilobaytlık bir sayfayı okumak için yapılan iş neredeyse aynıdır. Sabit boyutlu bir birim ayrıca defter tutmayı kolaylaştırır: tampon havuzundaki her yuva aynı büyüklüktedir, bir sayfanın yerine başka bir sayfa parçalanma üretmeden konabilir.
Sayfa boyutu veritabanı oluşturulurken belirlenir ve sonradan değiştirilmesi verinin yeniden yazılmasını gerektirir. Küçük sayfa, seçici okumalarda daha az boşa taşınan bayt demektir; büyük sayfa, sıralı taramalarda daha az erişim ve daha az defter tutma demektir.
Sayfa Sayısı Dosya Boyutunu Belirler
İlişki tam olarak ölçülebilir. Aşağıdaki blok, önceki kurslardan devam eden kütüphane veri kümesini kurar ve üç sayıyı yan yana koyar.
rm -f kutuphane.db cat > kurulum.sql <<'SQL' CREATE TABLE sube ( sube_id INTEGER PRIMARY KEY, ad TEXT NOT NULL, sehir TEXT NOT NULL ); CREATE TABLE uye ( uye_id INTEGER PRIMARY KEY, ad TEXT NOT NULL, sehir TEXT NOT NULL, kayit_tarihi TEXT NOT NULL ); CREATE TABLE kitap ( kitap_id INTEGER PRIMARY KEY, baslik TEXT NOT NULL, yazar TEXT NOT NULL, yil INTEGER NOT NULL, sube_id INTEGER NOT NULL ); CREATE TABLE odunc ( odunc_id INTEGER PRIMARY KEY, kitap_id INTEGER NOT NULL, uye_id INTEGER NOT NULL, alis_tarihi TEXT NOT NULL, iade_tarihi TEXT ); INSERT INTO sube (sube_id, ad, sehir) VALUES (1,'Merkez','Ankara'),(2,'Bahcelievler','Ankara'),(3,'Kadikoy','Istanbul'), (4,'Beyoglu','Istanbul'),(5,'Konak','Izmir'),(6,'Nilufer','Bursa'), (7,'Selcuklu','Konya'),(8,'Cankaya','Ankara'); INSERT INTO uye (uye_id, ad, sehir, kayit_tarihi) WITH RECURSIVE sayac(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM sayac WHERE n < 120000) SELECT n, 'Uye ' || n, CASE n % 5 WHEN 0 THEN 'Ankara' WHEN 1 THEN 'Istanbul' WHEN 2 THEN 'Izmir' WHEN 3 THEN 'Bursa' ELSE 'Konya' END, date('2015-01-01', '+' || (n % 3200) || ' days') FROM sayac; INSERT INTO kitap (kitap_id, baslik, yazar, yil, sube_id) WITH RECURSIVE sayac(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM sayac WHERE n < 200000) SELECT n, 'Kitap ' || n, 'Yazar ' || (n % 4000), 1950 + (n % 75), 1 + (n % 8) FROM sayac; INSERT INTO odunc (odunc_id, kitap_id, uye_id, alis_tarihi, iade_tarihi) WITH RECURSIVE sayac(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM sayac WHERE n < 2000000) SELECT n, 1 + ((n * 7) % 200000), 1 + ((n * 13) % 120000), date('2018-01-01', '+' || ((n * 37) % 2437) || ' days'), CASE WHEN n % 9 = 0 THEN NULL ELSE date('2018-01-01', '+' || (((n * 37) % 2437) + 14) || ' days') END FROM sayac; SQL sqlite3 kutuphane.db < kurulum.sql sqlite3 -header -column kutuphane.db <<'SQL' SELECT (SELECT page_size FROM pragma_page_size) AS sayfa_boyutu, (SELECT page_count FROM pragma_page_count) AS sayfa_sayisi, (SELECT page_size FROM pragma_page_size) * (SELECT page_count FROM pragma_page_count) AS hesaplanan_bayt; .shell echo "dosyanin gercek boyutu: $(wc -c < kutuphane.db)" SQL
sayfa_boyutu sayfa_sayisi hesaplanan_bayt ------------ ------------ --------------- 4096 20859 85438464 dosyanin gercek boyutu: 85438464
Hesaplanan değer ile dosyanın gerçek boyutu birbirinin aynısı. Bu bir yaklaşıklık değil, tanımdır: veri dosyası, art arda dizilmiş eşit boyutlu sayfalardan başka bir şey içermez. Dosya büyüdüğünde sayfa sayısı artar; sayfa sayısı azalmadan dosya küçülmez. Bu iki cümle, ileride şişme ve boşluk geri kazanımı tartışılırken doğrudan kullanılacaktır.
Sayfa boyutu bu ortamda 4096 bayttır. Sayfa sayısı ve dolayısıyla dosya boyutu veriye bağlıdır; farklı bir sayfa boyutuyla kurulmuş bir veritabanında iki sayı da değişir, ilişki değişmez.
Sayfanın İçinde Ne Var
Sayfalar hepsi aynı işi görmez. B-ağacında yer alan bir tablo için üç sayfa türü ayırt edilir: yaprak sayfa (leaf page) satırların kendisini tutar, iç sayfa (internal page) alt ağaçlara yönlendiren anahtarları tutar, taşma sayfası (overflow page) ise bir sayfaya sığmayan değerin artan bölümünü tutar.
[ -f kutuphane.db ] || sqlite3 kutuphane.db < kurulum.sql # veri kümesi ilk blokta kuruldu sqlite3 -header -column kutuphane.db <<'SQL' SELECT pagetype AS sayfa_turu, count(*) AS sayfa, sum(ncell) AS hucre, round(1.0 * sum(ncell) / count(*), 1) AS sayfa_basina_hucre FROM dbstat WHERE name = 'odunc' GROUP BY pagetype; SELECT name AS nesne, count(*) AS sayfa, sum(ncell) AS hucre, round(100.0 * sum(payload) / sum(pgsize), 1) AS doluluk_yuzde FROM dbstat GROUP BY name ORDER BY sayfa DESC; SQL
sayfa_turu sayfa hucre sayfa_basina_hucre ---------- ----- ------- ------------------ internal 46 17966 390.6 leaf 17967 2000000 111.3 nesne sayfa hucre doluluk_yuzde ------------- ----- ------- ------------- odunc 18013 2017966 82.5 kitap 1780 201773 82.4 uye 1064 121059 82.3 sube 1 8 3.4 sqlite_schema 1 4 16.2
İki milyon satır 17.967 yaprak sayfaya yerleşmiş: sayfa başına ortalama 111 satır. Bunların üstünde 46 iç sayfa var ve her biri ortalama 390 anahtar taşıyor. Ağacın yüksekliği buradan okunur: 46 iç sayfanın biri köktür, kalan 45’i onun altındadır ve yapraklara işaret eder. Yani iki milyon satırlık bir tabloda herhangi bir satıra ulaşmak üç sayfa okumakla tamamlanır. Veri Yapıları kursundaki B-Ağaçları dersinin “yükseklik kayıt sayısının logaritmasıyla artar” sonucu, burada ölçülmüş bir sayıdır.
Doluluk sütunu ikinci bir bilgi verir: sayfaların yaklaşık %82’si gerçek veriyle doludur.
Kalan pay, hücre işaretçileri ve sayfa başlığı gibi defter bilgisiyle, bir de kasıtlı
bırakılmış boşlukla gider. Boşluğun bir kısmı gereklidir: sayfa tıka basa dolu olursa
araya eklenen tek bir satır sayfayı bölmeye zorlar. sube tablosunun %3,4 dolulukta
görünmesi bir kusur değildir; sekiz satır tek bir sayfaya girmiş, sayfanın geri kalanı
boş kalmıştır. Küçük tablolarda en küçük birim yine bir sayfadır.
Satır Sayfaya Sığmadığında
Sayfa boyutu sabit olduğuna göre, bir satırın uzunluğu sayfa boyutunu aşarsa ne olur? Motor satırın başlangıcını normal sayfada tutar ve artanı taşma sayfalarına zincirler.
rm -f tasma.db sqlite3 -header -column tasma.db <<'SQL' CREATE TABLE kitap_ozet(kitap_id INTEGER PRIMARY KEY, ozet TEXT NOT NULL); INSERT INTO kitap_ozet VALUES (1, replace(hex(zeroblob(40)),'0','a')); INSERT INTO kitap_ozet VALUES (2, replace(hex(zeroblob(600)),'0','b')); INSERT INTO kitap_ozet VALUES (3, replace(hex(zeroblob(6000)),'0','c')); SELECT kitap_id, length(ozet) AS ozet_uzunlugu FROM kitap_ozet; SELECT pageno AS sayfa, pagetype AS tur, ncell AS hucre, payload AS yuk, unused AS bos FROM dbstat WHERE name = 'kitap_ozet' ORDER BY pageno; SQL
kitap_id ozet_uzunlugu -------- ------------- 1 80 2 1200 3 12000 sayfa tur hucre yuk bos ----- -------- ----- ---- ---- 2 internal 1 0 4065 3 overflow 0 4080 0 4 overflow 0 4080 0 5 leaf 2 1288 2779 6 leaf 1 3845 222
İlk iki özet — 80 ve 1200 karakter — tek bir yaprak sayfaya birlikte sığmış (sayfa 5). Üçüncüsü 12.000 karakter ve tek başına bir yaprak sayfayı doldurmuş: o sayfada 3.845 bayt duruyor, kalanı iki taşma sayfasına yayılmış (3.845 + 4.080 + 4.080 = 12.005; fark satır başlığındandır). Sayfa numaralarının sıralı olmaması, sayfaların ayrılma sırasını yansıtır; mantıksal sıra değil, ayırma sırasıdır.
Bunun bedeli okumada ortaya çıkar. Uzun bir metin sütunu, o sütuna hiç bakmayan sorgular için bile satırı ağırlaştırır — sayfa başına daha az satır sığar, tarama daha çok sayfa okur. Şema tasarımındaki bir alışkanlığın gerekçesi budur: nadiren okunan uzun metin alanlarını ayrı bir tabloya taşımak, ana tablonun sayfa başına satır yoğunluğunu korur.
Tablo ve Dizinin Dosyadaki Payı
Dizin de bir B-ağacıdır ve o da sayfalarda durur. Aynı sayaç, dizinin yer maliyetini tabloyla aynı ölçekte gösterir.
[ -f kutuphane.db ] || sqlite3 kutuphane.db < kurulum.sql # veri kümesi ilk blokta kuruldu sqlite3 kutuphane.db 'CREATE INDEX odunc_uye ON odunc(uye_id);' sqlite3 -header -column kutuphane.db " SELECT name AS nesne, count(*) AS sayfa, count(*) * 4096 / 1048576 AS mib FROM dbstat GROUP BY name ORDER BY sayfa DESC;"
nesne sayfa mib ------------- ----- --- odunc 18013 70 odunc_uye 5754 22 kitap 1780 6 uye 1064 4 sube 1 0 sqlite_schema 1 0
Tek bir tamsayı sütunu üzerindeki dizin, tablonun yaklaşık üçte biri kadar yer kaplıyor: 70 MiB’lik tabloya 22 MiB. Oran şaşırtıcı değildir — dizin girdisi anahtar değerini ve satıra ulaşmayı sağlayan başvuruyu tutar, yani satırın tamamını değil ama satır başına sabit bir maliyeti. İleri SQL kursundaki Dizin Kavramı dersinde “dizin yer kaplar” cümlesiyle geçilen maliyet budur; burada nesne başına sayılabilir hâldedir.
Bu sayaç yönetimde doğrudan işe yarar. Bir veritabanının hangi tablo ve dizin tarafından şişirildiği, tahminle değil bu sorguyla belirlenir; kapasite planlaması da gereksiz dizinlerin tespiti de aynı listeden başlar.
Dosya Düzeni ve Tablo Alanı
Sayfaların hangi dosyalara yerleştiği motora göre değişir ve bu, yönetimin görünür yüzüdür. Üç düzen yaygındır: bütün veritabanının tek bir dosyada tutulması, her tablo ve dizinin ayrı bir dosyaya yazılması, ya da birden çok dosyanın önceden ayrılmış bir alan kümesi olarak yönetilmesi.
Tablo alanı (tablespace), bu dosyaların bulunduğu yeri adlandıran bir soyutlamadır. Tablo oluşturulurken bir tablo alanı adı verilir; o adın hangi dizine ya da hangi diske karşılık geldiği yönetim tarafından ayrıca tanımlanır. Kazancı, verinin fiziksel yerini şemadan ayırmasıdır: sık okunan bir tablo hızlı bir depolama birimine, arşiv nitelikli bir tablo ucuz bir birime yerleştirilebilir ve bunun için tablo tanımına dokunulmaz.
Bu ayrımın gösterilemediği bir ortamda çalışıldığı için açıkça söylemek gerekir: buradaki koşumlarda veritabanının tamamı tek bir dosyadır ve tablo alanı kavramının karşılığı yoktur. Sayfa, doluluk ve taşma üzerine ölçülen her şey düzenden bağımsızdır; dosya sayısı ve tablo alanı adlandırması ise motora özgüdür.
Özet
- Sayfa, motorun disk ile bellek arasında taşıdığı en küçük birimdir; erişimin sabit maliyeti tek satır okumayı anlamsız kılar.
- Dosya boyutu, sayfa boyutu ile sayfa sayısının çarpımıdır; ölçümde 4096 × 20.859 = 85.438.464 bayt, dosyanın gerçek boyutuna birebir eşit çıktı.
- İki milyon satır 17.967 yaprak sayfaya yerleşti (sayfa başına 111 satır) ve üstlerinde 46 iç sayfa var; ağacın yüksekliği üçtür ve sayfaların doluluğu %82 civarındadır.
- Sayfaya sığmayan değer taşma sayfalarına zincirlenir; 12.000 karakterlik bir metin yaprak sayfada 3.845 bayt bırakıp iki taşma sayfası kullandı.
- Dizin de sayfalarda durur: tek tamsayı sütunlu bir dizin, 70 MiB’lik tablonun yanında 22 MiB yer kapladı.
Sonraki Adım
Bu ders verinin diskte nasıl durduğunu gösterdi, ancak oraya nasıl vardığını sormadı. Bir satır güncellendiğinde ilgili sayfa bellekte değişir; o sayfa diske yazılmadan önce elektrik kesilirse ne olur? Kesinleştirilmiş bir işlemin sonucunun kaybolmaması, sayfaların diske yazılmasına bağlı olsaydı her kesinleştirme rastgele yerlere yazma anlamına gelirdi. Sonraki ders bu sorunun çözümünü ele alır: değişiklikleri veri dosyasından önce sıralı bir günlüğe yazmak. Günlük dosyasının oluşması, büyümesi ve bir çökmeden sonra verinin oradan geri gelmesi ölçülerek gösterilecek.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.