---
title: 'Sorgu Maliyeti ve Derinlik Sınırı'
source: 'https://academia.sh/tr/kurslar/api-tasarimi/sorgu-maliyeti-ve-derinlik-siniri'
course: 'Web API Tasarımı'
language: tr
updated: '2026-08-17T18:06:45+00:00'
license: 'CC BY-SA 4.0'
---

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

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.

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

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

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

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