---
title: 'Belge Veri Tipleri'
source: 'https://academia.sh/tr/kurslar/nosql/belge-veri-tipleri'
course: 'İlişkisel Olmayan Veri Modelleri'
language: tr
updated: '2026-08-17T18:08:58+00:00'
license: 'CC BY-SA 4.0'
---

# Belge Veri Tipleri

Belgenin kendi alan adlarını ve tip etiketlerini taşımasının bayt cinsinden bedeli: aynı 20.000 kitap kaydının açık adlı, kısa adlı ve sabit şemalı üç gösterimde kapladığı yer, kendini tanımlamanın payı ve aynı alanın iki tipte yazılmasının aralık sorgusunda ürettiği fark.

Önceki konu seçim ölçütleriyle kapandı ve arkasında tamamlanmamış bir karar bıraktı:
erişim örüntüsü bir aileye işaret ettiğinde iş bitmiyor. Aile seçmek şema seçmek
değildir. Belge modeli aynı alan için birbirinden çok farklı şemalar kurmaya izin verir
ve o izin bedelsiz değildir.

Bu konu o bedeli sayar; ilk soru en alttan başlar. İlişkisel bir tabloda alanın adı ve
tipi tablo tanımında bir kez yazılır, satırda yalnız değer durur. Belge modelinde böyle
bir tanım yoktur: her kayıt kendi alan adlarını ve tiplerini kendisi taşır. Bu dersin iki
ölçüsü budur — o taşımanın bayt cinsinden tuttuğu yer ve aynı alanın iki farklı tipte
yazılabilmesinin bir aralık sorgusunda ürettiği fark.

## Kendini Tanımlayan Kayıt

**Belge** (document), adlandırılmış alanların sıralı listesidir; bir alanın değeri sayı
ya da metin olabildiği gibi iç içe belge veya dizi de olabilir. Belgeler bir **derlem**
(collection) içinde durur. Depo bu yapıyı **ikili belge gösterimiyle** (binary document
representation) saklar: her alan bir **tip etiketiyle** (type tag) başlar, adını
sonlandırıcı bayta kadar yazar, değeri tipin gerektirdiği biçimde koyar. Bu gösterime
**kendini tanımlayan** denir; kaydı çözmek için dışarıdan bir şema gerekmez.

Gösterimin üç kuralı vardır. Sabit boylu tipler (tam sayı, ondalık, mantıksal, zaman)
uzunluk taşımaz, boyu etiketinden bilinir. Değişken boylu tipler (metin, iç içe belge,
dizi) kendi uzunluklarını dört baytlık bir ön ekle yazar; böylece okuyucu ilgilenmediği
bir alanı çözmeden atlayabilir. Dizi, alan adları `"0"`, `"1"` diye giden bir iç
belgedir — dizinin öge sayısı da bu adlar üzerinden taşınır.

