İçeriğe geç
academia.sh

Ders 11 / 19

Dizinleme

Dizinin belge yapısı üzerinde kurulması: bir dizi alanının tek belgeden kaç giriş ürettiği, iki dizi alanını tek bileşik dizinde birleştirmenin giriş sayısını 20.000'den 120.042'ye çıkarması, aynı ögede karşılanması gereken koşulun iki tek alan dizininde 6.033 belge okutup öge düzeyi dizinde 3.340 okutması ve kısmi dizinin aynı yanıtı derlemin %27,8'i yerine %6'sı yer kaplayarak vermesi.

İçindekiler

Önceki dersin üç boru hattı aynı yanıtı üç ayrı bedelle üretti, ama ortak bir yanları vardı: üçü de ilk aşamada derlemin tamamını, 20.000 belgeyi okudu. Aşama sırası işlenen kaydı ve tutulan belleği değiştirdi, okunan belge sayısını değiştirmedi. Onu değiştiren tek yapı dizindir.

Dizinin kuramı burada kurulmaz. İlişkisel Veritabanı Yönetimi kursunun Dizinler ve Bölümleme konusu ağaç, karma ve ters dizini, sütun sırasını ve kısmi dizinin kazancını ölçtü; sıralı bir yapının hangi soruya yanıt verdiği orada sayılara bağlandı ve o sayılar burada geçerlidir. Bu dersin sorusu dizinin üstüne kurulduğu veridir. İlişkisel bir satırda dizinlenen sütun tek bir değer taşır; belge modelinde aynı alan bir dizi olabilir, iç içe bir belgenin içinde durabilir ya da belgelerin yalnız bir bölümünde bulunabilir. Üç durumun üçü de dizin girişinin sayısını değiştirir, ve giriş sayısı hem yeri hem yazma bedelini belirler.

Bir Belge, Birden Çok Giriş

Dizin girişi bir anahtar değeri ile o değeri taşıyan belgeye bir başvurudan oluşur. İlişkisel dizinde bu ilişki birebirdir: bir satır bir giriş üretir. Bir yol dizinin içine indiğinde ilişki bire çok olur — beş kopyası olan bir kitap, kopya.sube dizininde beş giriş üretir. Buna çoklu giriş (multikey) denir ve önceki dersteki varoluşsal kuralın depolama tarafındaki karşılığıdır: yol birden çok değere ulaşıyorsa, dizin o değerlerin hepsini ayrı ayrı adreslemek zorundadır.

Aynı belgede aynı anahtarın iki kez üretilmesi durumunda dizin tek giriş tutar; üç kopyası da Kadıköy’de olan bir kitap kopya.sube dizininde bir kez görünür. Aşağıdaki gerçekleştirim üç kararı da taşır: çoklu giriş, bileşik anahtar ve kısmi koşul.

// dizin.mjs — belge dizini. Bir yol dizinin icine indiginde tek belge birden cok giris
// uretir: coklu giris. Bilesik anahtar, oge duzeyi anahtar ve kismi kosul desteklenir.
// Giris bayti 01. dersin kodlama kuralindan gelir; her giris ayrica belgeye 8 baytlik
// bir basvuru tasir.
const BASVURU = 8;

export function bayt(d) {
  if (d === null || d === undefined) return 0;
  if (typeof d === "boolean") return 1;
  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);
}

// Yoldaki degerler: yol bir diziye rastlarsa ogelerine iner.
export function degerler(kok, yol) {
  let kume = [kok];
  for (const ad of yol.split(".")) {
    const sonraki = [];
    for (const d of kume)
      for (const o of Array.isArray(d) ? d : [d])
        if (o && typeof o === "object" && !Array.isArray(o) && ad in o) sonraki.push(o[ad]);
    kume = sonraki;
  }
  return kume.flatMap((v) => (Array.isArray(v) ? v : [v]));
}

export class Dizin {
  // alanlar: anahtari kuran yollar. oge: verilirse anahtar o dizinin OGESI uzerinde
  // kurulur ve yollar ogeye goredir. kosul: verilirse yalniz kosulu saglayan belge ya da
  // oge dizine girer -- kismi dizin.
  constructor(ad, { alanlar, oge = null, kosul = null }) {
    Object.assign(this, { ad, alanlar, oge, kosul });
    this.giris = new Map();                         // anahtar -> belge anahtari dizisi
    this.ham = 0;                                   // tekillestirmeden onceki giris
    this.taranan = 0;
  }
  anahtarlar(belge) {                               // bir belgenin urettigi anahtarlar
    const kaynak = this.oge
      ? degerler(belge, this.oge).filter((o) => !this.kosul || this.kosul(o))
      : !this.kosul || this.kosul(belge) ? [belge] : [];
    const cikan = [];
    for (const k of kaynak) {
      let carpim = [[]];
      for (const alan of this.alanlar) {
        const d = degerler(k, alan);
        carpim = carpim.flatMap((on) => (d.length ? d : [null]).map((v) => [...on, v]));
      }
      cikan.push(...carpim);
    }
    return cikan;
  }
  kur(belgeler) {
    for (const b of belgeler) {
      const uretilen = this.anahtarlar(b).map((a) => JSON.stringify(a));
      this.ham += uretilen.length;
      for (const a of new Set(uretilen)) {           // ayni belgede ayni anahtar bir kez
        if (!this.giris.has(a)) this.giris.set(a, []);
        this.giris.get(a).push(b._k);
      }
    }
    return this;
  }
  get sayi() {
    return [...this.giris.values()].reduce((t, v) => t + v.length, 0);
  }
  get bayt() {
    let n = 0;
    for (const [a, k] of this.giris)
      n += k.length * (JSON.parse(a).reduce((t, v) => t + bayt(v), 0) + BASVURU);
    return n;
  }
  ara(...anahtar) {                                  // tam anahtarla arama
    const k = this.giris.get(JSON.stringify(anahtar)) ?? [];
    this.taranan += k.length;
    return k;
  }
}

oge seçeneği belge modeline özgü olan ayrımı taşır. alanlar: ["kopya.sube", "kopya.durum"] ile kurulan bileşik dizin iki yolu ayrı ayrı çözer ve değerlerinin çarpımını alır; oge: "kopya" ile kurulan dizin ise anahtarı her kopyanın kendi içinde kurar. İkisi aynı iki alanı taşır ve farklı soruya yanıt verir.

Yedi Dizin, Aynı Katalog

NS7 (varsayım): katalog 20.000 kitap belgesidir, her kitabın 1–5 kopyası ve 2–4 etiketi vardır, tohum 424242’dir. NS13 (varsayım): bir dizin girişi anahtar değerinin kodlanmış hâline ek olarak 8 baytlık bir belge başvurusu taşır ve düğüm doluluğu tam sayılır. Gerekçe: başvuru genişliği ile doluluk motora göre değişir; ikisi de dizinler arasındaki oranı değiştirmez, yalnız mutlak baytı ölçekler.

Ölçülen iş, önceki iki dersin sorusudur: Kadıköy şubesinde onarımdaki kopyası olan kitaplar. Yanıt dört yoldan alınır ve her yolda taranan dizin girişi ile okunan belge sayılır. Ardından, ikinci dersin üçüncü işi olan 5.000 kopya durumu değişikliği dizin bakımı açısından iki kararla koşturulur.

// dizin-olcum.mjs — ayni katalog uzerinde yedi dizin kurulur, ayni soru dort yoldan
// yanitlanir ve 5.000 durum degisikliginin dizin bakimi iki kararla sayilir.
// Ayni dizinde dizin.mjs bulunur.
import { Dizin, bayt } from "./dizin.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)]);
  KATALOG.push({ _k: `K-${String(i).padStart(5, "0")}`, yazar: `Yazar ${i % 4000}`,
    yayin_yili: 1950 + (i % 75), etiket, kopya });
}
const HARITA = new Map(KATALOG.map((b) => [b._k, b]));
const DERLEM = KATALOG.reduce((t, b) => t + bayt(b), 0);
const KOPYA = KATALOG.reduce((t, b) => t + b.kopya.length, 0);
console.log(`katalog ${KATALOG.length} belge, ${KOPYA} kopya, derlem ${DERLEM} bayt`);

