Ders 05 / 10
Tümleştirme Kalıpları
Aynı bağın dört kalıpla kurulup karşılaştırılması: sorgu başına kaynak sisteme giden tur ve istek, tazelik penceresi, tazeleme başına taşınan kayıt, tüketici koduna sızan fiziksel şema adı sayısı ve kaynak tek sütununu yeniden adlandırdığında kırılan bağ ile koordine edilecek sahip sayısı.
İçindekiler
Önceki konu kurumun yeteneklerini çıkardı ve her yeteneğin hangi sistemde durduğunu gösterdi. O eşleme bir soruyu hiç sormadı: sistemler birbirine nasıl bağlanıyor. Bir yetenek bir sistemde duruyorsa, aynı veriye ihtiyaç duyan ikinci sistem onu hangi yoldan, hangi sıklıkta ve kimin sorumluluğunda alıyor. Bu ders tek bir bağı alır, onu dört ayrı kalıpla kurar ve dördünü aynı ölçülerle karşılaştırır.
Kurum ölçeğinde ölçü birimi bağdır. Tek bir sistemin içinde bir çağrının nereye gittiği o sistemin sahibinin kararıdır; kurum ölçeğinde bağın iki ucunda iki ayrı sahip, iki ayrı bütçe ve iki ayrı takvim durur. Bu yüzden burada sayılan şey gecikme kadar, bağın kime ne borç yüklediğidir.
TM1 — kurum kurgudur ve node içinde bir veri yapısı olarak yazılır. Sistemler, sahipleri ve
aralarındaki bağlar bir dizi olarak durur; ölçülen bağ o dizinin bir satırıdır.
TM2 — tazeleme dönemi zamanlayıcı yerine tik ile modellenir. Gerekçe: dönemin saat karşılığı
(dakika, saat, gece) kalıbın kendisiyle değil işletim kararıyla belirlenir; ölçülen nicelik “kaç
tazeleme dönemi sonra taze” olduğudur ve bu sayı dönemin uzunluğundan bağımsızdır.
TM3 — mesajlaşma taşıyıcısı bir ekleme kütüğü olarak modellenir. Kuyruk mekaniği, teslim garantisi ve tüketici rekabeti Önbellekleme, Kuyruklar ve Eşzamansız İşleme kursunda ölçüldü; burada ölçülen şey taşıyıcının kendisi değil, bağın kaynağa ve sahibine ödettiğidir.
Kurum ve Ölçülen Bağ
Bölgesel kütüphane ağı kurgu bir kurumdur. Katalog sistemi dışarıdan alınmıştır; ödünç, ücretlendirme ve bildirim servisleri içeride yazılmıştır; üyelik ayrı bir birimin, kimlik doğrulama belediyenin elindedir. Bu derste ölçülen tek varlık nüsha durumudur: bir kitap nüshasının rafta olup olmadığı. Bu veriyi katalog yazar, beş sistem okur.
// kurum.mjs — bolgesel kutuphane agi: kurgu bir kurum modeli; dogrudan kosunca kendini yazar export const SISTEM = [ { ad: "katalog", sahip: "katalog birimi", disaridan: 1 }, { ad: "odunc", sahip: "ic gelistirme", disaridan: 0 }, { ad: "ucretlendirme", sahip: "ic gelistirme", disaridan: 0 }, { ad: "bildirim", sahip: "ic gelistirme", disaridan: 0 }, { ad: "raporlama", sahip: "yonetim birimi", disaridan: 0 }, { ad: "sube-arayuzu", sahip: "sube birimi", disaridan: 0 }, { ad: "uyelik", sahip: "uyelik birimi", disaridan: 0 }, { ad: "kimlik", sahip: "belediye bilgi islem", disaridan: 1 }, ]; // nusha durumunu katalogdan okuyan baglar; her bag bir kalipla kurulmustur (model) export const BAG = [ { hedef: "odunc", kalip: "ortak-db" }, { hedef: "ucretlendirme", kalip: "ortak-db" }, { hedef: "raporlama", kalip: "dosya" }, { hedef: "sube-arayuzu", kalip: "uzak-cagri" }, { hedef: "bildirim", kalip: "mesaj" }, ]; export const sahip = (a) => SISTEM.find((s) => s.ad === a).sahip; if (process.argv[1].endsWith("kurum.mjs")) { console.log(`${SISTEM.length} sistem, ${new Set(SISTEM.map((s) => s.sahip)).size} ayri sahip, ` + `${SISTEM.filter((s) => s.disaridan).length} sistem disaridan alinmis`); console.log(`katalogdan cikan bag: ${BAG.length}, dokunulan sahip: ` + `${new Set(BAG.map((b) => sahip(b.hedef))).size}`); for (const k of ["ortak-db", "dosya", "uzak-cagri", "mesaj"]) { console.log(` ${k.padEnd(11)}${BAG.filter((b) => b.kalip === k).length} bag`); } }
8 sistem, 6 ayri sahip, 2 sistem disaridan alinmis katalogdan cikan bag: 5, dokunulan sahip: 3 ortak-db 2 bag dosya 1 bag uzak-cagri 1 bag mesaj 1 bag
Kaynak sistem nüsha tablosunun sahibidir ve tabloya dört yoldan erişim verir: tek kaydı yayın
biçiminde döndüren bir uç, aynı biçimde bir anlık görüntü, fiziksel tablonun kopyasını çıkaran bir
döküm ve her değişiklikte yayın biçiminde bir satır eklenen kütük. Fiziksel sütun adları
SEMA içinde durur ve yalnız kaynağın kendi kodunda okunur.
// katalog.mjs — nusha tablosunun sahibi olan kaynak sistem; <port>; uzun omurlu surec import { createServer } from "node:http"; import { appendFileSync, existsSync, readFileSync, writeFileSync } from "node:fs"; const [port] = process.argv.slice(2); if (!port) { console.log("kullanim: node katalog.mjs <port>"); process.exit(1); } const IS = 20; // katalogun bir istegi karsilama suresi (ms) const SEMA = { durum: "durum", sube: "subeKodu" }; // fiziksel sutun adlari; katalogun malidir const yaz = (s) => writeFileSync("katalog.db", s.map((r) => JSON.stringify(r)).join("\n") + "\n"); const oku = () => readFileSync("katalog.db", "utf8").split("\n").filter(Boolean).map(JSON.parse); const yayin = (r) => ({ nushaId: r.nushaId, sube: r[SEMA.sube], uygun: r[SEMA.durum] === "rafta" }); // disari verilen bicim; katalogun sozudur if (!existsSync("katalog.db")) { yaz([...Array(200).keys()].map((i) => ({ nushaId: 401 + i, subeKodu: 1 + (i % 4), durum: i % 5 === 0 ? "oduncte" : "rafta", basimYili: 1990 + (i % 30) }))); } let yuk = 0; // kaynak sistemin gordugu istek sayisi createServer(async (istek, yanit) => { const u = new URL(istek.url, "http://y"); if (u.pathname === "/yuk") { yanit.end(String(yuk)); return; } yuk += 1; await new Promise((c) => setTimeout(c, IS)); const id = Number(u.searchParams.get("id")); if (u.pathname === "/nusha") { // uzak cagri: tek kayit, yayin bicimi yanit.end(JSON.stringify(yayin(oku().find((r) => r.nushaId === id)))); } else if (u.pathname === "/anlik") { // mesaj tuketicisinin ilk dolumu, yayin bicimi yanit.end(JSON.stringify(oku().map(yayin))); } else if (u.pathname === "/doku") { // dosya aktarimi: fiziksel tablonun kopyasi const s = oku(); writeFileSync("katalog.doku", s.map((r) => JSON.stringify(r)).join("\n") + "\n"); yanit.end(String(s.length)); } else if (u.pathname === "/degistir") { // katalogun kendi yazma yolu const s = oku(), r = s.find((x) => x.nushaId === id); r[SEMA.durum] = u.searchParams.get("durum"); yaz(s); appendFileSync("katalog.olay", JSON.stringify(yayin(r)) + "\n"); yanit.end("tamam"); } else if (u.pathname === "/goc") { // katalog kendi sutununu yeniden adlandirir yaz(oku().map((r) => { const { [SEMA.durum]: d, ...k } = r; return { ...k, nushaDurumu: d }; })); SEMA.durum = "nushaDurumu"; yanit.end("tamam"); } else yanit.end("yok"); }).listen(Number(port));
Aynı Bağın Dört Kurulumu
Tüketici tek bir kaynak dosyadan koşar ve kalıbı komut satırından alır. Dört bölümün her biri iki
işlev tanımlar: sorguyu yanıtlayan SOR ve tazeleme dönemini temsil eden TIK. Bölümler
işaretli olduğu için hangi kalıbın hangi adı okuduğu koddan sayılabilir.
// odunc.mjs — tuketici; <kalip: ortak-db|dosya|uzak-cagri|mesaj> <port> <katalog-port> import { createServer } from "node:http"; import { existsSync, readFileSync } from "node:fs"; const [kalip, port, kp] = process.argv.slice(2); if (!port) { console.log("kullanim: node odunc.mjs <kalip> <port> <katalog-port>"); process.exit(1); } const K = `http://127.0.0.1:${kp}`; const satir = (d) => readFileSync(d, "utf8").split("\n").filter(Boolean).map(JSON.parse); const yerel = new Map(); // tuketicinin kendi kopyasi let okunan = 0; const SOR = {}, TIK = {}; // TIK: tazeleme donemini modelleyen tetik // --- ortak-db --- tuketici kaynagin fiziksel tablosunu dogrudan okur SOR["ortak-db"] = (id) => satir("katalog.db").find((r) => r.nushaId === id).durum === "rafta"; TIK["ortak-db"] = () => 0; // --- dosya --- kaynak fiziksel tabloyu doker, tuketici doku dosyasini bastan okur SOR["dosya"] = (id) => yerel.get(id); TIK["dosya"] = async () => { await fetch(`${K}/doku`); const s = satir("katalog.doku"); for (const r of s) yerel.set(r.nushaId, r.durum === "rafta"); return s.length; }; // --- uzak-cagri --- her sorgu kaynagin yayin bicimini cagirir SOR["uzak-cagri"] = async (id) => (await (await fetch(`${K}/nusha?id=${id}`)).json()).uygun; TIK["uzak-cagri"] = () => 0; // --- mesaj --- ilk dolum yayin bicimindedir, sonra yalniz degisiklik satirlari okunur SOR["mesaj"] = (id) => yerel.get(id); TIK["mesaj"] = () => { if (!existsSync("katalog.olay")) return 0; const s = satir("katalog.olay"); let n = 0; while (okunan < s.length) { yerel.set(s[okunan].nushaId, s[okunan].uygun); okunan += 1; n += 1; } return n; }; if (kalip === "mesaj") for (const r of await (await fetch(`${K}/anlik`)).json()) yerel.set(r.nushaId, r.uygun); // --- ortak --- kalipten bagimsiz servis yuzeyi createServer(async (istek, yanit) => { const u = new URL(istek.url, "http://y"); if (u.pathname === "/tik") { yanit.end(String(await TIK[kalip]())); return; } yanit.end(String(await SOR[kalip](Number(u.searchParams.get("id"))))); }).listen(Number(port));
Ölçüm
Araç üç aşamada koşar. Önce her tüketiciye yüz sorgu gönderip sorgu başına turu ve bu sırada kaynak sisteme ulaşan istek sayısını sayar. Sonra kaynakta tek bir nüshanın durumunu değiştirir ve tüketiciye iki kez sorar: tikten önce ve tikten sonra. Son aşamada kaynak kendi sütununu yeniden adlandırır ve dört tüketicinin de aynı soruya doğru yanıt verip vermediğine bakılır. Tüketici kodundaki ad sayımı çalışan süreçlerden değil, kaynak metinden okunur.
// olc.mjs — ayni bagi dort kalipla olcer; <katalog-port> <dort tuketici portu> import { readFileSync } from "node:fs"; import { BAG, sahip } from "./kurum.mjs"; const arg = process.argv.slice(2); if (arg.length < 5) { console.log("kullanim: node olc.mjs <katalog-port> <p1> <p2> <p3> <p4>"); process.exit(1); } const [kp, ...port] = arg; const KALIP = ["ortak-db", "dosya", "uzak-cagri", "mesaj"]; const UC = Object.fromEntries(KALIP.map((k, i) => [k, `http://127.0.0.1:${port[i]}`])); const K = `http://127.0.0.1:${kp}`, IS = 20, SORGU = 100; const metin = (u) => fetch(u).then((y) => y.text()); const sor = (k, id) => metin(`${UC[k]}/sor?id=${id}`); const tik = (k) => metin(`${UC[k]}/tik`).then(Number); const o = {}; for (const k of KALIP) { o[k] = {}; await tik(k); } // ilk dolum, olcumden once for (const k of KALIP) { // gecikme ve kaynagin gordugu yuk const y0 = Number(await metin(`${K}/yuk`)); const t = performance.now(); for (let i = 0; i < SORGU; i += 1) await sor(k, 401 + (i % 200)); o[k].tur = Math.round((performance.now() - t) / SORGU / IS); o[k].yuk = Number(await metin(`${K}/yuk`)) - y0; } await metin(`${K}/degistir?id=402&durum=oduncte`); // kaynakta tek kayit degisir for (const k of KALIP) { const once = await sor(k, 402); o[k].kayit = await tik(k); o[k].pencere = once === "false" ? 0 : (await sor(k, 402)) === "false" ? 1 : -1; } const bolum = {}; // kalip basina tuketici kodu for (const p of readFileSync("odunc.mjs", "utf8").split(/^\/\/ --- /m).slice(1)) { bolum[p.slice(0, p.indexOf(" "))] = p; } const FIZIKSEL = ["katalog.db", "katalog.doku", "durum", "rafta"]; // kaynagin ic semasi const YAYIN = ["/nusha", "/anlik", "katalog.olay", "uygun"]; // kaynagin verdigi soz const say = (m, l) => l.filter((a) => m.includes(a)).length; await metin(`${K}/goc`); // kaynak kendi sutununu adlandirir for (const k of KALIP) { await tik(k); o[k].dogru = (await sor(k, 403)) === "true"; } console.log(`${"kalip".padEnd(12)}${"tur".padStart(5)}${"kaynak istegi".padStart(15)}` + `${"tazelik tik".padStart(13)}${"tik kaydi".padStart(11)}${"fiziksel ad".padStart(13)}` + `${"yayin adi".padStart(11)}${"goc sonrasi".padStart(13)}`); for (const k of KALIP) { console.log(`${k.padEnd(12)}${String(o[k].tur).padStart(5)}${String(o[k].yuk).padStart(15)}` + `${String(o[k].pencere).padStart(13)}${String(o[k].kayit).padStart(11)}` + `${String(say(bolum[k], FIZIKSEL)).padStart(13)}${String(say(bolum[k], YAYIN)).padStart(11)}` + `${(o[k].dogru ? "dogru" : "yanlis").padStart(13)}`); } const kirik = BAG.filter((b) => !o[b.kalip].dogru); console.log(`tek sutun adi degisti: ${kirik.length}/${BAG.length} bag kirildi ` + `(${kirik.map((b) => b.hedef).join(", ")}); koordine edilecek sahip ` + `${new Set(kirik.map((b) => sahip(b.hedef))).size}/${new Set(BAG.map((b) => sahip(b.hedef))).size}`);
rm -f katalog.db katalog.doku katalog.olay node katalog.mjs 8981 & K=$! sleep 0.6 node odunc.mjs ortak-db 8982 8981 & A=$! node odunc.mjs dosya 8983 8981 & B=$! node odunc.mjs uzak-cagri 8984 8981 & C=$! node odunc.mjs mesaj 8985 8981 & D=$! sleep 0.8 node olc.mjs 8981 8982 8983 8984 8985 kill $K $A $B $C $D
kalip tur kaynak istegi tazelik tik tik kaydi fiziksel ad yayin adi goc sonrasi ortak-db 0 0 0 0 3 0 yanlis dosya 0 0 1 200 3 0 yanlis uzak-cagri 1 100 0 0 0 2 dogru mesaj 0 0 1 1 0 3 dogru tek sutun adi degisti: 3/5 bag kirildi (odunc, ucretlendirme, raporlama); koordine edilecek sahip 2/3
Gecikme, Yük ve Tazelik
İlk üç kolon kalıpların yüzeyde neye benzediğini veriyor. Uzak çağrı her sorguda kaynağa gidiyor: sorgu başına bir tur ve yüz sorguda yüz istek. Diğer üç kalıpta sorgu turu sıfır, çünkü yanıt tüketicinin kendi tarafından geliyor; kaynak sistem yüz sorgunun hiçbirini görmüyor.
Tazelik kolonu bu kazancın karşılığını gösteriyor. Ortak veritabanı ile uzak çağrıda kaynakta yapılan değişiklik tüketiciye aynı anda yansıyor; iki kalıpta da tazelik penceresi sıfır tiktir. Dosya aktarımı ve mesajlaşmada değişiklik ancak bir tazeleme döneminden sonra görünüyor. Aradaki fark dördüncü kolonda: dosya aktarımı tek bir nüshanın durumu değiştiği hâlde her tazelemede iki yüz kaydın tamamını taşıyor, mesajlaşma bir kayıt taşıyor. Aynı tazelik penceresi, iki yüz kat farklı taşıma hacmiyle satın alınıyor. Kaynak sisteme düşen iş de buradan çıkıyor: dosya aktarımı sorgu başına sıfır istek üretir ama her dönemde tabloyu bir baştan sona okur.
Buradan tek başına bir sıralama çıkmıyor. Uzak çağrı tazelik penceresini sıfırlar ve kaynağın yükünü sorgu sayısına bağlar; dosya aktarımı yükü dönem sayısına bağlar ve tazeliği feda eder; mesajlaşma dönem sayısına bağlı kalıp taşıma hacmini değişiklik sayısına indirir. Ortak veritabanı hepsinden ucuz görünüyor: sıfır tur, sıfır istek, sıfır pencere. Bedeli sonraki iki kolonda duruyor.
Sahiplik Sızıntısı
Beşinci ve altıncı kolon aynı bağı sahiplik açısından ölçüyor. Ortak veritabanı ve dosya aktarımı kalıplarında tüketici kodu kaynağın fiziksel şemasından üç ad taşıyor: tablonun adı, sütunun adı ve o sütunun anlamlı olduğu değer. Üçüncü ad en pahalısıdır, çünkü bir ad değil bir kuraldır. “Rafta” değerinin uygunluk demek olduğu bilgisi katalog biriminin işletme kuralıdır ve o kural tüketicinin kodunda ikinci bir kopya olarak durur. Katalog yarın üçüncü bir durum değeri eklerse, kaynak kod hiç değişmediği hâlde tüketicinin yanıtı yanlışlaşır.
Uzak çağrı ve mesajlaşma kalıplarında fiziksel ad sayısı sıfırdır. Tüketici kaynağın tablosunu değil, kaynağın dışarıya verdiği biçimi okur; uygunluk kararı kaynağın kodunda tek bir yerde hesaplanır. Altıncı kolondaki adlar bir kayıp değil, bir sözleşmedir: kaynak sistem o adları değiştirmemeyi taahhüt etmiştir ve karşılığında tablosunda dilediğini yapabilir.
Ortak veritabanının asıl sorunu bu satırlarda görünüyor. Tablo tek bir sistemin adını taşıyor ama iki sistemin kodu ona bağlı. Şemanın tek sahibi yok; katalog birimi tabloyu tek başına değiştiremeyeceğini ancak bağı kuran taraf değişikliği fark ettiğinde öğrenir.
Değişikliğin Yayılması
Son kolon bunu ölçüyor. Kaynak sistem tek bir sütunun adını değiştiriyor; kendi yayın biçimini korumak dışında hiçbir şey yapmıyor. Uzak çağrı ve mesajlaşma bağları bu değişiklikten hiç etkilenmiyor, çünkü ikisi de yayın biçiminden okuyor. Ortak veritabanı ve dosya aktarımı bağları yanlış yanıt vermeye başlıyor; üstelik gürültüsüzce, çünkü eksik alanın karşılığı hata değil, tanımsız bir değerdir.
Son satır bu sonucu kurumun tamamına taşıyor. Katalogdan çıkan beş bağın üçü kırılıyor ve kırılanların ardında iki ayrı sahip duruyor. Kaynak sistemin sahibi için bu, tek satırlık bir göçün önce iki birimle takvim uzlaştırmasını, sonra üç sistemin uyarlanmasını beklemesi demektir. Kalıp seçimi burada bir gecikme tercihi olmaktan çıkıp bir koordinasyon borcuna dönüşüyor: hangi kalıp seçilirse seçilsin veri karşıya geçer, ama kaç sahibin izni olmadan değişemeyeceği kalıba göre değişir.
Özet
- Aynı bağ dört kalıpla kuruldu: uzak çağrıda sorgu başına 1 tur ve yüz sorguda 100 kaynak isteği, diğer üç kalıpta 0 tur ve 0 istek ölçüldü.
- Tazelik penceresi ortak veritabanı ve uzak çağrıda 0 tik, dosya aktarımı ve mesajlaşmada 1 tik; aynı pencere dosya aktarımında 200, mesajlaşmada 1 kayıt taşıyarak alınıyor.
- Ortak veritabanı ve dosya aktarımı tüketici koduna kaynağın fiziksel şemasından 3 ad taşıyor; bunların biri sütun adı değil, uygunluk kuralının ikinci kopyasıdır.
- Uzak çağrı ve mesajlaşma fiziksel ad taşımıyor, yerine kaynağın taahhüt ettiği 2 ve 3 yayın adına bağlanıyor.
- Kaynak tek bir sütun adını değiştirdiğinde 5 bağın 3’ü sessizce yanlış yanıt vermeye başlıyor ve 3 sahipten 2’siyle koordinasyon gerekiyor.
Sonraki Adım
Ölçüm tek bir kaynak ve tek bir tüketiciyle yapıldı; kurum modelinde ise katalogdan çıkan beş bağ var ve katalog kurumun tek sistemi değil. Her sistem her sisteme kendi seçtiği kalıpla bağlandığında bağ sayısının sistem sayısıyla nasıl arttığı, yeni bir sistem eklemenin kaç tarafa iş çıkardığı ve bu bağların ortak bir noktadan geçirilmesinin neyi ucuzlatıp neyi tek bir yerde topladığı hiç sorulmadı. Sonraki ders aynı kurum modelinde ikili bağları ve merkezî noktayı yan yana kurar; bağ sayısını, yeni sistemin maliyetini, merkezin düştüğü anda kopan bağ sayısını ve merkezde biriken iş mantığının kime ait olduğunu sayar.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.