```js
// ikilibelge.mjs — kendini tanimlayan ikili belge gosterimi.
// Kodlama: [4 bayt uzunluk] (tip etiketi + alan adi + NUL + deger)* [0x00]
export const ETIKET = { bos: 0x0a, tam32: 0x10, ondalik: 0x01, metin: 0x02,
  belge: 0x03, dizi: 0x04, mantik: 0x08, zaman: 0x09 };
const SIRA = ["bos", "tam32", "ondalik", "metin", "belge", "dizi", "mantik", "zaman"];

export function tipAdi(d) {
  if (d === null) return "bos";
  if (Array.isArray(d)) return "dizi";
  if (d instanceof Date) return "zaman";
  if (typeof d === "boolean") return "mantik";
  if (typeof d === "number") return Number.isInteger(d) ? "tam32" : "ondalik";
  return typeof d === "string" ? "metin" : "belge";
}

export function kodla(belge, s = { ad: 0, etiket: 0, uzunluk: 0, deger: 0 }) {
  const parca = [];
  for (const [ad, d] of Object.entries(belge)) {
    parca.push(Buffer.from([ETIKET[tipAdi(d)]]), Buffer.from(ad + "\0", "utf8"), deger(d, s));
    s.etiket += 1;
    s.ad += Buffer.byteLength(ad) + 1;
  }
  const govde = Buffer.concat([...parca, Buffer.from([0])]);
  const bas = Buffer.alloc(4);
  bas.writeInt32LE(govde.length + 4);
  s.uzunluk += 4;
  s.deger += 1;                                  // kapanis NUL
  return Buffer.concat([bas, govde]);
}

function deger(d, s) {
  const t = tipAdi(d), n = { bos: 0, mantik: 1, tam32: 4, ondalik: 8, zaman: 8 }[t];
  if (n !== undefined) {
    s.deger += n;
    const b = Buffer.alloc(n);
    if (t === "mantik") b[0] = d ? 1 : 0;
    if (t === "tam32") b.writeInt32LE(d);
    if (t === "ondalik") b.writeDoubleLE(d);
    if (t === "zaman") b.writeBigInt64LE(BigInt(d.getTime()));
    return b;
  }
  if (t === "metin") {
    const m = Buffer.from(d + "\0", "utf8"), b = Buffer.alloc(4);
    b.writeInt32LE(m.length);
    s.uzunluk += 4;
    s.deger += m.length;
    return Buffer.concat([b, m]);
  }
  return kodla(t === "dizi" ? { ...d } : d, s);   // dizi: alan adlari "0","1",...
}

export function coz(t, konum = 0) {
  const son = konum + t.readInt32LE(konum), cikan = {};
  let i = konum + 4;
  while (i < son - 1) {
    const et = t[i], kes = t.indexOf(0, i + 1), ad = t.toString("utf8", i + 1, kes);
    i = kes + 1;
    if (et === ETIKET.bos) cikan[ad] = null;
    else if (et === ETIKET.mantik) cikan[ad] = t[i++] === 1;
    else if (et === ETIKET.tam32) { cikan[ad] = t.readInt32LE(i); i += 4; }
    else if (et === ETIKET.ondalik) { cikan[ad] = t.readDoubleLE(i); i += 8; }
    else if (et === ETIKET.zaman) { cikan[ad] = new Date(Number(t.readBigInt64LE(i))); i += 8; }
    else if (et === ETIKET.metin) {
      const n = t.readInt32LE(i);
      cikan[ad] = t.toString("utf8", i + 4, i + 3 + n);
      i += 4 + n;
    } else {
      const ic = coz(t, i);
      cikan[ad] = et === ETIKET.dizi ? Object.values(ic) : ic;
      i += t.readInt32LE(i);
    }
  }
  return cikan;
}

// Siralama tipe, sonra degere bakar: tip sirasi.
export function kiyas(a, b) {
  const f = SIRA.indexOf(tipAdi(a)) - SIRA.indexOf(tipAdi(b));
  if (f !== 0) return f;
  if (a === null) return 0;
  if (a instanceof Date) return a.getTime() - b.getTime();
  return a < b ? -1 : a > b ? 1 : 0;
}
```

## Adın Bayt Bedeli

Ölçüm, önceki kursların kütüphane katalogunu belge biçiminde kurar. **NS7 (varsayım):**
katalogda 20.000 kitap belgesi vardır; her kitabın 1–5 kopyası ve 2–4 etiketi bulunur,
üretim tohumu 424242'dir. Aynı veri üç kararla kodlanır: alan adları açık yazılır, alan
adları kısaltılır, ya da adlar ve tipler kayıttan çıkarılıp sabit bir şemaya taşınır.
Üçüncüsü ilişkisel satırın yaptığı şeydir ve burada karşılaştırma tabanı olarak durur.

