Ders 15 / 18
Komut ve Sorgu Ayrımı
Okuma ve yazma yollarının ayrı modellerle ölçeklenmesi: takip sorgusunun tek modelde sekiz, izdüşümde bir kayıt dokunması, komutun üç dokunuştan beşe çıkması, iki yerleşimin düğüm sayısının okuma ve yazma çarpanları altında karşılaştırılması ve okuma deposunun K01'in yazma/okuma oranından kaçamaması.
İçindekiler
Önceki konu okuma yolunu bir katman öne çekti: önbellek isteklerin çoğunu depoya hiç
ulaştırmadı — depoya ulasan istek/s satırı 140,23’e indi — ve ulaşanların gördüğü bayatlığı
bir pencereyle sınırladı. Ama önbelleğin sakladığı
şey, deponun verdiği yanıtın kopyasıdır; ıskalanan her istek yine aynı modele gider. O model
gönderi kaydını, durum olaylarını ve aralarındaki bağı yazma tarafının değişmezlerini koruyacak
biçimde düzenler. Okuma yolu, yazma için tasarlanmış bir modeli ödünç alır ve her ıskalamada
onun bedelini öder.
Bu konu ödünç almayı bırakır. Sorusu şudur: aynı veriyi iki ayrı yapıda tutmak, okuma ve yazma yollarının ayrı ölçeklenmesini sağlar mı, sağlıyorsa hangi orandan sonra.
İki Ayrı Ayrım
Adları benzeyen iki kavram var ve birbirinin yerine geçmezler. Programlama Temelleri kursunda tanıtılan komut–sorgu ayrımı bir yöntemin imzasıyla ilgilidir: bir yöntem ya durumu değiştirir ya değer döndürür, ikisini birden yapmaz. Komut ve sorgu sorumluluğu ayrımı (command query responsibility segregation) ise yöntemi değil modeli ayırır; aynı veri iki ayrı yapıda tutulur.
İkinci kalıp Alan Odaklı Tasarım kursunda kuruldu ve orada ölçüldü: pano sorgusunun yazma modeli üzerinden gezdiği nesne sayısı veri boyuyla büyüyordu, okuma modeli üzerinden sabit kalıyordu. O ders bir sınır da koymuştu — tek bir kaydı kimliğiyle okuyan bir sorgu yazma modelinde de tek nesne gezer, ayrım ona hiçbir şey kazandırmaz. Takip sorgusu tam olarak böyle görünür: tek bir takip numarası verilir. Ölçüm bu benzerliğin yanıltıcı olup olmadığını sınayarak başlar.
Erişim Örüntüsü
Karar bir örüntüyle birlikte verilir. Bu konuda örüntü üçtür: takip numarasına göre tekil okuma, taşıyıcıdan gelen durum olayının yazılması ve satıcıya göre dönemsel toplu tarama. İlk ikisi bu dersin konusudur.
Takip yanıtının gövdesi K01’de tanımlıydı (V5, 480 bayt): durum, bölge, güncellenme zamanı ve son üç rota adımı. Son üç adım gönderi kaydında değil, olay kayıtlarındadır. Yazma modeli üzerinden yanıt derlemek bu yüzden gönderi kaydını ve o gönderinin bütün durum olaylarını okumayı gerektirir; olay sayısı K01’in V4 varsayımıyla 7’dir.
Düzenek süreç içi bir depo modelidir. Gerçek bir veritabanı, disk ya da ağ yoktur; kayıtlar bellekte durur ve her erişim dokunduğu kayıt sayısını arttırır. Ölçü süre değil kayıt dokunuşudur, çünkü süre makineye bağlıdır, dokunuş sayısı değildir.
// ayrim/depo.mjs — surec ici depo modeli. Gercek bir veritabani yoktur: kayitlar bellekte // durur ve her erisim dokundugu kayit sayisini sayar. Olculen sey sure degil, dokunustur. const NOKTA = ["34", "06", "35", "01", "16"]; export function depoKur() { return { gonderi: new Map(), olay: new Map(), izdusum: new Map(), sayac: { okuma: 0, yazma: 0 } }; } function yanitKur(g, dizi) { return { id: g.id, durum: g.durum, bolge: g.bolge, an: g.an, sonAdim: dizi.slice(-3).map((o) => o.nokta) }; } export function tohumla(d, sayi, olayBasina) { let x = 7; const sonraki = () => (x = (x * 48271 + 11) % 2147483647); for (let i = 1; i <= sayi; i += 1) { const id = `G${i}`; const g = { id, bolge: NOKTA[sonraki() % NOKTA.length], durum: "yolda", an: olayBasina }; const dizi = Array.from({ length: olayBasina }, (_, k) => ({ id, sira: k + 1, nokta: NOKTA[sonraki() % NOKTA.length] })); d.gonderi.set(id, g); d.olay.set(id, dizi); d.izdusum.set(id, yanitKur(g, dizi)); } return d.gonderi.size; } // Tek model: takip yaniti gonderi kaydindan ve o gonderinin olay kayitlarindan derlenir. export function takipTekModel(d, id) { const g = d.gonderi.get(id); d.sayac.okuma += 1; const dizi = d.olay.get(id); d.sayac.okuma += dizi.length; return yanitKur(g, dizi); } // Ayrik model: takip yaniti hazir bekleyen tek izdusum satirindan okunur. export function takipIzdusumden(d, id) { d.sayac.okuma += 1; return d.izdusum.get(id); } // Komut: olay eklenir, gonderi kaydi oku-degistir-yaz ile guncellenir; ayrik yerlesimde // ayrica izdusum satiri oku-degistir-yaz ile tazelenir. export function komut(d, id, nokta, izdusumeYaz) { const dizi = d.olay.get(id); dizi.push({ id, sira: dizi.length + 1, nokta }); d.sayac.yazma += 1; const g = d.gonderi.get(id); d.sayac.okuma += 1; g.an = dizi.length; d.sayac.yazma += 1; if (izdusumeYaz !== true) return; const eski = d.izdusum.get(id); d.sayac.okuma += 1; d.izdusum.set(id, { ...eski, an: g.an, sonAdim: dizi.slice(-3).map((o) => o.nokta) }); d.sayac.yazma += 1; }
İzdüşüm (projection), Alan Odaklı Tasarım kursunda tanımlandığı biçimiyle, yazma tarafının ürettiği değişimlerden türetilen okuma modelidir. Burada izdüşüm satırı takip yanıtının kendisidir.
Ölçüm ve Hesap
Ölçüm iki adımdır. Önce model koşturulur ve sorgu ile komut başına dokunuş sayısı sayılır; bu sayılar belirlenimlidir. Sonra sayılar K01’in hızlarına uygulanır ve düğüm sayısı çıkarılır. Düğüm sayısı için bir kapasite gerekiyor.
OY1 — bir depo düğümünün saniyede sürdürdüğü kayıt dokunuşu: 100. Bu konunun kendi varsayımıdır ve K01’in tablosuna eklenmez. Gerekçesi, bölme yapılabilmesi için bir birim gerekmesidir; sayının mutlak değeri bir ölçüm değildir. Duyarlılığı bu yüzden özel bir biçim alır: 200 alınırsa bütün düğüm sayıları yarılanır ama iki yerleşimin oranı değişmez, çünkü kapasite iki tarafta da aynı bölendir.
// ayrim/olc.mjs — iki yerlesimin sorgu ve komut basina dokunusu modelden olculur, sonra // K01'in hizlarina uygulanir. Butun sayilar belirlenimlidir; sinif olarak hesaptir. import { depoKur, tohumla, takipTekModel, takipIzdusumden, komut } from "./depo.mjs"; const V4 = 7; // K01 varsayimi: gonderi basina durum olayi const OKUMA = 41.67; // K01 hesabi: onbellek arkasi okuma/s const YAZMA = 97.22; // K01 hesabi: tepe yazma istek/s const OY1 = 100; // bu konunun varsayimi: bir dugumun surdurdugu kayit dokunusu/s const N = 200; const d = depoKur(); tohumla(d, N, V4); const kimlik = [...d.gonderi.keys()]; d.sayac.okuma = 0; d.sayac.yazma = 0; const tekYanit = kimlik.map((id) => takipTekModel(d, id)); const tekSorgu = d.sayac.okuma / N; d.sayac.okuma = 0; d.sayac.yazma = 0; const izYanit = kimlik.map((id) => takipIzdusumden(d, id)); const izSorgu = d.sayac.okuma / N; const esit = JSON.stringify(tekYanit) === JSON.stringify(izYanit); d.sayac.okuma = 0; d.sayac.yazma = 0; for (const id of kimlik) komut(d, id, "16", false); const tekKomut = { okuma: d.sayac.okuma / N, yazma: d.sayac.yazma / N }; d.sayac.okuma = 0; d.sayac.yazma = 0; for (const id of kimlik) komut(d, id, "01", true); const ayrikKomut = { okuma: d.sayac.okuma / N, yazma: d.sayac.yazma / N }; const b = (x, n = 2) => x.toFixed(n); console.log(`model: ${N} gonderi, gonderi basina ${V4} olay (K01 V4)`); console.log(`takip sorgusu / tek model = ${tekSorgu} kayit dokunusu`); console.log(`takip sorgusu / izdusumden = ${izSorgu} kayit dokunusu, iki yanit esit mi = ${esit}`); console.log(`komut / tek model = ${tekKomut.okuma + tekKomut.yazma} dokunus ` + `(${tekKomut.okuma} okuma, ${tekKomut.yazma} yazma)`); console.log(`komut / ayrik model = ${ayrikKomut.okuma + ayrikKomut.yazma} dokunus ` + `(${ayrikKomut.okuma} okuma, ${ayrikKomut.yazma} yazma)`); const tek = (r, w) => OKUMA * r * tekSorgu + YAZMA * w * (tekKomut.okuma + tekKomut.yazma); const ayrikYazma = (w) => YAZMA * w * (tekKomut.okuma + tekKomut.yazma); const ayrikOkuma = (r, w) => OKUMA * r * izSorgu + YAZMA * w * (ayrikKomut.okuma + ayrikKomut.yazma - tekKomut.okuma - tekKomut.yazma); const dugum = (yuk) => Math.ceil(yuk / OY1); console.log(`\nK01: onbellek arkasi okuma ${OKUMA}/s, tepe yazma ${YAZMA}/s, oran ${b(YAZMA / OKUMA)}`); console.log(`dugum kapasitesi OY1 = ${OY1} dokunus/s (bu konunun varsayimi)\n`); console.log(`${"okuma x".padStart(8)}${"yazma x".padStart(8)}${"tek dokunus/s".padStart(15)}` + `${"tek dugum".padStart(11)}${"ayrik y+o dokunus/s".padStart(21)}${"ayrik dugum".padStart(13)}`); for (const [r, w] of [[1, 1], [2, 1], [4, 1], [1, 2], [1, 4], [4, 4]]) { const t = tek(r, w), ay = ayrikYazma(w), ao = ayrikOkuma(r, w); console.log(`${String(r).padStart(8)}${String(w).padStart(8)}${b(t).padStart(15)}` + `${String(dugum(t)).padStart(11)}${`${b(ay)} + ${b(ao)}`.padStart(21)}` + `${`${dugum(ay)} + ${dugum(ao)} = ${dugum(ay) + dugum(ao)}`.padStart(13)}`); } const kazanc = (r, w) => dugum(tek(r, w)) - dugum(ayrikYazma(w)) - dugum(ayrikOkuma(r, w)); let ilkKayip = null; for (let w = 1; w <= 20; w += 1) if (kazanc(1, w) < 0) { ilkKayip = w; break; } console.log(`\nayrimin dugum kazanci: taban ${kazanc(1, 1)}, okuma x4'te ${kazanc(4, 1)}, ` + `yazma x4'te ${kazanc(1, 4)}`); console.log(`ayrimin dugum sayisini artirdigi en kucuk yazma carpani = ${ilkKayip}`); const izGuncelleme = ayrikKomut.okuma + ayrikKomut.yazma - tekKomut.okuma - tekKomut.yazma; console.log(`\nokuma deposunun gordugu istek: sorgu ${b(OKUMA)}/s, izdusum guncellemesi ` + `${b(YAZMA)}/s -> yazma/okuma orani ${b(YAZMA / OKUMA)} (K01'in orani)`); console.log(`okuma deposunun gordugu dokunus: ${b(OKUMA * izSorgu)} + ${b(YAZMA * izGuncelleme)} ` + `-> oran ${b((YAZMA * izGuncelleme) / (OKUMA * izSorgu))}`);
model: 200 gonderi, gonderi basina 7 olay (K01 V4)
takip sorgusu / tek model = 8 kayit dokunusu
takip sorgusu / izdusumden = 1 kayit dokunusu, iki yanit esit mi = true
komut / tek model = 3 dokunus (1 okuma, 2 yazma)
komut / ayrik model = 5 dokunus (2 okuma, 3 yazma)
K01: onbellek arkasi okuma 41.67/s, tepe yazma 97.22/s, oran 2.33
dugum kapasitesi OY1 = 100 dokunus/s (bu konunun varsayimi)
okuma x yazma x tek dokunus/s tek dugum ayrik y+o dokunus/s ayrik dugum
1 1 625.02 7 291.66 + 236.11 3 + 3 = 6
2 1 958.38 10 291.66 + 277.78 3 + 3 = 6
4 1 1625.10 17 291.66 + 361.12 3 + 4 = 7
1 2 916.68 10 583.32 + 430.55 6 + 5 = 11
1 4 1500.00 15 1166.64 + 819.43 12 + 9 = 21
4 4 2500.08 26 1166.64 + 944.44 12 + 10 = 22
ayrimin dugum kazanci: taban 1, okuma x4'te 10, yazma x4'te -6
ayrimin dugum sayisini artirdigi en kucuk yazma carpani = 2
okuma deposunun gordugu istek: sorgu 41.67/s, izdusum guncellemesi 97.22/s -> yazma/okuma orani 2.33 (K01'in orani)
okuma deposunun gordugu dokunus: 41.67 + 194.44 -> oran 4.67
Sayıların Okunması
İlk üç satır benzerliğin yanıltıcı olduğunu gösteriyor. Takip sorgusu tek bir kimlikle geliyor ama yazma modeli üzerinden 8 kayda dokunuyor: bir gönderi kaydı ve yedi olay kaydı. İzdüşüm üzerinden 1 kayda dokunuyor ve iki yolun ürettiği yanıtlar eşit çıkıyor. Ayrımın kazanç getirmediği durum, sorgunun tek kimlikle gelmesi değil, yanıtın tek kayıttan çıkmasıdır; takip yanıtı çıkmıyor.
Komut satırları bedeli veriyor. Tek modelde bir durum olayı 3 dokunuş ediyor: olay eklenir, gönderi kaydı okunur ve yazılır. Ayrık yerleşimde izdüşüm satırının da okunup yazılması gerekiyor ve komut 5 dokunuşa çıkıyor. Yazma yolu 1,67 katına çıkıyor.
Düğüm tablosu iki yolu K01’in hızlarında karşılaştırıyor. Tabanda tek model 625,02 dokunuş/s ile 7 düğüm istiyor, ayrık yerleşim 3 yazma ve 3 okuma düğümüyle 6 düğüm istiyor. Kazanç bir düğüm; bu, ayrımı tek başına haklı çıkaracak bir fark değildir.
Asıl fark büyüme yönünde. Okuma dört katına çıktığında tek model 17 düğüme tırmanıyor, ayrık yerleşim 7’de kalıyor: kazanç 10 düğüm. Çünkü ayrık yerleşimde okuma büyümesi yalnız izdüşüm sorgularını büyütüyor ve her sorgu 1 dokunuş. Yazma dört katına çıktığında tablo tersine dönüyor: tek model 15, ayrık yerleşim 21 düğüm istiyor. Kayıp 6 düğüm ve eşik erken geliyor — yazma çarpanı 2 olduğunda ayrım zaten pahalıya düşüyor. Ayrım bir bahistir: okumanın yazmadan hızlı büyüyeceği bahsi.
Oran Kaybolmuyor, Taşınıyor
Son iki satır kalıbın en kolay yanlış anlaşılan yerini ölçüyor. K01 depoda yazma/okuma oranını 2,33 bulmuştu; okuma ağırlıklı görünen sistem, deponun gördüğü yerde yazma ağırlıklıydı. Ayrım bu orandan kaçmıyor. Okuma deposu saniyede 41,67 sorgu ve 97,22 izdüşüm güncellemesi görüyor; istek cinsinden oranı yine 2,33. Dokunuş cinsinden bakıldığında durum daha da belirgin: 41,67 dokunuşa karşı 194,44 dokunuş, oran 4,67. “Okuma modeli” adı taşıyan depo, özgün depodan iki kat daha yazma ağırlıklıdır.
Ayrımın verdiği şey oranı düzeltmek değil, dokunuş başına bedeli ayırmaktır. Okuma yolunun
dokunuşu 8’den 1’e iniyor; yazma yolunun dokunuşu 3’ten 5’e çıkıyor. K01’in depoya ulasan istek/s satırındaki 138,89 istek ortadan kalkmıyor, iki depoya bölünüyor: 97,22 yazma
deposuna, 41,67 okuma deposuna. Toplam dokunuş 625,02’den 527,77’ye iniyor, yani 1,18 kat.
Bu sayıların hepsi hesap sınıfındadır; tek varsayım OY1’dir ve o da yalnız bölmeyi yapıyor.
Ayrımın Getirdiği Yeni Soru
Ayrık yerleşimde izdüşüm satırı komutla birlikte güncellendi ve bu yüzden hep taze kaldı. Bu, modeli gereğinden iyi gösteriyor: iki depoya aynı anda yazmak, iki deponun tek bir tutarlılık sınırında olmasını gerektirir. Ayrı ölçeklenmeleri isteniyorsa aynı sınırda olamazlar; izdüşüm komuttan sonra güncellenir ve arada bir pencere açılır. O pencerenin ne kadar açık kalacağı ve okuma deposunun neden yeniden kurulabilir olması gerektiği, izdüşümün neyden türetildiğine bağlıdır.
Özet
- Komut–sorgu ayrımı bir yöntemin imzasıyla, komut ve sorgu sorumluluğu ayrımı modelle ilgilidir; ikincisi Alan Odaklı Tasarım kursunda kuruldu, burada ölçekleme aracı olarak ele alınıyor.
- Takip sorgusu tek kimlikle gelse de yanıtı tek kayıttan çıkmıyor: yazma modeli üzerinden 8, izdüşüm üzerinden 1 kayıt dokunuşu, iki yanıt eşit.
- Bedel yazma yolundadır: komut 3 dokunuştan 5 dokunuşa, yani 1,67 katına çıkıyor.
- OY1 = 100 dokunuş/s kapasiteyle tabanda düğüm 7’den 6’ya iniyor; okuma dört katına çıkarsa 17’ye karşı 7, yazma dört katına çıkarsa 15’e karşı 21. Ayrım yazma çarpanı 2’de zarara geçiyor.
- K01’in 2,33’ü kaybolmuyor, taşınıyor: okuma deposunun istek oranı da 2,33, dokunuş oranı 4,67. Ayrım oranı değil, dokunuş başına bedeli değiştirir.
Sonraki Adım
İzdüşümün komutla aynı anda güncellenmesi bu dersin ölçümünü kolaylaştırdı ama ayrımın amacını yok ediyor: aynı tutarlılık sınırında duran iki depo ayrı ölçeklenemez. İzdüşüm komuttan sonra, kendi hızıyla güncellenecekse neyden türetileceği belirlenmelidir. Gönderi kaydı yalnız son durumu tuttuğu için ondan türetilemez — bir izdüşüm satırı, kaydın hangi değişimle o hâle geldiğini bilmek zorundadır. Sonraki ders değişimin kendisini kayıt olarak saklamayı ele alır: eklemeli bir kütüğün yazma yolundan hangi okumayı kaldırdığını, K01’in bir yazma isteğinin depoda kaç işleme dönüştüğünü hiç sormadığını ve olayın kaynak kayda dönüşmesinin günlük veri artışı ile saklanan veri satırlarına ne eklediğini ölçer.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.