---
title: 'Şema Doğrulama'
source: 'https://academia.sh/tr/kurslar/nosql/sema-dogrulama'
course: 'İlişkisel Olmayan Veri Modelleri'
language: tr
updated: '2026-08-17T18:08:58+00:00'
license: 'CC BY-SA 4.0'
---

# Ş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.

Ö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.

```js
// 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.

```js
// 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 `reddet` kipinde 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.