```js
// ad-maliyeti.mjs — ayni 20.000 kitap belgesi iki semada kodlanir: acik adlar ve kisa
// adlar. Ayni veri, ucuncu bir karar olarak sabit semali satirda da kodlanir.
// Ayni dizinde ikilibelge.mjs bulunur.
import { kodla, coz } from "./ikilibelge.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"];

function kitaplar(n) {
  const cikan = [];
  for (let i = 1; i <= n; i += 1) {
    const kopyaSayisi = 1 + Math.floor(rast() * 5);
    const kopya = [];
    for (let j = 0; j < kopyaSayisi; j += 1)
      kopya.push({ barkod: `B${String(i * 10 + j).padStart(7, "0")}`,
        sube: SUBE[Math.floor(rast() * SUBE.length)],
        durum: DURUM[Math.floor(rast() * DURUM.length)] });
    const etiketSayisi = 2 + Math.floor(rast() * 3);
    const etiket = [];
    for (let j = 0; j < etiketSayisi; j += 1) etiket.push(ETIKETLER[Math.floor(rast() * 6)]);
    cikan.push({ anahtar: `K-${String(i).padStart(5, "0")}`,
      baslik: `Kitap ${i} uzerine incelemeler`, yazar: `Yazar ${i % 4000}`,
      yayin_yili: 1950 + (i % 75), etiket, kopya });
  }
  return cikan;
}

const KISA = { anahtar: "_k", baslik: "b", yazar: "y", yayin_yili: "yy",
  etiket: "e", kopya: "k", barkod: "bk", sube: "s", durum: "d" };
const kisalt = (b) => Object.fromEntries(Object.entries(b).map(([a, d]) => [KISA[a] ?? a,
  Array.isArray(d) ? d.map((x) => (typeof x === "object" ? kisalt(x) : x)) : d]));

const KAYIT = kitaplar(20000);
const olc = (kume) => {
  const s = { etiket: 0, ad: 0, uzunluk: 0, deger: 0 };
  let bayt = 0;
  for (const b of kume) bayt += kodla(b, s).length;
  return { bayt, ...s };
};
const acik = olc(KAYIT), kisa = olc(KAYIT.map(kisalt));

// Sabit semali satir: tip ve alan adi semada bir kez durur, kayitta yalniz deger vardir.
function sabitSema(kume) {
  let bayt = 0;
  for (const b of kume) {
    bayt += 4;                                          // yayin_yili: tam32
    for (const alan of ["anahtar", "baslik", "yazar"]) bayt += 2 + Buffer.byteLength(b[alan]);
    bayt += 2 + b.etiket.reduce((t, e) => t + 1 + Buffer.byteLength(e), 0);
    bayt += 2;
    for (const k of b.kopya)
      bayt += 2 + Buffer.byteLength(k.barkod) + 1 + Buffer.byteLength(k.sube)
        + 1 + Buffer.byteLength(k.durum);
  }
  return bayt;
}
const sabit = sabitSema(KAYIT);
const alan = KAYIT.reduce((t, b) => t + 6 + b.etiket.length + b.kopya.length * 4, 0);

console.log(`belge sayisi                 ${KAYIT.length}`);
console.log(`toplam alan (ic ice dahil)   ${alan}`);
console.log(`acik adli sema      ${acik.bayt} bayt   belge basina ${(acik.bayt / 20000).toFixed(1)}`);
console.log(`kisa adli sema      ${kisa.bayt} bayt   belge basina ${(kisa.bayt / 20000).toFixed(1)}`);
console.log(`sabit semali satir  ${sabit} bayt   belge basina ${(sabit / 20000).toFixed(1)}`);
console.log(`acik semada bayt dagilimi: ad ${acik.ad}  tip etiketi ${acik.etiket}  ` +
  `uzunluk alani ${acik.uzunluk}  deger ${acik.deger}`);
console.log(`kendini tanimlama payi  acik %${(100 * (acik.bayt - sabit) / acik.bayt).toFixed(1)}` +
  `   kisa %${(100 * (kisa.bayt - sabit) / kisa.bayt).toFixed(1)}`);

const tur = kodla(KAYIT[0]);
console.log(`ilk belge ${tur.length} bayt, cozuldugunde ilk kopya: ` +
  JSON.stringify(coz(tur).kopya[0]));
```

```
belge sayisi                 20000
toplam alan (ic ice dahil)   417706
acik adli sema      7357008 bayt   belge basina 367.9
kisa adli sema      6082574 bayt   belge basina 304.1
sabit semali satir  3139138 bayt   belge basina 157.0
acik semada bayt dagilimi: ad 2209340  tip etiketi 417706  uzunluk alani 1670824  deger 3059138
kendini tanimlama payi  acik %57.3   kisa %48.4
ilk belge 492 bayt, cozuldugunde ilk kopya: {"barkod":"B0000010","sube":"Beyoglu","durum":"rafta"}
```

Ölçüm 417.706 alan üzerinden yapılır — bir kitap belgesi ortalama 20,9 alan taşır, çünkü
her kopya ve her etiket kendi alanıdır. Açık adlı gösterim 7.357.008 bayt tutar, sabit
şemalı satır 3.139.138 bayt. Fark 4.217.870 bayttır ve bunun tamamı verinin kendisi
değil, verinin kendini anlatmasıdır: 2.209.340 bayt alan adlarına, 417.706 bayt tip
etiketlerine, 1.670.824 bayt uzunluk ön eklerine gider. Hesap: açık gösterimde saklanan
her üç bayttan yaklaşık ikisi (%57,3) veri değil tanımdır.

Kısaltma bu payı %48,4'e düşürür. Aradaki 1.274.434 bayt yalnız alan adlarının
kısalmasından gelir; belge başına 63,8 bayt, ölçekle çarpıldığında 20.000 belgede 1,2 MB
civarı. Kararın öteki yüzü de sayılabilir: kısa adlı belge kendi başına okunduğunda `bk`
alanının barkod olduğunu söylemez, o bilgi artık depoda değil uygulamadadır. Kendini
tanımlama, tam da bu bilginin kayıtta durmasıdır ve bedeli ölçülmüş durumdadır.

## Aynı Alan İki Tipte

Kayıtta tip taşımanın karşılığı, aynı alanın iki belgede iki farklı tipte olabilmesidir.
İlişkisel şema bunu tanım anında engeller; belge modelinde engel yoktur ve engelin
yokluğu sorgu tarafında ortaya çıkar. **NS8 (varsayım):** dış kaynaktan alınan kayıtların
sekizde birinde yayın yılı metin olarak yazılmıştır — `2010` yerine `"2010"`.

Bir aralık sorgusunun bu belgelere ne yapacağı, işlecin semantiğine bağlıdır ve iki
seçenek vardır. **Tip sırası** (type ordering), farklı tipteki değerleri de karşılaştırır:
önce tipler, tip eşitse değerler sıralanır. **Tip parantezi** (type bracketing) ise
karşılaştırmayı yalnız işlecin sağ tarafıyla aynı tipteki değerlere uygular, ötekiler
eşleşmez.

```js
// tip-karisikligi.mjs — ayni alan iki tipte: yayin_yili kayitlarin bir bolumunde tam
// sayi, bir bolumunde metin. Ayni araligi iki islec semantigi ile sayar.
// Ayni dizinde ikilibelge.mjs bulunur.
import { kiyas, tipAdi, kodla } from "./ikilibelge.mjs";

const N = 20000;
const kume = [];
for (let i = 1; i <= N; i += 1) {
  const yil = 1950 + (i % 75);
  kume.push({ anahtar: `K-${String(i).padStart(5, "0")}`,
    yayin_yili: i % 8 === 0 ? String(yil) : yil });      // her sekizinci belge metin yazar
}
const metinli = kume.filter((b) => tipAdi(b.yayin_yili) === "metin").length;

// A karari: karsilastirma tip sirasini kullanir (siralamadaki toplam duzen).
const A = (b, d, yon) => (yon === "ge" ? kiyas(b.yayin_yili, d) >= 0 : kiyas(b.yayin_yili, d) < 0);
// B karari: karsilastirma tip parantezi uygular, farkli tipteki deger eslesmez.
const B = (b, d, yon) => tipAdi(b.yayin_yili) === tipAdi(d) && A(b, d, yon);
const say = (f, d, yon) => kume.filter((b) => f(b, d, yon)).length;

console.log(`belge ${N}, yayin_yili metin olan ${metinli} (%${(100 * metinli / N).toFixed(1)})`);
console.log(`A tip sirasi     >= 2010 -> ${say(A, 2010, "ge")}   < 2010 -> ${say(A, 2010, "lt")}` +
  `   toplam ${say(A, 2010, "ge") + say(A, 2010, "lt")}`);
console.log(`B tip parantezi  >= 2010 -> ${say(B, 2010, "ge")}   < 2010 -> ${say(B, 2010, "lt")}` +
  `   toplam ${say(B, 2010, "ge") + say(B, 2010, "lt")}`);
console.log(`tek tipli sema   >= 2010 -> ` +
  `${kume.filter((b) => Number(b.yayin_yili) >= 2010).length}`);
console.log(`B ile hicbir yana dusmeyen belge ${N - say(B, 2010, "ge") - say(B, 2010, "lt")}`);
console.log(`B, operand metin olursa: >= "2010" -> ${say(B, "2010", "ge")}`);

// Siralama her zaman toplam duzeni kullanir: sonuc tipe gore obeklenir.
const dizili = [...kume].sort((a, b) => kiyas(a.yayin_yili, b.yayin_yili));
const gecis = dizili.findIndex((b) => tipAdi(b.yayin_yili) === "metin");
console.log(`siralamada ilk metin ${gecis}. sirada ${JSON.stringify(dizili[gecis].yayin_yili)}` +
  `, bir oncesi ${JSON.stringify(dizili[gecis - 1].yayin_yili)}`);

const bayt = (d) => kodla({ yayin_yili: d }).length;
console.log(`tek alanli belge: tam sayi ${bayt(2010)} bayt, metin ${bayt("2010")} bayt`);
```

