İçeriğe geç
academia.sh

Ders 33 / 34

Sorgu Maliyeti ve Derinlik Sınırı

Çevrimli tip sisteminde sorgu derinliğinin sınırsız büyümesi, çalıştırmadan önce hesaplanan maliyet üst sınırı ve derinlik sınırının tek başına yetmediğini gösteren ölçüm.

İçindekiler

Yetkilendirme sorgunun neyi görebileceğini sınırladı, ne kadar isteyebileceğini sınırlamadı. Sayfalama dersi bunu açıkta bırakmıştı: her alan kendi sınırına uysa da iç içe listelerin çarpımı yanıtı büyütebiliyordu.

Asıl sorun tip sisteminin çevrimli olmasıdır. Ödünç kaydından üyeye, üyeden ödünçlerine, oradan yeniden üyeye gidilebilir. Şemada bu çevrim tek bir satır alan çift yönlü bir ilişkidir; sorguda ise istenen derinlikte açılabilir. Bu ders sorguyu çalıştırmadan önce ölçer ve sınırı aşanı reddeder.

Bu dersteki ayrıştırıcı, düzleyici ve çalıştırıcı önceki derslerde yazıldıkları hâliyle kullanılır; şemaya sayfa boyu argümanı eklenir ve yeni olan ölçüm katmanıdır. Veri kaynağı üç üye ve altı ödünç kaydı içerir.

// veri.mjs — 3 uye, 6 odunc; tip sistemi cevrimli oldugu icin derinlik sinirsiz gezilebilir
export const UYELER = new Map(Array.from({ length: 3 }, (_, i) =>
  [`U-${1001 + i}`, { id: `U-${1001 + i}`, olusturma: "2025-01-01", ad: `Üye ${i}`, ceza: i * 5 }]));

export const ODUNCLER = Array.from({ length: 6 }, (_, i) => ({
  id: `O-${i + 1}`, olusturma: `2026-01-0${i + 1}`, uyeId: `U-${1001 + (i % 3)}`,
  durum: "ACIK", iadeTarihi: "2026-04-01", sube: "merkez", not: null,
  kalemler: [{ isbn: "978-0262033848", sube: "merkez" }],
}));

export const COZUCULER = {
  __tip: { Kayit: (d) => (d.kalemler ? "Odunc" : "Uye") },
  Sorgu: {
    odunc: (_, a) => ODUNCLER.find((o) => o.id === a.id) ?? null,
    oduncler: (_, a) => ODUNCLER.slice(0, a.ilk ?? ODUNCLER.length),
    uye: (_, a) => UYELER.get(a.kimlik) ?? null,
  },
  Odunc: { uye: (o) => UYELER.get(o.uyeId) },
  Uye: { oduncler: (u, a) => ODUNCLER.filter((o) => o.uyeId === u.id).slice(0, a.ilk ?? ODUNCLER.length) },
};

Ölçüm, bir liste alanının kaç kayıt döndüreceğini ilk argümanından okur. Argüman şemada bildirilmemişse onu kullanan sorgu denetimden geçemez; bu yüzden iki liste alanı sayfa boyu argümanıyla genişletilir. Argümanın isteğe bağlı olması dersin sonunda tartışılacak soruyu şimdiden kuruyor: verilmediğinde ölçüm neye dayanacak?

// sema-liste.mjs — liste alanlarina sayfa boyu argumani eklenmis sema
import { SEMA } from "./sema.mjs";

const LISTE_ALANI = { tip: "[Odunc!]!", argumanlar: { durum: "Durum", ilk: "Int" } };

export const SEMA_L = {
  ...SEMA,
  tipler: {
    ...SEMA.tipler,
    Uye:   { ...SEMA.tipler.Uye,   alanlar: { ...SEMA.tipler.Uye.alanlar,   oduncler: LISTE_ALANI } },
    Sorgu: { ...SEMA.tipler.Sorgu, alanlar: { ...SEMA.tipler.Sorgu.alanlar, oduncler: LISTE_ALANI } },
  },
};

Maliyetin Hesaplanması

Sorgunun maliyeti, çalıştırılmadan önce yalnız belge ve şemadan hesaplanabilir. Model basittir: bir alanın üreteceği düğüm sayısı, üst düzeyden gelen çarpanla kendi çarpanının çarpımıdır. Liste alanlarının çarpanı ilk argümanından okunur; argüman verilmemişse şemanın öngördüğü bir varsayılan kullanılır.

// maliyet.mjs — sorguyu calistirmadan derinligini ve maliyetini hesaplar
// Maliyet modeli: bir alanin uretecegi dugum sayisi = ust carpan × kendi carpani.
// Liste alanlarinin carpani "ilk" argumanindan, yoksa varsayilandan gelir.
import { tipCoz, alanTanimi } from "./sema.mjs";
import { duzle } from "./duzle.mjs";

const YAPRAK = new Set(["skaler", "sirali"]);

export function olc(sema, belge, islem, { varsayilanCarpan = 10, degiskenler = {} } = {}) {
  const kok = islem.tur === "mutation" ? sema.mutasyon : sema.sorgu;
  const carpanOku = (a) => {
    const d = a.argumanlar.ilk;
    if (!d) return null;
    return d.tur === "degisken" ? degiskenler[d.ad] : d.deger;
  };

  let enDerin = 0, maliyet = 0;
  const gez = (tipAdi, secim, derinlik, ustCarpan) => {
    enDerin = Math.max(enDerin, derinlik);
    for (const a of duzle(sema, belge, tipAdi, secim)) {
      const tanim = alanTanimi(sema.tipler[tipAdi], a.ad);
      if (!tanim) continue;
      const t = tipCoz(tanim.tip);
      const carpan = ustCarpan * (t.liste ? (carpanOku(a) ?? varsayilanCarpan) : 1);
      maliyet += carpan;
      if (!YAPRAK.has(sema.tipler[t.ad].tur)) gez(t.ad, a.secim, derinlik + 1, carpan);
    }
  };
  gez(kok, islem.secim, 1, 1);
  return { derinlik: enDerin, maliyet };
}

// Sinirlari asan sorgu calistirilmadan reddedilir.
export function sinirDenetle(olcum, { enCokDerinlik, enCokMaliyet }) {
  const hatalar = [];
  if (olcum.derinlik > enCokDerinlik) hatalar.push(`derinlik ${olcum.derinlik}, sınır ${enCokDerinlik}`);
  if (olcum.maliyet > enCokMaliyet) hatalar.push(`maliyet ${olcum.maliyet}, sınır ${enCokMaliyet}`);
  return hatalar;
}

Hesabın parçaları düzleyiciden geçtiğine dikkat edilmeli: parça içine gizlenmiş bir derinlik ölçüme girer. Ölçüm yalnız yazılı seçim kümesine bakan bir sayaç olsaydı, sorgu parçalara bölünerek sınır aşılabilirdi.

Ölçüm

// kosu.mjs — sorgularin derinligini ve maliyetini olcer, sinirlari uygular, tahmini sinar
import { ayristir } from "./ayristirici.mjs";
import { calistir } from "./calistirici.mjs";
import { olc, sinirDenetle } from "./maliyet.mjs";
import { SEMA_L as SEMA } from "./sema-liste.mjs";
import { COZUCULER } from "./veri.mjs";

const SINIR = { enCokDerinlik: 6, enCokMaliyet: 1000 };

// Cevrimli tip sistemi: odunc -> uye -> oduncler -> uye -> ...
const cevrim = (kat) => {
  let ic = "id";
  for (let i = 0; i < kat; i++) ic = `id uye { ad oduncler { ${ic} } }`;
  return `{ oduncler { ${ic} } }`;
};

const SORGULAR = {
  "olağan sorgu": `{ oduncler { id durum uye { ad } } }`,
  "sayfalı sorgu": `{ oduncler { id uye { ad oduncler { id } } } }`,
  "çevrim ×2": cevrim(2),
  "çevrim ×5": cevrim(5),
  "sığ ama geniş": `{ a: oduncler(ilk: 500) { id uye { ad } } b: oduncler(ilk: 500) { id uye { ad } }
                     c: oduncler(ilk: 500) { id uye { ad } } d: oduncler(ilk: 500) { id uye { ad } } }`,
  "sınırlı sorgu": `{ oduncler(ilk: 6) { id durum uye { ad } } }`,
};

// Yanittaki yaprak deger sayisi: tahminin sinandigi gercek sayi
const yaprakSay = (d) =>
  d === null || typeof d !== "object" ? 1 : Array.isArray(d) ? d.reduce((t, x) => t + yaprakSay(x), 0)
    : Object.values(d).reduce((t, x) => t + yaprakSay(x), 0);

console.log("sorgu             derinlik  maliyet  karar      yanıttaki yaprak");
for (const [ad, metin] of Object.entries(SORGULAR)) {
  const belge = ayristir(metin);
  const olcum = olc(SEMA, belge, belge.islemler[0], { varsayilanCarpan: 6 });
  const hatalar = sinirDenetle(olcum, SINIR);
  let yaprak = "—";
  if (!hatalar.length) yaprak = String(yaprakSay((await calistir(SEMA, COZUCULER, belge)).data));
  console.log(`${ad.padEnd(17)} ${String(olcum.derinlik).padStart(8)}  ${String(olcum.maliyet).padStart(7)}  ` +
    `${(hatalar.length ? "RED" : "kabul").padEnd(9)}  ${yaprak.padStart(15)}${hatalar.length ? "   (" + hatalar.join("; ") + ")" : ""}`);
}
sorgu             derinlik  maliyet  karar      yanıttaki yaprak
olağan sorgu             3       30  kabul                   18
sayfalı sorgu            4       96  kabul                   24
çevrim ×2                6      600  kabul                   60
çevrim ×5               12   130632  RED                      —   (derinlik 12, sınır 6; maliyet 130632, sınır 1000)
sığ ama geniş            3     8000  RED                      —   (maliyet 8000, sınır 1000)
sınırlı sorgu            3       30  kabul                   18

