İçeriğe geç
academia.sh

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.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat