---
title: 'Konum Tabanlı Servis'
source: 'https://academia.sh/tr/kurslar/vaka-calismalari/konum-tabanli-servis'
course: 'Vaka Çalışmaları'
language: tr
updated: '2026-08-17T18:10:55+00:00'
license: 'CC BY-SA 4.0'
---

# Konum Tabanlı Servis

Sıralanabilir bir anahtarı olmayan sorgunun vakası: yakınlık sorgusunun düz taramayla karşılaştırılması, uzamsal anahtarın taradığı aday sayısının gerçek veri üzerinde ölçülmesi, hücre boyunun aday sayısı ile dizin araması arasındaki ödünleşimi belirlemesi ve komşu hücre sorgulanmadığında kaçırılan sonucun sayılması.

Önceki vakada bir kaydı bulmak kolaydı: seri kimliği ile zaman aralığı verildiğinde okunacak
satırlar tek bir yerde ve sıralı duruyordu. Bu vaka o kolaylığı kaldırır. Kayıtlar iki boyutlu bir
düzlemde durur ve soru "şu noktaya yakın olanlar" biçiminde gelir. Yakınlığın sıralı bir anahtarı
yoktur: iki boyutu tek bir eksende sıralamanın her yolu bazı komşuları birbirinden uzağa düşürür,
çünkü bir eksende ardışık olan iki nokta düzlemde uzak, düzlemde komşu olan iki nokta eksende uzak
olabilir.

Çözüm düzlemi hücrelere bölmek ve her kayda bir **uzamsal anahtar** (geohash) yazmaktır: aynı
hücredeki kayıtlar aynı anahtarı taşır ve **yakınlık sorgusu** önce hücreleri, sonra hücre içindeki
adayları tarar. Ölçülecek karar hücre boyudur ve iki yönlü bir ödünleşim taşır: hücre büyüdükçe
taranan aday artar, hücre küçüldükçe sorgulanması gereken hücre sayısı artar.

## Kısıtlar

İşlevsel gereksinim: yer kaydetme, hareketli nesnenin konumunu güncelleme, verilen bir noktanın
belirli yarıçapı içindeki yerleri döndürme ve sonucu uzaklığa göre sıralama.

İşlevsel olmayan gereksinim, sayıyla: yakınlık sorgusu yarıçap içindeki kayıtların **tamamını**
döndürür, kaçırma oranı **sıfırdır**; sorgu başına taranan aday **1000**'i geçmez; tasarım tepe
**1666,67** sorgu/s ve **24.000** konum güncellemesi/s taşır.

Kapsam daraltması: rota bulma, tahmini varış süresi, harita çizimi, yer sıralaması ve coğrafi
projeksiyon dönüşümleri tasarlanmaz; bu vakada bölge küçük olduğu için düzlem yaklaşımı kullanılır
ve bu bir model basitleştirmesidir.

## Varsayımlar

| Kod | Varsayım | Değer | Gerekçe |
|---|---|---|---|
| KT1 | kayıtlı yer | 40.000.000 | hizmet alanındaki toplam nokta |
| KT2 | günlük etkin kullanıcı | 6.000.000 | yakınlık sorgusu yapanlar |
| KT3 | kullanıcı başına günlük sorgu | 8 | arama ve harita gezinmesi |
| KT4 | tepe çarpanı | 3 | tepe saatin gün ortalamasına oranı |
| KT5 | yer kaydı | 320 bayt | ad, konum, kategori, çalışma saatleri |
| KT6 | hareketli nesne | 120.000 | konumu sürekli değişen taşıyıcılar |
| KT7 | konum güncelleme aralığı | 15 s | taşıyıcının bildirim sıklığı |
| KT8 | yerleşim bölgesinde yoğunluk | 20 yer/km² | model bölgenin ortalaması |
| KT9 | bir hücre aramasının bedeli | 20 aday | dizin araması ile satır taramasının oranı |
| KT10 | yakınlık yarıçapı | 1 km | yürüme mesafesi sorgusu |