const [yazarD, etiketD, subeD, durumD, carpimD, ogeD, kismiD] = [
  new Dizin("yazar", { alanlar: ["yazar"] }),
  new Dizin("etiket", { alanlar: ["etiket"] }),
  new Dizin("kopya.sube", { alanlar: ["kopya.sube"] }),
  new Dizin("kopya.durum", { alanlar: ["kopya.durum"] }),
  new Dizin("etiket + kopya.sube", { alanlar: ["etiket", "kopya.sube"] }),
  new Dizin("oge(sube,durum)", { alanlar: ["sube", "durum"], oge: "kopya" }),
  new Dizin("kismi oge(sube)", { alanlar: ["sube"], oge: "kopya",
    kosul: (o) => o.durum === "onarimda" }),
].map((d) => d.kur(KATALOG));
const DIZINLER = [yazarD, etiketD, subeD, durumD, carpimD, ogeD, kismiD];

for (const d of DIZINLER)
  console.log(`${d.ad.padEnd(19)} ham ${String(d.ham).padStart(6)}` +
    `  giris ${String(d.sayi).padStart(6)}  belge basina ${(d.sayi / 20000).toFixed(2)}` +
    `  ${String(d.bayt).padStart(7)} bayt  derlemin %${(100 * d.bayt / DERLEM).toFixed(1)}`);

// Ayni soru: Kadikoy subesinde onarimdaki kopyasi olan kitaplar.
const OGE = (k) => k.sube === "Kadikoy" && k.durum === "onarimda";
const dogru = KATALOG.filter((b) => b.kopya.some(OGE)).length;
const suz = (anahtarlar) => anahtarlar.filter((k) => HARITA.get(k).kopya.some(OGE)).length;
console.log(`kosulu saglayan kopya ${KATALOG.reduce((t, b) => t + b.kopya.filter(OGE).length, 0)}` +
  `, bu kopyalari tasiyan belge ${dogru}`);

const kesisim = new Set(subeD.ara("Kadikoy"));
const B = durumD.ara("onarimda").filter((k) => kesisim.has(k));
const C = ogeD.ara("Kadikoy", "onarimda");
const D = kismiD.ara("Kadikoy");
const yollar = [
  ["A tarama", 0, KATALOG.map((b) => b._k)],
  ["B iki tek alan dizini", subeD.taranan + durumD.taranan, B],
  ["C oge duzeyi bilesik", ogeD.taranan, C],
  ["D kismi oge dizini", kismiD.taranan, D],
];
for (const [ad, taranan, kume] of yollar)
  console.log(`${ad.padEnd(22)} taranan giris ${String(taranan).padStart(6)}` +
    `  okunan belge ${String(kume.length).padStart(5)}  sonuc ${suz(kume)}`);

// 5.000 kopyanin durumu degisir: dizin bakimi iki kararla sayilir.
const anahtarKumesi = (d, b) => [...new Set(d.anahtarlar(b).map((a) => JSON.stringify(a)))];
const fark = (a, b) => a.filter((v) => !b.includes(v)).length
  + b.filter((v) => !a.includes(v)).length;
let farkli = 0, tam = 0, gercek = 0;
for (let i = 0; i < 5000; i += 1) {
  const kitap = KATALOG[(i * 7) % KATALOG.length];
  const hedef = kitap.kopya[i % kitap.kopya.length];
  const eski = DIZINLER.map((d) => anahtarKumesi(d, kitap));
  if (hedef.durum !== "rafta") gercek += 1;
  hedef.durum = "rafta";
  const yeni = DIZINLER.map((d) => anahtarKumesi(d, kitap));
  for (let j = 0; j < DIZINLER.length; j += 1) {
    farkli += fark(eski[j], yeni[j]);
    tam += eski[j].length + yeni[j].length;
  }
}
console.log(`5.000 durum degisikliginin ${gercek} tanesi degeri gercekten degistiriyor`);
console.log(`  fark bakimi             ${farkli} giris yazmasi`);
console.log(`  belge yeniden dizinleme ${tam} giris yazmasi, ${(tam / farkli).toFixed(1)} kat`);
katalog 20000 belge, 59494 kopya, derlem 6388114 bayt
yazar               ham  20000  giris  20000  belge basina 1.00   454450 bayt  derlemin %7.1
etiket              ham  59730  giris  50179  belge basina 2.51   911722 bayt  derlemin %14.3
kopya.sube          ham  59494  giris  47982  belge basina 2.40   976310 bayt  derlemin %15.3
kopya.durum         ham  59494  giris  38997  belge basina 1.95   766544 bayt  derlemin %12.0
etiket + kopya.sube ham 177480  giris 120042  belge basina 6.00  3664167 bayt  derlemin %57.4
oge(sube,durum)     ham  59494  giris  55516  belge basina 2.78  1777092 bayt  derlemin %27.8
kismi oge(sube)     ham  20021  giris  18752  belge basina 0.94   381997 bayt  derlemin %6.0
kosulu saglayan kopya 3508, bu kopyalari tasiyan belge 3340
A tarama               taranan giris      0  okunan belge 20000  sonuc 3340
B iki tek alan dizini  taranan giris  21248  okunan belge  6033  sonuc 3340
C oge duzeyi bilesik   taranan giris   3340  okunan belge  3340  sonuc 3340
D kismi oge dizini     taranan giris   3340  okunan belge  3340  sonuc 3340
5.000 durum degisikliginin 3311 tanesi degeri gercekten degistiriyor
  fark bakimi             10840 giris yazmasi
  belge yeniden dizinleme 173994 giris yazmasi, 16.1 kat

Giriş Sayısının Bedeli

İlk satırlar dizin boyutunun kaynağını verir. yazar dizini belge başına tam bir giriş üretir ve derlemin %7,1’i kadar yer tutar; bu, ilişkisel bir dizinin oranıdır. etiket dizini 59.730 ham giriş üretir, tekilleştirmeden sonra 50.179 kalır — belge başına 2,51. Aradaki 9.551 giriş aynı etiketin bir kitapta iki kez yazılmasından gelir ve dizin bunu tek girişe indirir. kopya.durum dizininde tekilleştirme daha da güçlüdür: 59.494 ham giriş 38.997’ye iner, çünkü üç değerli bir alanda bir kitabın birden çok kopyasının aynı durumda olması sıradandır.

Beşinci satır dersin en sert sayısıdır. etiket ile kopya.sube tek bir bileşik dizinde birleştirildiğinde giriş sayısı toplam değil çarpım olur: 177.480 ham giriş, 120.042 tekil giriş, belge başına altı. Dizin 3.664.167 bayt tutar, yani derlemin %57,4’ü. İki dizi alanının aynı bileşik dizinde bulunmasının motorlar tarafından engellenmesinin nedeni budur; engel bir kolaylık değil, bu çarpımın önlenmesidir. Aynı iki alan ayrı iki dizinde tutulduğunda toplam 98.161 giriş ve 1.888.032 bayt eder — yarısından az.

Aynı Ögede Olma Koşulu

Dört yol aynı 3.340 belgeyi döndürür; ayrıldıkları yer bu yanıta ulaşmak için okunan belgedir. Tarama 20.000 belge okur. İki tek alan dizininin kesişimi 21.248 giriş tarar ve 6.033 belge okur — üçüncü dersin belge düzeyi bağlaç sayısının tam olarak kendisi. Dizin kesişimi “Kadıköy’de kopyası olan” ile “onarımda kopyası olan” kümelerini keser ve koşulların aynı kopyada karşılandığını doğrulayamaz; o doğrulamayı belgeyi okuyarak yapmak zorundadır. Fazladan okunan 2.693 belge bu yüzden okunur.

Öge düzeyi bileşik dizin bu işi dizinin içinde yapar. Anahtar her kopyanın kendi şubesi ve durumundan kurulduğu için ("Kadikoy", "onarimda") araması doğrudan 3.340 belge başvurusu verir; taranan giriş sayısı da 3.340’tır. Koşulu sağlayan kopya sayısı 3.508’dir — aradaki 168 kopya, aynı kitabın aynı şubede onarımdaki ikinci kopyasıdır ve dizin bunları tek girişe indirmiştir. Okunan belge sayısı taramaya göre altıda bire, dizin kesişimine göre 1,8 kat düşer ve okunan her belge sonuca kalır.

