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