```
belge 20000, yayin_yili metin olan 2500 (%12.5)
A tip sirasi     >= 2010 -> 5991   < 2010 -> 14009   toplam 20000
B tip parantezi  >= 2010 -> 3491   < 2010 -> 14009   toplam 17500
tek tipli sema   >= 2010 -> 3990
B ile hicbir yana dusmeyen belge 2500
B, operand metin olursa: >= "2010" -> 499
siralamada ilk metin 17500. sirada "1950", bir oncesi 2024
tek alanli belge: tam sayi 21 bayt, metin 26 bayt
```

Doğru yanıt 3.990'dır: alanın tamamı tam sayı olsaydı 2010 ve sonrası için bu kadar belge
dönerdi. İki semantik de bu sayıyı vermez, ama farklı biçimde vermez. Tip sırası 5.991
döndürür; fazlalık 2.001 belgenin tamamı metin yazılmış kayıtlardır ve içlerinde 1950
yılına ait olanlar da vardır — metin tipi sırada tam sayıdan sonra geldiği için her metin
değer her tam sayıdan büyük sayılır. Tip parantezi 3.491 döndürür; eksik 499 belge, yılı
metin yazılmış ve gerçekte 2010 ya da sonrası olan kayıtlardır.

Tip parantezinin daha sinsi sonucu son satırda görünür. `>= 2010` ile `< 2010` bir
aralığın iki yarısıdır ve toplamları 20.000 olmalıdır; ölçüm 17.500 verir. 2.500 belge iki
sorgunun hiçbirine düşmez. Bu belgeler bir raporda eksik, ters sorguda da eksik görünür;
hiçbir yanıt onları göstermediği için hata fark edilmez. Sıralama ise her zaman toplam
düzeni kullanır: 17.500. sıraya kadar tam sayılar, oradan sonra metinler gelir, yani
`"1950"` değeri `2024` değerinden sonraya düşer.

Bayt tarafı da aynı yöne işaret eder: tek alanlı bir belge yıl tam sayı olduğunda 21,
metin olduğunda 26 bayt tutar. 2.500 belgede fark 12.500 bayttır — küçük bir sayı, ama
tipin belgede taşınması yalnız yer değil, anlam da ayırır. Ders kuralı şudur: belge modeli
alanın tipini yazma anında bağlamaz, bu yüzden tipi bağlama işi ya yazan uygulamaya ya da
sonradan konacak bir kurala kalır. Bu kuralın nasıl konacağı ve neyi yakalayıp neyi
kaçırdığı bu konunun altıncı dersinin sorusudur.

## Özet

- İkili belge gösterimi her alanı tip etiketi, alan adı ve gerekiyorsa uzunluk ön ekiyle
  saklar; kayıt kendini tanımlar, çözmek için dış şema gerekmez.
- Aynı 20.000 kitap belgesi açık adlarla 7.357.008, kısa adlarla 6.082.574, sabit şemalı
  satırda 3.139.138 bayt tutar; açık gösterimde baytın %57,3'ü veri değil tanımdır.
- Alan adlarını kısaltmak 1.274.434 bayt (belge başına 63,8 bayt) kazandırır ve alanın ne
  olduğu bilgisini depodan uygulamaya taşır.
- Aynı alanın iki tipte yazılması aralık sorgusunu bozar: tip sırası 3.990 yerine 5.991,
  tip parantezi 3.491 belge döndürür.
- Tip parantezinde `>= 2010` ile `< 2010` toplamı 20.000 değil 17.500'dür; 2.500 belge iki
  yanıtın hiçbirinde görünmez.

## Sonraki Adım

Bu ders tek bir belgenin içine baktı ve alanın adıyla tipinin bedelini saydı. Katalog ise
tek bir belgeden ibaret değil: kitap, kopya, üye ve ödünç kayıtları birbirine bağlıdır ve
belge modeli bu bağı iki farklı yoldan kurmaya izin verir — ilgili veriyi belgenin içine
gömmek ya da ayrı bir derlemde tutup anahtarıyla göstermek. İkisi de aynı katalogu
saklar, ama aynı okumayı farklı sayıda gidişle karşılar, aynı güncellemede farklı sayıda
belgeyi yeniden yazar ve belge boyutu sınırına farklı hızda yaklaşır. Sonraki ders bu iki
kararı aynı iş üstünde koşturup farkı sayar.