Bedeli boyuttur: bu dizin 1.777.092 bayt, derlemin %27,8’i. Kısmi dizin aynı yanıtı çok daha ucuza verir. durum alanı onarimda olan kopyalar 59.494’ün 20.021’idir; yalnız onları kapsayan dizin 18.752 giriş ve 381.997 bayt tutar, derlemin %6’sı. Aynı arama aynı 3.340 belgeyi aynı sayıda giriş tarayarak döndürür. Kısıtı, kapsadığı koşulun sorguda bulunmasıdır: bu dizin “Kadıköy’de raftaki kopyalar” sorusuna hiçbir şey söyleyemez, çünkü o kopyalar dizinde yoktur.

Yazmanın Bedeli

Son üç satır dizinin öteki yüzüdür. 5.000 kopya durum değişikliğinin 3.311’i değeri gerçekten değiştirir. Değişen anahtarları hesaplayıp yalnız onları yazan bakım yedi dizin için 10.840 giriş yazması yapar — denenen değişiklik başına 2,17. Aynı işi belgenin bütün girişlerini silip yeniden üreterek yapan bakım 173.994 giriş yazması yapar, 16,1 kat. Fark ikinci dersin bulgusuyla aynı kökten gelir: gömülü şemada bir kopyanın durumu değiştiğinde kitabın tamamı yeniden yazılır, ve yeniden yazılan bir belgenin bütün dizin girişlerini yeniden üretmek en kolay uygulamadır. Depo hangi alanın değiştiğini biliyorsa bu 16 katın tamamından kaçınır; bilmiyorsa dizin sayısı arttıkça yazma bedeli doğrusal büyür.

Dizin başına düşen paya bakıldığında karar netleşir: yedi dizinin toplamı 8.932.282 bayt, derlemin 1,4 katı. Belge modelinde dizin, alan başına değil giriş başına ücretlendirilir ve giriş sayısını belirleyen şey şemanın kendisidir.

Özet

  • Bir yol dizinin içine indiğinde tek belge birden çok dizin girişi üretir; kopya.sube dizini 20.000 belgeden 59.494 ham, tekilleştirmeden sonra 47.982 giriş üretir.
  • İki dizi alanını tek bileşik dizinde birleştirmek girişleri toplamaz, çarpar: 120.042 giriş ve derlemin %57,4’ü kadar yer.
  • İki tek alan dizininin kesişimi koşulların aynı ögede karşılandığını doğrulayamaz; 6.033 belge okur, bunların 2.693’ü sonuca kalmaz.
  • Öge düzeyi bileşik dizin aynı yanıtı 3.340 giriş tarayıp 3.340 belge okuyarak verir; kısmi dizin aynı yanıtı derlemin %27,8’i yerine %6’sı kadar yer kaplayarak verir.
  • Değişen anahtarı hesaplayan bakım 5.000 güncellemeye 10.840 giriş yazması harcar, belgeyi bütünüyle yeniden dizinleyen bakım 173.994 (16,1 kat).

Sonraki Adım

Bu dersteki bütün dizinler var olan alanların üstüne kuruldu ve alanların ne taşıdığına hiç bakmadı. kopya.durum dizini onarimda yerine onarım yazılmış bir kopyayı da sessizce dizinler; birinci dersin ölçtüğü metin yazılmış yayın yılları da yayin_yili dizinine kendi tipleriyle girer. Esnek şema, alanın bulunup bulunmamasını ve tipini yazma anında bağlamaz — birinci ders bunun aralık sorgusunda 3.990 yerine 5.991 ya da 3.491 belge döndürdüğünü saymıştı ve kuralın nasıl konacağını bu derse bırakmıştı. Sonraki ders o kuralı yazar ve iki sayıyı ölçer: kuralın yakaladığı bozuk belge sınıfı ile kaçırdığı sınıf, ve kural derlem doluyken konduğunda ortaya çıkan ihlal sayısıyla geriye dönük düzeltmenin dokunduğu belge sayısı.

İ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