Üç Satırın Söyledikleri

Çevrim ×5 satırı sorunun büyüklüğünü gösteriyor. Beş kat açılmış bir çevrim, on iki derinliğe ve yüz otuz bini aşan bir maliyete ulaşıyor. Bu sorgu yirmi satırdan kısadır ve yazması bir dakika sürer; çalıştırılırsa altı ödünç kaydının bulunduğu bir veri kaynağında bile işlemciyi uzun süre meşgul eder. Gerçek bir kütüphane verisinde sonuç bellek tükenmesidir.

Sığ ama geniş satırı derinlik sınırının neden yetmediğini gösteriyor. Bu sorgunun derinliği üçtür ve altı olan sınırın çok altındadır; ama dört kök alanın her biri beş yüz kayıt istediği için maliyeti sekiz bine çıkar. Yalnız derinliğe bakan bir koruma bu sorguyu geçirirdi. Derinlik bir kalkan değil, bir tavan ölçüsüdür; maliyet asıl sınırı çizer.

Sınırlı sorgu satırı tahminin niteliğini gösteriyor. ilk: 6 verildiğinde maliyet otuz hesaplanıyor, yanıtta on sekiz yaprak dönüyor. Tahmin gerçeğin üstündedir ve öyle olmalıdır: maliyet bileşik alanları da sayar, gerçek yaprak sayısı yalnız uçları. Bir kabul kararı tahmine dayanacaksa tahminin üst sınır olması gerekir; altında kalan bir tahmin, geçmesi gerekmeyen sorguları geçirir.

Sınırın Nereden Seçileceği

İki sayı da keyfî görünür. Seçimin dayanağı ölçümdür.

Maliyet sınırı, sunucunun karşılayabileceği yükten türetilir: bir isteğin ürettiği düğüm sayısıyla harcadığı süre arasındaki ilişki ölçülür, kabul edilebilir en uzun yanıt süresine karşılık gelen düğüm sayısı sınır olur. Derinlik sınırı ise gerçek istemcilerin kullandığı en derin sorgudan türetilir; kütükteki sorgu derinliklerinin dağılımına bakılıp gerçek kullanımın üstünde bir değer seçilir.

Varsayılan çarpan üçüncü ve en kritik sayıdır. ilk argümanı verilmemiş bir liste alanının kaç kayıt döndüreceği bilinemez, dolayısıyla tahmin bir varsayıma dayanır. İki çözüm vardır: liste alanlarında ilk argümanını zorunlu kılmak, ya da şemada alan başına bir üst sınır bildirmek. İkisi de yapılmazsa varsayılan çarpan bir tahmin olarak kalır ve gerçek yanıt tahminin üstüne çıkabilir; bu da maliyet korumasının temel varsayımını bozar.

Reddin nasıl bildirileceği ayrı bir karardır. Sorgu çalıştırılmadığı için kısmi veri yoktur; yanıt yalnız hata taşır. Hatanın ölçülen değerleri ve sınırları bildirmesi gerekir, çünkü istemcinin yapması gereken şey sorgusunu küçültmektir ve neyi küçülteceğini bilmelidir. Yalnız “sorgu çok karmaşık” diyen bir hata, geliştiriciyi deneme yanılmaya bırakır.

Özet

  • Çevrimli tip sistemi sorgu derinliğine kendiliğinden bir sınır koymaz; kısa bir sorgu keyfî derinliğe açılabilir.
  • Maliyet, yalnız belge ve şemadan çalıştırmadan önce hesaplanır: bir alanın üreteceği düğüm sayısı üst çarpanla kendi çarpanının çarpımıdır.
  • Ölçüm parçalar açıldıktan sonra yapılır; yoksa sorgu parçalara bölünerek sınır aşılabilir.
  • Derinlik sınırı tek başına yetmez: derinliği üç olan bir sorgu, kök alanları çoğaltarak maliyet sınırını sekiz katına çıkarabilir.
  • Maliyet tahmini gerçek yaprak sayısının üstünde olmalıdır; altında kalan bir tahmin geçmemesi gereken sorguları geçirir.
  • Varsayılan çarpanın güvenilir olması için liste alanlarında ilk zorunlu kılınmalı ya da şemada alan başına üst sınır bildirilmelidir; ret bildirimi ölçülen değeri ve sınırı içermelidir.

Sonraki Adım

Buraya kadar yazılan her şey tek bir süreç içinde çalıştı: sorgu bir dizgiydi, sonuç bir nesne. Gerçek bir servis sorguyu ağ üzerinden alır ve yanıtı ağ üzerinden döndürür. Bu, yalnız bir sarmalama işi değildir. Tek uç nokta ve tek yöntem kullanan bir aktarım, önceki konuda kurulan her şeyi — adres tabanlı önbellekleme, durum kodu eşlemesi, aracıların gördüğü bilgi — yeniden düşünmeyi gerektirir. Sonraki ders GraphQL’i HTTP üzerine oturtur ve bu aktarım kararının önbelleklemeye, hata bildirimine ve ölçüme ne yaptığını ölçer.

İ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