Ders 17 / 19
Komut ve Sorgu Sorumluluğu Ayrımı
Yazma ve okuma modellerinin ayrılması: operasyon panosunun toplama kökleri üzerinden ve ayrı bir okuma modeli üzerinden üretilmesi, üç veri boyunda gezilen nesne sayısının karşılaştırılması, okuma modelinin kaç komut geriden geldiğinin ve kaç yanıtın bayat çıktığının ölçülmesi, bedel olarak eklenen dosya, satır ve eşleme yazımı sayısı.
İçindekiler
Buraya kadarki bütün ölçüler yazma yönündeydi: varış kaydının hangi kararlardan geçtiği ve hangi bağlantı noktalarından dışarı çıktığı. Operasyon masasının asıl işi ise okumaktır: hangi aktarma noktasında kaç paket bekliyor, bunların kaçı rotasının gerisinde kalmış. Bu soruların yanıtı toplama kökleri üzerinden aranırsa her yanıt için bütün teslimatlar ve rotalarındaki bütün ayaklar gezilir; gezinti teslimat sayısıyla büyür.
Komut ve sorgu sorumluluğu ayrımı (command query responsibility segregation) bu iki yönü ayrı modellere verir. Yazma tarafı değişmezleri koruyacak biçimde kurulur — toplama kökleri, tutarlılık sınırları, kararlar. Okuma tarafı ise sorunun biçimine göre kurulur: sorgunun istediği satırlar hazır tutulur.
Bu ayrım, Programlama Temelleri kursunda tanıtılan komut–sorgu ayrımından farklı bir şeydir: orada ölçüt bir yöntemin ya durumu değiştirmesi ya değer döndürmesiydi, yani tek bir yöntemin imzasıydı. Burada ayrılan şey yöntem değil, model: aynı veriyi iki ayrı yapıda tutmak.
Yazma Modeli
Yazma tarafı önceki derslerin toplama kökünü sürdürür. Her teslimatın rotası, geçtiği noktalar ve o ana kadar geçmiş olması beklenen nokta sayısı vardır.
// yazma-modeli.mjs — teslimat toplamalari ve tek komut: varis kaydi const NOKTALAR = ["34", "06", "35", "01", "16"]; const TESLIMATLAR = new Map(); export function tohumla(sayi) { TESLIMATLAR.clear(); let x = 7; for (let i = 1; i <= sayi; i += 1) { x = (x * 48271 + 11) % 2147483647; const uzunluk = 2 + (x % 4); const bas = x % NOKTALAR.length; const rota = Array.from({ length: uzunluk }, (_, j) => NOKTALAR[(bas + j) % NOKTALAR.length]); const ilerleme = x % uzunluk; TESLIMATLAR.set(`T${i}`, { id: `T${i}`, rota, gecilen: rota.slice(0, ilerleme), beklenen: (x % 2) + ilerleme, }); } return TESLIMATLAR.size; } export const yazmaModeli = { tumu: () => [...TESLIMATLAR.values()].map((t) => structuredClone(t)), bul: (id) => structuredClone(TESLIMATLAR.get(id) ?? null), }; export function varisKomutu(id) { const t = TESLIMATLAR.get(id); if (t === undefined || t.gecilen.length >= t.rota.length) return null; const once = structuredClone(t); t.gecilen.push(t.rota[t.gecilen.length]); return { once, sonra: structuredClone(t) }; }
Tohumlama sabit bir üreteçle yapılıyor; aynı teslimat sayısı her koşumda aynı rotaları verir. Komut değişimin öncesini ve sonrasını birlikte döndürür, çünkü okuma tarafının hangi satırın değiştiğini bilmesi gerekir.
Panonun Yazma Modelinden Üretilmesi
Pano sorgusu her teslimatı okur, rotasını ve geçilen noktalarını gezer, sıradaki noktayı bulur ve o noktanın satırını arttırır.
// pano-yazma.mjs — pano sorgusu yazma modeli uzerinden: her toplama ve her rota ayagi gezilir export function panoYazmadan(yazmaModeli) { let gezilen = 0; const satir = new Map(); for (const t of yazmaModeli.tumu()) { gezilen += 1; for (const _ of t.rota) gezilen += 1; for (const _ of t.gecilen) gezilen += 1; const siradaki = t.rota[t.gecilen.length]; if (siradaki === undefined) continue; const s = satir.get(siradaki) ?? { nokta: siradaki, bekleyen: 0, geride: 0 }; s.bekleyen += 1; if (t.gecilen.length < t.beklenen) s.geride += 1; satir.set(siradaki, s); } return { satirlar: [...satir.values()].sort((a, b) => a.nokta.localeCompare(b.nokta)), gezilen }; }
gezilen sayacı iddiayı ölçüye çeviriyor: her toplama kökü, her rota ayağı ve her geçilmiş
nokta bir gezinti sayılıyor.
Okuma Modeli
Okuma modeli beş satırdan oluşur — her aktarma noktası için bir satır. Satırlar bir kez kurulur, sonra yalnız komutların ürettiği farklarla güncellenir. Uygulanmamış farklar bir kuyrukta bekler; kuyruğun boyu modelin gecikmesidir.
// okuma-modeli.mjs — pano satirlarini hazir tutan okuma modeli ve uygulanmamis guncelleme kuyrugu const SATIR = new Map(); const BEKLEYEN = []; export const okumaModeli = { kur(satirlar) { SATIR.clear(); BEKLEYEN.length = 0; for (const s of satirlar) SATIR.set(s.nokta, { ...s }); }, yayinla(delta) { BEKLEYEN.push(delta); }, gecikme: () => BEKLEYEN.length, ilerlet(adet = BEKLEYEN.length) { const uygulanan = BEKLEYEN.splice(0, adet); for (const delta of uygulanan) { for (const [nokta, bekleyen, geride] of delta) { const s = SATIR.get(nokta) ?? { nokta, bekleyen: 0, geride: 0 }; s.bekleyen += bekleyen; s.geride += geride; SATIR.set(nokta, s); } } return uygulanan.length; }, pano() { let gezilen = 0; const satirlar = []; for (const s of SATIR.values()) { gezilen += 1; if (s.bekleyen > 0) satirlar.push({ ...s }); } return { satirlar: satirlar.sort((a, b) => a.nokta.localeCompare(b.nokta)), gezilen }; }, };
İki modeli birbirine bağlayan tek parça eşlemedir: bir komutun hangi satırları hangi yönde değiştirdiğini hesaplar.
// izdusum.mjs — bir komutun degistirdigi pano satirlarini hesaplayan esleme export function izdusum({ once, sonra }) { const eski = once.rota[once.gecilen.length], yeni = sonra.rota[sonra.gecilen.length]; const delta = []; if (eski !== undefined) delta.push([eski, -1, once.gecilen.length < once.beklenen ? -1 : 0]); if (yeni !== undefined) delta.push([yeni, 1, sonra.gecilen.length < sonra.beklenen ? 1 : 0]); return delta; }
Ölçüm
Ölçüm üç şeyi ayrı ayrı sayar. Birincisi gezinti: üç ayrı teslimat sayısında iki yolun gezdiği nesne. İkincisi tazelik: yirmi dört komut boyunca okuma modelinin kaç komut geriden geldiği ve kaç sorgunun yazma modelinden farklı yanıt verdiği. Üçüncüsü bedel: eklenen dosya, satır ve eşleme yazımı sayısı.
// pano-say.mjs — sorgu icin gezilen nesne, tazelik gecikmesi ve izdusum esleme sayisi import { readFileSync } from "node:fs"; import { tohumla, yazmaModeli, varisKomutu } from "./yazma-modeli.mjs"; import { panoYazmadan } from "./pano-yazma.mjs"; import { okumaModeli } from "./okuma-modeli.mjs"; import { izdusum } from "./izdusum.mjs"; for (const n of [50, 200, 800]) { tohumla(n); const y = panoYazmadan(yazmaModeli); okumaModeli.kur(y.satirlar); const o = okumaModeli.pano(); const esit = JSON.stringify(y.satirlar) === JSON.stringify(o.satirlar); console.log(`teslimat = ${String(n).padStart(3)} yazma modelinden gezilen = ${String(y.gezilen).padStart(4)}` + `, okuma modelinden gezilen = ${o.gezilen}, satirlar esit mi = ${esit}`); } tohumla(200); okumaModeli.kur(panoYazmadan(yazmaModeli).satirlar); console.log(`baslangic panosu = ${JSON.stringify(okumaModeli.pano().satirlar)}`); const KOMUTLAR = Array.from({ length: 24 }, (_, i) => `T${i * 7 + 3}`); const ILERLETME_ARALIGI = 6; let bayat = 0, enBuyukGecikme = 0, eslemeSayisi = 0, komutSayisi = 0; for (const [sira, id] of KOMUTLAR.entries()) { const sonuc = varisKomutu(id); if (sonuc === null) continue; komutSayisi += 1; const delta = izdusum(sonuc); eslemeSayisi += delta.length; okumaModeli.yayinla(delta); if ((sira + 1) % ILERLETME_ARALIGI === 0) okumaModeli.ilerlet(); enBuyukGecikme = Math.max(enBuyukGecikme, okumaModeli.gecikme()); const yazma = JSON.stringify(panoYazmadan(yazmaModeli).satirlar); const okuma = JSON.stringify(okumaModeli.pano().satirlar); if (yazma !== okuma) bayat += 1; } console.log(`komut = ${komutSayisi}, izdusum esleme yazimi = ${eslemeSayisi}`); console.log(`bayat yanit = ${bayat} / ${komutSayisi}, en buyuk gecikme = ${enBuyukGecikme} komut`); okumaModeli.ilerlet(); const sonYazma = JSON.stringify(panoYazmadan(yazmaModeli).satirlar); const sonOkuma = JSON.stringify(okumaModeli.pano().satirlar); console.log(`kuyruk bosaltildiktan sonra esit mi = ${sonYazma === sonOkuma}, gecikme = ${okumaModeli.gecikme()}`); console.log(`son pano = ${sonOkuma}`); const satirSay = (d) => readFileSync(d, "utf8").split("\n").filter((s) => s.trim() !== "").length; for (const [ad, dosyalar] of [["tek model", ["yazma-modeli.mjs", "pano-yazma.mjs"]], ["okuma modeli icin eklenen", ["okuma-modeli.mjs", "izdusum.mjs"]]]) { console.log(`${ad.padEnd(26)} ${dosyalar.length} dosya, ${dosyalar.reduce((s, d) => s + satirSay(d), 0)} satir`); }
node pano-say.mjs
teslimat = 50 yazma modelinden gezilen = 309, okuma modelinden gezilen = 5, satirlar esit mi = true
teslimat = 200 yazma modelinden gezilen = 1220, okuma modelinden gezilen = 5, satirlar esit mi = true
teslimat = 800 yazma modelinden gezilen = 4621, okuma modelinden gezilen = 5, satirlar esit mi = true
baslangic panosu = [{"nokta":"01","bekleyen":45,"geride":29},{"nokta":"06","bekleyen":46,"geride":25},{"nokta":"16","bekleyen":32,"geride":19},{"nokta":"34","bekleyen":40,"geride":15},{"nokta":"35","bekleyen":37,"geride":24}]
komut = 24, izdusum esleme yazimi = 44
bayat yanit = 20 / 24, en buyuk gecikme = 5 komut
kuyruk bosaltildiktan sonra esit mi = true, gecikme = 0
son pano = [{"nokta":"01","bekleyen":43,"geride":27},{"nokta":"06","bekleyen":39,"geride":18},{"nokta":"16","bekleyen":29,"geride":17},{"nokta":"34","bekleyen":38,"geride":12},{"nokta":"35","bekleyen":47,"geride":23}]
tek model 2 dosya, 46 satir
okuma modeli icin eklenen 2 dosya, 36 satir
Sayıların Okunması
Gezinti sayıları üç boyda da aynı şeyi söylüyor. Yazma modeli üzerinden pano üretmek 50 teslimatta 309, 200 teslimatta 1220, 800 teslimatta 4621 nesne geziyor; oran teslimat başına yaklaşık 6’dır, çünkü her teslimatın kendisi, rota ayakları ve geçilmiş noktaları ayrı ayrı okunuyor. Okuma modeli üzerinden aynı pano her boyda 5 nesne geziyor. Bu sayı teslimat sayısına değil, aktarma noktası sayısına bağlıdır; veri büyürken sabit kalır. Üç boyda da iki yolun ürettiği satırlar eşit çıktı, yani okuma modeli doğru bir izdüşümdür.
Tazelik sayıları bedeli gösteriyor. Yirmi dört komut boyunca kuyruk her altı komutta bir boşaltıldı; okuma modeli en fazla 5 komut geriden geldi ve 24 sorgunun 20’si yazma modelinden farklı yanıt verdi. Bu 20 yanıt yanlış değil, eski: bir aktarma noktasında bekleyen paket sayısı en çok beş komut önceki hâliyle görünüyor. Kuyruk boşaltıldıktan sonra iki model yine eşitlendi ve gecikme 0’a indi.
Bedel iki dosya ve 36 satır. Buna komut başına eşleme yazımı ekleniyor: 24 komut 44 satır
güncellemesi üretti, yani komut başına ortalama 1,8. Ayrım gezinti sayısını 6n‘den sabit
5’e indirirken yazma yolunu bu kadar ağırlaştırıyor.
Gecikmenin Alan Kararı Olması
Beş komutluk gecikme teknik bir ayar gibi görünür, ama kararı veren alan uzmanıdır. Aktarma merkezinin panosunda beş komutluk gerilik sorun değildir: sayaç zaten dakikalar içinde değişiyor. Aynı gecikme, bir paketin ikinci kez aynı noktaya kaydedilmesini engelleyen kararda kabul edilemez. Bu yüzden ayrım “her sorgu okuma modelinden” biçiminde uygulanmaz; hangi sorgunun ne kadar gerilik taşıyabileceği bağlam bağlamdır.
Okuma modeli tazeliğinin genel ölçütleri, geçersizleştirme ve tazelik penceresi Önbellekleme, Kuyruklar ve Eşzamansız İşleme kursunda ele alındı; burada ölçülen tek şey ayrımın kendi getirdiği geriliktir.
Ne Zaman Uygulanmaz
Kazanç iki koşula bağlıdır. Birincisi sorgunun gezinti sayısının veri boyuyla büyümesi: tek bir teslimatı kimliğiyle okuyan bir sorgu yazma modelinde de 1 nesne geziyor, okuma modeli ona hiçbir şey kazandırmaz. İkincisi sorgunun biçiminin kararlı olması; sorgu her ay değişiyorsa eşleme de her ay değişir ve iki dosya sürekli açılır.
Üçüncü sınır tutarlılıktır. Bir alan kuralı sorgunun yanıtına dayanıyorsa — “bir noktada en çok yüz paket bekleyebilir” gibi — o kural okuma modelinden okunamaz, çünkü okuma modeli geriden gelir. Böyle bir kural yazma tarafındaki tutarlılık sınırının içinde kalmak zorundadır.
Özet
- Komut ve sorgu sorumluluğu ayrımı yazma ve okuma için iki ayrı model tutar; komut–sorgu ayrımı ise tek bir yöntemin ya durumu değiştirmesini ya değer döndürmesini söyler.
- Pano yazma modeli üzerinden 50, 200 ve 800 teslimatta 309, 1220 ve 4621 nesne gezdi; okuma modeli üzerinden üç boyda da 5 nesne gezdi ve satırlar eşit çıktı.
- Okuma modeli en fazla 5 komut geriden geldi; 24 sorgunun 20’si eski yanıt verdi, kuyruk boşaltılınca iki model eşitlendi.
- Bedel 2 dosya, 36 satır ve komut başına ortalama 1,8 eşleme yazımıdır; ayrım okuma yolunu ucuzlatırken yazma yolunu ağırlaştırır.
- Ne kadar geriliğin kabul edilebilir olduğu alan kararıdır; bir alan kuralı sorgunun yanıtına dayanıyorsa o kural yazma tarafındaki tutarlılık sınırında kalmalıdır.
Sonraki Adım
Okuma modeli komutların ürettiği farklardan kuruldu; farkların kendisi ise uygulandıktan sonra atıldı. Bu, yazma tarafına bir soru sorulamaz hâle getiriyor: bir teslimatın adresi kaç kez düzeltildi, hangi noktada kaç kez yeniden deneme yapıldı, dağıtıma çıkış ile teslim arasında hangi sırayla ne oldu? Kayıt yalnız son durumu tuttuğu için bu soruların yanıtı modelde yok. Sonraki ders durumu değil olay dizisini saklayan bir kalıcılık biçimini ele alır: durumun olaylardan yeniden kurulup kurulabildiğini sınar, yeniden kurma için kaç olay okunduğunu sayar ve anlık görüntü alındığında bu sayının nereye indiğini ölçer.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.