Ders 12 / 19
Şema Doğrulama
Esnek şemaya kural eklemenin iki ölçüsü: yüzeysel bir kuralın dört bozuk belge sınıfının ikisini yakalayıp ikisini kaçırması, aynı kuralın dolu bir derleme sonradan konduğunda 3.200 belgede 3.900 ihlal üretmesi, geriye dönük düzeltmenin 3.000 ihlali makineyle kapatıp 900'ünü karara bırakması ve düzeltilmemiş derlemde sıradan 5.000 güncellemenin 785'inin reddedilmesi.
İçindekiler
Önceki dersin bütün dizinleri var olan alanların üstüne kuruldu ve alanların ne taşıdığına
hiç bakmadı: kopya.durum dizini sözlükte olmayan bir değeri de sessizce dizinler, birinci
dersin ölçtüğü metin yazılmış yayın yılları da kendi tipleriyle girer. Esnek şema alanın
bulunup bulunmadığını ve tipini yazma anında bağlamaz; birinci ders bunun bir aralık
sorgusunda 3.990 yerine 5.991 ya da 3.491 belge döndürdüğünü saymış ve kuralın nasıl
konacağını bu derse bırakmıştı.
Kural konabilir. Şema doğrulama (schema validation), bir derleme bağlanan ve yazma anında değerlendirilen bir kural kümesidir. Bu ders kuralın iki ölçüsünü üretir: kuralın yakaladığı bozuk belge sınıfı ile kaçırdığı sınıf, ve kural dolu bir derleme sonradan konduğunda ortaya çıkan ihlal sayısı ile geriye dönük düzeltmenin bedeli.
Kuralın Kapsamı
Bir doğrulama kuralı üç ayrı kararı taşır. Birincisi kapsamdır: kural yalnız üst düzey alanlara mı bakar, yoksa iç içe belgelere ve dizi ögelerine iner mi. İkincisi kapalılık: kuralda sayılmayan bir alanın belgede bulunması ihlal sayılır mı. Üçüncüsü kip: ihlalli yazma reddedilir mi, yoksa yazılıp sayılır mı. Üç kararın üçü de ölçülebilir sonuç üretir.
// dogrulayici.mjs — derleme baglanan kural kumesi. Kural yazma aninda degerlendirilir ve // ihlalleri sinifiyla birlikte dondurur: eksik alan, yanlis tip, sozluk disi deger, // kuralda bulunmayan alan. Kural ic ice belgelere ve dizi ogelerine inebilir. export function tipAdi(d) { if (d === null) return "bos"; if (Array.isArray(d)) return "dizi"; if (typeof d === "number") return Number.isInteger(d) ? "tam" : "ondalik"; return typeof d === "string" ? "metin" : "belge"; } export function bayt(d) { // 01. dersin kodlama kurali if (d === null || d === undefined) return 0; if (typeof d === "number") return Number.isInteger(d) ? 4 : 8; if (typeof d === "string") return 4 + Buffer.byteLength(d) + 1; const oge = Array.isArray(d) ? d.map((v, i) => [String(i), v]) : Object.entries(d); return 5 + oge.reduce((t, [a, v]) => t + 2 + Buffer.byteLength(a) + bayt(v), 0); } // kural: { alanlar: { ad: { zorunlu, tip, sozluk, oge } }, kapali } // kapali=true ise alanlar listesinde bulunmayan her alan ihlaldir. export function dogrula(belge, kural, yol = "") { const ihlal = []; for (const [ad, k] of Object.entries(kural.alanlar)) { const d = belge[ad]; if (d === undefined) { if (k.zorunlu) ihlal.push({ sinif: "eksik", alan: yol + ad }); continue; } if (tipAdi(d) !== k.tip) { ihlal.push({ sinif: "tip", alan: yol + ad }); continue; } if (k.sozluk && !k.sozluk.includes(d)) ihlal.push({ sinif: "sozluk", alan: yol + ad }); if (k.oge) for (const o of d) ihlal.push(...dogrula(o, k.oge, `${yol}${ad}.`)); } if (kural.kapali) for (const ad of Object.keys(belge)) if (ad !== "_k" && !(ad in kural.alanlar)) ihlal.push({ sinif: "fazla", alan: yol + ad }); return ihlal; } export class Derlem { // kip: "reddet" ihlalli yazmayi geri cevirir, "uyar" yazar ve sayar, null kural yoksa. constructor(kural = null, kip = "reddet") { Object.assign(this, { kural, kip, belge: new Map() }); this.o = { yazilan: 0, reddedilen: 0, uyarilan: 0, yazilanBayt: 0 }; } yaz(b) { const ihlal = this.kural ? dogrula(b, this.kural) : []; if (ihlal.length) { if (this.kip === "reddet") { this.o.reddedilen += 1; return false; } this.o.uyarilan += 1; } this.belge.set(b._k, b); this.o.yazilan += 1; this.o.yazilanBayt += bayt(b); return true; } denetle() { // dolu derlemin kurala gore taranmasi const sayim = new Map(); let bozuk = 0; for (const b of this.belge.values()) { const ihlal = dogrula(b, this.kural); if (ihlal.length) bozuk += 1; for (const i of ihlal) sayim.set(i.sinif + " " + i.alan, (sayim.get(i.sinif + " " + i.alan) ?? 0) + 1); } return { bozuk, sayim }; } }
Dört Bozuk Sınıf, İki Kural
Ölçüm önceki derslerin bıraktığı bozukluklarla yapılır. NS7 (varsayım): katalog 20.000 kitap belgesidir, tohum 424242’dir. NS8 (varsayım): kayıtların sekizde birinde yayın yılı metin olarak yazılmıştır. NS12 (varsayım): kayıtların kırkta birinde yayın yılı alanı hiç yoktur. NS28 (varsayım): dış kaynaktan alınan kayıtların ellide birinde bir kopyanın durumu sözlükte bulunmayan bir değerle yazılmıştır ve yirmide birinde yayın yılı ikinci bir alan adıyla tekrarlanmıştır. Gerekçe: dört sınıfın dördü de aynı kaynaktan gelir — alanın adını, tipini ve değer kümesini bağlayan bir tanımın bulunmaması.
Kural iki biçimde kurulur. A yüzeyseldir: yalnız üst düzey alanlara bakar, kapalı değildir. B öge düzeyine iner ve kapalıdır. İkisi de aynı derlem üzerinde koşturulur.
// kural-olcum.mjs — ayni katalog iki kuralla denetlenir: yuzeysel kural yalniz ust duzey // alanlara bakar, tam kural dizi ogelerine iner ve kuralda bulunmayan alani yasaklar. // Yakalanan ve kacirilan bozuk belge siniflari, kuralin dolu derleme sonradan konmasi ve // geriye donuk duzeltme sayilir. Ayni dizinde dogrulayici.mjs bulunur. import { Derlem, dogrula, bayt } from "./dogrulayici.mjs"; let cekirdek = 424242; // gorunur tohum const rast = () => (cekirdek = (cekirdek * 1103515245 + 12345) % 2147483648) / 2147483648; const SUBE = ["Merkez", "Bahcelievler", "Kadikoy", "Beyoglu", "Konak", "Nilufer"]; const DURUM = ["rafta", "oduncte", "onarimda"]; const ETIKETLER = ["roman", "tarih", "cocuk", "siir", "bilim", "basvuru"]; const KATALOG = []; for (let i = 1; i <= 20000; i += 1) { const kopya = [], etiket = []; for (let j = 0, n = 1 + Math.floor(rast() * 5); j < n; j += 1) kopya.push({ barkod: `B${String(i * 10 + j).padStart(7, "0")}`, sube: SUBE[Math.floor(rast() * 6)], durum: DURUM[Math.floor(rast() * 3)] }); for (let j = 0, n = 2 + Math.floor(rast() * 3); j < n; j += 1) etiket.push(ETIKETLER[Math.floor(rast() * 6)]); const b = { _k: `K-${String(i).padStart(5, "0")}`, yazar: `Yazar ${i % 4000}`, yayin_yili: 1950 + (i % 75), etiket, kopya }; if (i % 8 === 0) b.yayin_yili = String(b.yayin_yili); // NS8: yil metin yazilmis if (i % 40 === 0) delete b.yayin_yili; // NS12: alan hic yok if (i % 50 === 0) b.kopya[0].durum = "kayip"; // NS28: sozluk disi durum if (i % 20 === 0) b.yayinYili = 1950 + (i % 75); // NS28: alan ikinci adla tekrar KATALOG.push(b); } const ALAN = { yazar: { zorunlu: true, tip: "metin" }, yayin_yili: { zorunlu: true, tip: "tam" }, etiket: { zorunlu: true, tip: "dizi" }, }; const KURAL_A = { alanlar: ALAN }; // yuzeysel: ust duzey alanlar const KURAL_B = { // tam: ogelere iner, kapalidir alanlar: { ...ALAN, kopya: { zorunlu: true, tip: "dizi", oge: { alanlar: { barkod: { zorunlu: true, tip: "metin" }, sube: { zorunlu: true, tip: "metin", sozluk: SUBE }, durum: { zorunlu: true, tip: "metin", sozluk: DURUM } }, kapali: true } } }, kapali: true, }; const SINIF = { "eksik yil": (b) => b.yayin_yili === undefined, "metin yil": (b) => typeof b.yayin_yili === "string", "sozluk disi durum": (b) => b.kopya.some((k) => !DURUM.includes(k.durum)), "yinelenen alan": (b) => "yayinYili" in b, }; console.log(`katalog ${KATALOG.length} belge, ${KATALOG.reduce((t, b) => t + bayt(b), 0)} bayt`); for (const [ad, f] of Object.entries(SINIF)) console.log(` ${ad.padEnd(18)} ${KATALOG.filter(f).length} belge`); console.log(` ${"tekil bozuk belge".padEnd(18)} ` + `${KATALOG.filter((b) => Object.values(SINIF).some((f) => f(b))).length}`); // Her kural hangi sinifi yakaliyor. for (const [ad, kural] of [["A yuzeysel", KURAL_A], ["B tam", KURAL_B]]) { const satir = Object.entries(SINIF).map(([sad, f]) => { const kume = KATALOG.filter(f); return `${sad} ${kume.filter((b) => dogrula(b, kural).length > 0).length}/${kume.length}`; }); console.log(`${ad.padEnd(11)} yakalanan ` + `${KATALOG.filter((b) => dogrula(b, kural).length > 0).length} belge ${satir.join(" ")}`); } // Kural dolu derleme sonradan konuyor: once var olan belgelerdeki ihlal sayilir. const derlem = new Derlem(KURAL_B, "uyar"); for (const b of KATALOG) derlem.yaz(b); const { bozuk, sayim } = derlem.denetle(); console.log(`kural sonradan konunca ${bozuk} belge ihlalli, ` + `${[...sayim.values()].reduce((t, v) => t + v, 0)} ihlal:`); for (const [ad, n] of [...sayim].sort((a, b) => b[1] - a[1])) console.log(` ${ad.padEnd(22)} ${n}`); // Kural "reddet" kipinde, derlem duzeltilmeden: siradan bir guncelleme ne yapar. function guncelle(kip) { const d = new Derlem(KURAL_B, kip); for (let i = 0; i < 5000; i += 1) { const kitap = KATALOG[(i * 7) % KATALOG.length]; const yeni = { ...kitap, kopya: kitap.kopya.map((k) => ({ ...k })) }; yeni.kopya[i % yeni.kopya.length].durum = "rafta"; d.yaz(yeni); } return d.o; } const once = guncelle("reddet"); console.log(`duzeltilmemis derlemde 5.000 durum guncellemesi: ` + `${once.yazilan} kabul, ${once.reddedilen} red`); // Geriye donuk duzeltme: hangi ihlal makineyle kapanir, hangisi karar ister. let makine = 0, karar = 0, dokunulan = 0, yazilanBayt = 0; for (const b of KATALOG) { if (dogrula(b, KURAL_B).length === 0) continue; if (typeof b.yayin_yili === "string") { b.yayin_yili = Number(b.yayin_yili); makine += 1; } if ("yayinYili" in b) { delete b.yayinYili; makine += 1; } if (b.yayin_yili === undefined) karar += 1; karar += b.kopya.filter((k) => !DURUM.includes(k.durum)).length; dokunulan += 1; yazilanBayt += bayt(b); } console.log(`geriye donuk duzeltme ${dokunulan} belgeye dokundu, ${yazilanBayt} bayt yazdi`); console.log(` makineyle kapanan ihlal ${makine}, karar isteyen ihlal ${karar}`); console.log(` duzeltmeden sonra ihlalli belge ${derlem.denetle().bozuk}`); const sonra = guncelle("reddet"); console.log(`duzeltilmis derlemde ayni 5.000 guncelleme: ` + `${sonra.yazilan} kabul, ${sonra.reddedilen} red`);
katalog 20000 belge, 6404466 bayt eksik yil 500 belge metin yil 2000 belge sozluk disi durum 400 belge yinelenen alan 1000 belge tekil bozuk belge 3200 A yuzeysel yakalanan 2500 belge eksik yil 500/500 metin yil 2000/2000 sozluk disi durum 100/400 yinelenen alan 500/1000 B tam yakalanan 3200 belge eksik yil 500/500 metin yil 2000/2000 sozluk disi durum 400/400 yinelenen alan 1000/1000 kural sonradan konunca 3200 belge ihlalli, 3900 ihlal: tip yayin_yili 2000 fazla yayinYili 1000 eksik yayin_yili 500 sozluk kopya.durum 400 duzeltilmemis derlemde 5.000 durum guncellemesi: 4215 kabul, 785 red geriye donuk duzeltme 3200 belgeye dokundu, 1012270 bayt yazdi makineyle kapanan ihlal 3000, karar isteyen ihlal 900 duzeltmeden sonra ihlalli belge 800 duzeltilmis derlemde ayni 5.000 guncelleme: 4821 kabul, 179 red
Yakalanan ve Kaçırılan
İlk beş satır bozukluğun dağılımını verir ve bir yan bulgu taşır: metin yazılmış yıl 2.500 değil 2.000 belgededir. Sekizde bir metin yazılmış kayıtların kırkta biri aynı zamanda yıl alanını hiç taşımayan kayıtlardır ve alan yoksa tipi de yoktur. İki bozukluk sınıfı birbirini örter; sınıfların toplamı 3.900, ayrı belge sayısı 3.200’dür. Bir doğrulama raporunda ihlal sayısıyla belge sayısının farklı olmasının nedeni budur.
İki kural aynı derlemde farklı sayı üretir. A yüzeysel kuralı 2.500 belge yakalar, B tam kuralı 3.200. Fark, kuralın kapsamındadır ve satır satır okunabilir. Yıl sınıflarında iki kural aynıdır: 500/500 ve 2.000/2.000. Sözlük dışı durum sınıfında A yalnız 400’ün 100’ünü yakalar — ve bu 100 belge, kopya durumu yüzünden değil, aynı belgede bulunan bir yıl ihlali yüzünden yakalanmıştır. Kuralın gerçekten kaçırdığı sınıf tamdır: A kuralı bir dizi ögesinin içine hiç bakmaz. Yinelenen alan sınıfında da aynı yanılgı vardır: A 1.000’in 500’ünü yakalar, hepsi başka bir ihlal sayesinde.
Buradan kuralın okunma biçimi çıkar. “Kural 2.500 belge yakaladı” cümlesi bir kapsama ölçüsü değildir; kapsama ancak sınıf sınıf sorulduğunda görünür. Yüzeysel kural, belge modelinin en çok kullanılan yapısına — dizi içindeki iç içe belgeye — hiç dokunmaz, ve kapalı olmadığı için yanlış adla yazılmış bir alanı ihlal saymaz. Yanlış adla yazılan alan sessiz bir sınıftır: sorgu onu okumaz, dizin onu dizinlemez, kural onu görmez.
Kuralın Geç Konması
Kural boş bir derleme konduğunda bedeli yalnız yazma yolundaki denetimdir. Dolu bir
derleme konduğunda üç ayrı sayı doğar. Birincisi ihlal sayısıdır: 3.200 belge, 3.900
ihlal. İkincisi, kural reddet kipinde ve derlem düzeltilmemişken sıradan güncellemelerin
başına gelendir. İkinci dersin üçüncü işi olan 5.000 kopya durumu değişikliği bu derlemde
koşturulduğunda 785’i reddedilir. Reddedilen yazmaların hiçbiri yeni bir bozukluk
getirmiyordu — durumu rafta yapmak istiyorlardı. Reddedilme nedenleri, gömülü şemada
güncellemenin belgenin tamamını yeniden yazması ve yeniden yazılan belgenin kendi eski
ihlalini de taşımasıdır. Kural, geçmişi düzeltmeden konduğunda bugünün işini durdurur.
Üçüncü sayı düzeltmenin kendisidir. 3.200 belge okunur ve yeniden yazılır: 1.012.270 bayt,
derlemin %15,8’i. Düzeltmenin 3.000 ihlali makineyle kapanır — metin yıl sayıya
çevrilebilir, yinelenen alan silinebilir. Kalan 900 ihlal karar ister: yılı hiç bulunmayan
500 belgenin yılı türetilemez, sözlük dışı 400 durumun ne anlama geldiği ise ancak
kütüphanenin kendisine sorularak bilinir — kayip bir yazım hatası mı, yoksa sözlüğe
eklenmesi gereken gerçek bir durum mu. Düzeltmeden sonra 800 belge hâlâ ihlallidir ve aynı
5.000 güncellemenin reddedilen sayısı 785’ten 179’a iner.
Kararın kuralı bu üç sayıdan çıkar. Doğrulama kuralı bir şema tanımı değil, bir yazma yolu
denetimidir; kapsamı sınıf sınıf ölçülür, ve dolu bir derleme konurken sırası şudur: önce
uyar kipinde ölç, sonra geriye dönük düzelt, sonra reddet kipine geç. Ters sıra,
düzeltilmemiş belgelere dokunan her işi durdurur.
Özet
- Şema doğrulama derleme bağlanan ve yazma anında değerlendirilen bir kuraldır; kapsamı, kapalılığı ve kipi ayrı ayrı seçilir.
- Yüzeysel kural dört bozuk sınıfın ikisini tam yakalar, dizi ögesindeki sözlük ihlalinin 400’ünün 100’ünü ve yinelenen alanın 1.000’inin 500’ünü yakalar; bu belgeler de kendi sınıfları yüzünden değil, aynı belgedeki başka bir ihlal yüzünden yakalanır.
- İhlal sayısı ile belge sayısı farklıdır: 3.900 ihlal 3.200 belgeye dağılır, çünkü bozukluk sınıfları birbirini örter.
- Kural dolu derleme
reddetkipinde konduğunda 5.000 sıradan durum güncellemesinin 785’i reddedilir; reddedilen yazmalar yeni bir bozukluk getirmiyordu. - Geriye dönük düzeltme 3.200 belgeye dokunur ve 1.012.270 bayt yazar; 3.000 ihlal makineyle kapanır, 900’ü karar ister ve 800 belge ihlalli kalır, red sayısı 179’a iner.
Sonraki Adım
Geriye dönük düzeltme 3.200 belgeye tek tek dokundu ve bu, konunun şimdiye kadar söylenmemiş bir varsayımını görünür kılıyor: her yazma tek bir belgeyi kapsıyordu. Belge modelinin atomiklik güvencesi de tam olarak buradadır — bir belgeye yapılan yazma ya bütünüyle olur ya hiç olmaz, ama iki belgeye yapılan iki yazma arasında böyle bir bağ yoktur. Düzeltme yarıda kesilseydi derlemin bir bölümü düzelmiş, bir bölümü düzelmemiş olurdu ve hangi bölümün hangisi olduğunu söyleyen bir kayıt bulunmazdı. Aynı soru kütüphanenin günlük işinde de vardır: bir ödünç verme işlemi kopyanın durumunu, üyenin ödünç sayısını ve ödünç kaydını birlikte değiştirir. Sonraki ders tek belge atomikliğinin bu işe yettiği ve yetmediği durumları aynı işi iki şemada koşturarak sayar, ve çok belgeli işlemin bedelini ölçer.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.