KT9 bu vakanın karar verdiren varsayımıdır: hücre boyu seçimi bir dizin aramasının kaç satır
taramasına denk olduğuna bağlıdır ve bu oran depo motoruna göre değişir.

## Ölçek

```js
// konum-olcek.mjs — KT tablosundan cikan olcek hesabi; hepsi aritmetiktir
const KT = { yer: 40_000_000, kullanici: 6_000_000, sorgu: 8, tepe: 3, yerBayt: 320,
  hareketli: 120_000, guncellemeSn: 15, yogunluk: 20, yaricapKm: 1, adayButce: 1000 };
const GUN = 86_400;
const sorguSn = (KT.kullanici * KT.sorgu) / GUN;
const guncellemeSn = KT.hareketli / KT.guncellemeSn;

for (const [ad, d] of [
  ["tepe yakinlik sorgusu/s", sorguSn * KT.tepe],
  ["tepe konum guncellemesi/s", guncellemeSn * KT.tepe],
  ["guncelleme / sorgu orani", guncellemeSn / sorguSn],
  ["yer verisi GB", (KT.yer * KT.yerBayt) / 1e9],
  ["yaricap alani km2", Math.PI * KT.yaricapKm ** 2],
  ["alandaki beklenen yer", Math.PI * KT.yaricapKm ** 2 * KT.yogunluk],
]) console.log(ad.padEnd(26) + d.toFixed(2).padStart(14));

const duz = sorguSn * KT.tepe * KT.yer;
console.log(`\nduz tarama: sorgu basina ${KT.yer.toLocaleString("tr-TR")} kayit -> tepede ` +
  `${duz.toExponential(2)} kayit/s`);
console.log(`aday butcesi ${KT.adayButce} olursa tepede ${(sorguSn * KT.tepe * KT.adayButce)
  .toLocaleString("tr-TR", { maximumFractionDigits: 0 })} kayit/s (duz taramanin 1/${KT.yer / KT.adayButce}'i)`);
```

```
tepe yakinlik sorgusu/s          1666.67
tepe konum guncellemesi/s       24000.00
guncelleme / sorgu orani           14.40
yer verisi GB                      12.80
yaricap alani km2                   3.14
alandaki beklenen yer              62.83

duz tarama: sorgu basina 40.000.000 kayit -> tepede 6.67e+10 kayit/s
aday butcesi 1000 olursa tepede 1.666.667 kayit/s (duz taramanin 1/40000'i)
```

Bu sayılar **hesap** sınıfındadır ve üçü tasarımı belirliyor. Birincisi, bu vakada veri hacmi küçük:
40.000.000 yer kaydı 12,80 GB tutuyor, önceki üç vakanın terabaytlarının yanında hiç. Yoğunluk
buradaki veri hacminde değil, **sorgu başına dokunulan kayıt sayısında**. Düz tarama tepede saniyede
66,7 milyar kayıt okumak demek; aday sayısı 1000'de tutulursa aynı yük 1.666.667 kayıt/s'ye, yani
kırk binde birine iniyor. İkincisi, yazma okumadan baskın: konum güncellemesi sorgunun 14,40
katı ve tepede saniyede 24.000. Üçüncüsü, bir sorgunun gerçek yanıtı ortalama 62,83 kayıt; aday
bütçesi bu sayının on altı katı, yani tasarım baştan bir israfa razı oluyor.

## Aday Sayısı

Hücre boyu ancak gerçek bir dağılım üzerinde savunulabilir, çünkü noktalar düzgün dağılmaz. Aşağıdaki
model 100 × 100 km'lik bir düzleme kümelenmiş 200.000 nokta koyar, beş hücre boyu için uzamsal
anahtarı bir dizine yazar ve aynı 200 sorguyu hepsine birden sorar.

```js
// uzamsal.mjs — node:sqlite ile gercek veri uzerinde uzamsal anahtarin taradigi aday sayisi.
// Bolge 100x100 km'lik bir duzlem olarak modellenir (kucuk bolgede duzlem yaklasimi); noktalar
// kumelenmis, uretec kendi yazilmis ve tohum gorunur. Model oldugu yazilir.
import { DatabaseSync } from "node:sqlite";

const N = 200_000, KENAR = 100, R = 1, SORGU = 200, TOHUM = 20260802, KUME = 20, ARAMA = 20;
const HUCRE = [0.5, 1, 2, 4, 8];                     // hucre kenar uzunlugu, km
let durum = TOHUM;
const rast = () => { durum = (durum * 1103515245 + 12345) % 2147483648; return durum / 2147483648; };
const merkez = Array.from({ length: KUME }, () => [rast() * KENAR, rast() * KENAR]);
function nokta() {                                    // %60 kume cevresinde, %40 duzgun dagilim
  if (rast() < 0.4) return [rast() * KENAR, rast() * KENAR];
  const [mx, my] = merkez[Math.floor(rast() * KUME)];
  const a = rast() * 2 * Math.PI, d = -Math.log(1 - rast()) * 3;
  return [Math.min(KENAR, Math.max(0, mx + d * Math.cos(a))), Math.min(KENAR, Math.max(0, my + d * Math.sin(a)))];
}
const kod = (x, y, c) => Math.floor(y / c) * 10_000 + Math.floor(x / c);

const db = new DatabaseSync(":memory:");
db.exec(`CREATE TABLE yer(id INTEGER PRIMARY KEY, x REAL, y REAL, ${HUCRE.map((_, i) => `h${i} INTEGER`).join(", ")});
${HUCRE.map((_, i) => `CREATE INDEX yer_h${i} ON yer(h${i});`).join("\n")}`);
db.exec("BEGIN");
const ekle = db.prepare(`INSERT INTO yer VALUES(?,?,?,${HUCRE.map(() => "?").join(",")})`);
const noktalar = [];
for (let i = 0; i < N; i += 1) {
  const [x, y] = nokta();
  noktalar.push([x, y]);
  ekle.run(i, x, y, ...HUCRE.map((c) => kod(x, y, c)));
}
db.exec("COMMIT");
const sorgular = Array.from({ length: SORGU }, nokta);
const yakin = (q, x, y) => (x - q[0]) ** 2 + (y - q[1]) ** 2 <= R * R;

let duzEslesen = 0;                                   // duz tarama: dogrulugun olcusu
for (const q of sorgular) duzEslesen += noktalar.filter(([x, y]) => yakin(q, x, y)).length;

console.log(`model: ${N} yer, ${KENAR}x${KENAR} km, ${KUME} kume, yaricap ${R} km, ` +
  `${SORGU} sorgu, tohum ${TOHUM}`);
console.log(`duz tarama: sorgu basina ${N} aday, ${(duzEslesen / SORGU).toFixed(2)} eslesme\n`);
console.log("hucre km".padEnd(10) + "sorgulanan".padStart(12) + "aday".padStart(10) +
  "isabet".padStart(9) + "esdeger is".padStart(12) + "kendi hucresi".padStart(15) +
  "kacirilan".padStart(11));
const IS = {};
for (const c of HUCRE) {
  const i = HUCRE.indexOf(c), k = Math.ceil(R / c);
  let aday = 0, eslesen = 0, hucre = 0, kendi = 0;
  for (const q of sorgular) {
    const cx = Math.floor(q[0] / c), cy = Math.floor(q[1] / c), liste = [];
    for (let dy = -k; dy <= k; dy += 1) for (let dx = -k; dx <= k; dx += 1) liste.push((cy + dy) * 10_000 + cx + dx);
    hucre += liste.length;
    const s = db.prepare(`SELECT x, y FROM yer WHERE h${i} IN (${liste.map(() => "?").join(",")})`).all(...liste);
    aday += s.length;
    for (const r of s) if (yakin(q, r.x, r.y)) {
      eslesen += 1;
      if (Math.floor(r.y / c) === cy && Math.floor(r.x / c) === cx) kendi += 1;
    }
  }
  IS[c] = [aday / SORGU, hucre / SORGU];
  console.log(`${c}`.padEnd(10) + (hucre / SORGU).toFixed(0).padStart(12) + (aday / SORGU).toFixed(0).padStart(10) +
    `%${((eslesen / aday) * 100).toFixed(1)}`.padStart(9) +
    (aday / SORGU + (hucre / SORGU) * ARAMA).toFixed(0).padStart(12) +
    (kendi / SORGU).toFixed(2).padStart(15) +
    `%${(((eslesen - kendi) / eslesen) * 100).toFixed(1)}`.padStart(11));
}
console.log(`\neslesme her hucre boyunda duz taramayla ayni (${(duzEslesen / SORGU).toFixed(2)}): ` +
  `komsu hucreler sorgulandigi icin kacirma yok`);
console.log(`esdeger is = aday + sorgulanan hucre x ${ARAMA} (KT9: bir hucre aramasinin bedeli)`);
const [a1, h1] = IS[0.5], [a2, h2] = IS[1];
console.log(`0.5 km ile 1 km esitlendigi nokta: hucre aramasi = ${((a2 - a1) / (h1 - h2)).toFixed(2)} ` +
  `aday taramasi; bunun altinda 0.5 km, ustunde 1 km kazanir`);
```

```
model: 200000 yer, 100x100 km, 20 kume, yaricap 1 km, 200 sorgu, tohum 20260802
duz tarama: sorgu basina 200000 aday, 443.95 eslesme

hucre km    sorgulanan      aday   isabet  esdeger is  kendi hucresi  kacirilan
0.5                 25       700    %63.4        1200         105.33      %76.3
1                    9       864    %51.4        1044         181.41      %59.1
2                    9      2123    %20.9        2303         322.27      %27.4
4                    9      5251     %8.5        5431         381.24      %14.1
8                    9     13611     %3.3       13791         415.85       %6.3

eslesme her hucre boyunda duz taramayla ayni (443.95): komsu hucreler sorgulandigi icin kacirma yok
esdeger is = aday + sorgulanan hucre x 20 (KT9: bir hucre aramasinin bedeli)
0.5 km ile 1 km esitlendigi nokta: hucre aramasi = 10.25 aday taramasi; bunun altinda 0.5 km, ustunde 1 km kazanir
```

Bu sayılar **ölçüm** sınıfındadır; değerler bu koşumun tohumuna ve kümelenme parametrelerine bağlı,
sütunlar arasındaki eğilim bağlı değil. Model kümelenmiş olduğu için sorgu başına 443,95 eşleşme
çıkıyor — ölçek hesabındaki 62,83'ün yedi katı, çünkü sorgular da noktalarla aynı dağılımdan
çekiliyor ve yoğun bölgelerde toplanıyor.

Üç şey okunuyor. Birincisi **dizin doğruluğu bozmuyor:** her hücre boyunda eşleşme sayısı düz
taramanınkiyle aynı, 443,95. Uzamsal anahtar bir yaklaşıklık değil, bir ön elemedir; kesin uzaklık
her adayda ayrıca hesaplanır. İkincisi **isabet hücre boyuyla çöküyor:** 0,5 km'de taranan adayın
yüzde 63,4'ü gerçek eşleşme, 8 km'de yalnız yüzde 3,3. Sekiz kilometrelik hücrede 13.611 aday
taranıyor; aday bütçesi 1000 olan kısıt 2 km ve üstünü doğrudan eliyor.

Üçüncüsü ödünleşimin şekli. Hücre küçüldükçe aday azalıyor ama sorgulanan hücre sayısı artıyor: 0,5
km'de 25 hücre ve 700 aday, 1 km'de 9 hücre ve 864 aday. KT9'un oranıyla eşdeğer iş 1200'e karşı
1044 çıkıyor ve **1 km kazanıyor**. Kazanan tarafın oranı da yazılabilir: iki boy tam olarak bir
hücre araması 10,25 aday taramasına denk geldiğinde eşitleniyor. Bir dizin araması bundan ucuzsa 0,5
km, pahalıysa 1 km doğru seçimdir — yani karar depo motorunun bir özelliğine bağlıdır, coğrafyaya
değil.

## Tasarım

Uzamsal anahtar kayda **denormalize** edilerek yazılır (Veri Katmanı Ölçekleme kursunun Veri
Dağıtımı konusu) ve üzerine bir **dizin** konur (Veritabanları müfredatının Veritabanı Yönetimi
kursu, Dizin Türleri); ikisi birlikte **uzamsal dizini** oluşturur. Dizin mekaniği orada kuruldu,
burada seçilen tek şey anahtarın taneciğidir — 1 km. Kategori süzgeci gerektiğinde anahtar bir **bileşik dizine** girer (aynı kurs, Bileşik ve
Kısmi Dizinler): sıralama (hücre, kategori) olur, çünkü hücre her sorguda eşitlikle verilir.

**Parçalama** anahtarı uzamsal anahtarın kendisi değil, karmasıdır (Veri Dağıtımı konusu,
Parçalama). Uzamsal anahtara göre parçalanırsa yoğun bir bölgenin bütün hücreleri aynı parçaya
düşer ve modeldeki kümelenme doğrudan sıcak parçaya dönüşür; karma parçalamada bir sorgunun 9
hücresi farklı parçalara dağılır ve dokuz paralel arama olur. Bu, gecikme için ödenen bir bedeldir
ve tercih edilmesinin nedeni tepe 1666,67 sorgunun tek parçada toplanmamasıdır.

Hareketli nesnelerin konumu ayrı bir **anahtar–değer deposunda** tutulur (aynı konu, Depo Türleri):
anahtar nesne kimliği, değer son konum ve hücre. Saniyede 24.000 güncelleme yer tablosuna
yazılmaz, çünkü yer kayıtları neredeyse hiç değişmez ve iki iş yükünün aynı dizini paylaşması
dizin bakımını güncelleme hızına bağlardı. Yer sorgusu için hücre sonucu **yanında okuma** ile
önbelleğe alınır (Önbellek Mimarisi konusu); bayatlık penceresi burada güvenlidir çünkü yerler
değişmez, hareketli nesneler ise önbelleğe hiç girmez.

**Bilerek kullanılmayan kalıp: somutlaştırılmış görünüm.** Her hücre için komşularıyla birlikte
önceden hesaplanmış bir aday listesi tutmak sorguyu tek okumaya indirirdi, ama her kaydın dokuz
hücrede birden görünmesi demektir; hareketli nesnelerin saniyede 8000 güncellemesi 72.000 yazmaya
çıkar ve tepe yazma hızı üç katına çıkarken kazanç bir dizin aramasıdır. İkincisi **talep fişi
kalıbıdır** (Dayanıklılık ve Güvenilirlik kursunun Dağıtık Doğruluk konusu): en kötü durumda yanıt
gövdesi 1000 adayın süzülmüş halidir ve 320 baytlık kayıtlarla yüz kilobaytın altında kalır —
gövdeyi bir depoya koyup fiş döndürmek yanıta bir tur daha ekler.

## Elenen Alternatifler

**Düz tarama** kaçırma kısıtını kusursuz karşılar ve hiçbir dizin bakımı istemez; sorgu başına
40.000.000 kayıt okur ve tepede saniyede 66,7 milyar kayda çıkar — aday bütçesinin kırk bin katı.
**Kaba hücre**, örneğin 8 km, dizin aramasını en aza indirir (9 hücre) ama 13.611 aday tarar ve 1000
kısıtını on üç kat aşar; 2 km bile 2123 adayla kısıtın iki katındadır.

**Tek hücre sorgusu** üçüncü alternatiftir ve dokuz arama yerine bir arama ister. Onu tablonun son
iki sütunu eliyor: yalnız sorgu noktasının kendi hücresi taransaydı 1 km hücrede 443,95 eşleşmenin
yalnız 181,41'i bulunurdu, yani **yüzde 59,1 kaçırma**. Oran hücre büyüdükçe düşüyor (8 km'de yüzde
6,3) ama sıfıra inmiyor, çünkü sorgu noktası hücrenin kenarına ne kadar yakınsa daire o kadar
dışarı taşar.

Tek hücre sorgusu hiçbir hücre boyunda kaçırma kısıtını karşılamıyor; komşu hücrelerin taranması
bir iyileştirme değil, bir gerekliliktir. **Hangi kısıt değişirse alternatif kazanır:** kaçırma
kısıtı gevşetilip "sonuçların yüzde 90'ı yeter" denseydi 8 km hücrede tek hücre sorgusu yüzde 93,7
ile geçerdi ve dokuz arama bire inerdi.

## Arıza Davranışı ve Feda Edilen

Bir parça düğümü düştüğünde sorgunun dokuz hücresinden bir kısmı yanıtsız kalır. Burada iki seçenek
vardır ve tasarım ikincisini seçer: eksik yanıtı hata saymak ya da elde olan hücrelerle yanıt verip
sonucun kısmi olduğunu belirtmek. İkincisi **zarif bozulmadır** (Dayanıklılık ve Güvenilirlik
kursunun Arıza Yalıtımı konusu) ve bedeli ölçülmüştür: bir hücre eksik kalırsa kaçırma oranı sıfır
kısıtı çiğnenir, ama tek hücre sorgusunun yüzde 59,1'lik kaçırmasının yanında dokuzda bir eksik
tarama çok daha küçük bir bozulmadır. Önbellek düştüğünde yük doğrudan dizine biner ve tepe
1666,67 sorgu tamamen depoya ulaşır; bu, aday bütçesinin neden yanıt boyutuna değil tepe hıza göre
seçildiğinin gerekçesidir.

**Feda edilen:** sorgu başına 864 aday taranıyor, gerçek yanıt ise 443,95 kayıt — isabet yüzde 51,4
ve tasarım her sorguda yaklaşık yarısı boşa giden bir tarama kabul ediyor. Bu israfın karşılığı,
kaçırma oranının sıfır kalması ve hücre boyunun tek bir sayıya sabitlenebilmesidir.

## Özet

- Bu vakada hacim küçük (12,80 GB) ama sorgu pahalı: düz tarama tepede saniyede 66,7 milyar kayıt
  okumak demek, aday bütçesi 1000 olduğunda aynı yük kırk binde birine iniyor.
- Uzamsal anahtar doğruluğu bozmuyor: her hücre boyunda eşleşme sayısı düz taramayla aynı (443,95),
  çünkü hücre bir ön elemedir ve kesin uzaklık her adayda ayrıca hesaplanır.
- Aday sayısı hücre boyuyla hızla büyüyor: 0,5 km'de 700, 1 km'de 864, 8 km'de 13.611; isabet yüzde
  63,4'ten yüzde 3,3'e düşüyor ve 1000 aday kısıtı 2 km ve üstünü eliyor.
- Küçük hücre adayı azaltır ama hücre sayısını artırır: 0,5 km 25 hücre ve 700 aday, 1 km 9 hücre ve
  864 aday; eşdeğer iş 1200'e karşı 1044 ve iki boy bir hücre araması 10,25 aday taramasına denk
  geldiğinde eşitlenir.
- Komşu hücre sorgulamak bir iyileştirme değil zorunluluktur: yalnız kendi hücresi taransa 1 km'de
  kaçırma yüzde 59,1, 8 km'de yüzde 6,3 olur ve hiçbir boyda sıfıra inmez.

## Sonraki Adım

Bu konudaki dört vaka aynı ayrımın iki yakasında durdu. Nesne deposu ile video servisi işi
**yazıldığı anda** yaptı: parça yerleştirildi, kalite basamakları üretildi ve okuma yalnız hazır
olanı aldı. Ölçüt sistemi ile konum servisi işin bir kısmını **sorulduğu anda** yaptı:
toplulaştırma önceden hesaplansa da yüzdelik sorusu okuma anında yanıtlandı, uzamsal anahtar
önceden yazılsa da yakınlık sorgusu her seferinde yeniden tarandı. Hiçbir vakada ikisinin **aynı
sistemde birlikte** gerekmesi ele alınmadı: aynı veri hem geriye dönük olarak eksiksiz işlenmek hem
de geldiği anda saniyeler içinde yanıtlanmak zorunda kalsaydı, iki işleme biçiminin ürettiği
sonuçların birbirini tutması ayrı bir tasarım problemi olurdu. Sonraki ders o problemi ele alır.
