Ders 04 / 19
Dağıtık İzleme
İz bağlamının elle yazılıp süreç sınırından geçirilmesi ve örnekleme oranının ölçülmesi: bağlamın taşınmadığı çağrı yerinde kopan iz sayısı, örnekleme oranının yakalanan sorunlu istek yüzdesine etkisi ve iz kaydının aralık başına hacmi.
İçindekiler
Bağıntı kimliği bir isteğin hangi servislerden geçtiğini söylüyor, ölçütler sabit maliyetle özet veriyor. İkisi de bir şeyi söylemiyor: sürelerin nasıl iç içe geçtiğini. Katalog çağrısı ödünç isteğinin içinde nerede başladı, üyelik dalıyla aynı anda mı koştu, bekleme hangi çağrıda oluştu. Bu ders o yapıyı kuran sinyali yazar.
İz, bir dış isteğin doğurduğu bütün işlerin ortak kaydıdır; aralık o kaydın tek bir parçasıdır — bir işin başlangıcıyla bitişi arasında geçen süre ve o işin üst aralığa bağı. Buradaki aralık, veri erişimi derslerinde geçen sorgu aralığından bütünüyle ayrı bir şeydir: orada aralık bir anahtar kümesinin alt ve üst sınırıdır, burada bir işin zaman içindeki başlangıç ve bitiş noktasıdır. Yayılım, bağlamın süreç sınırından geçirilmesidir; olmadığı yerde iz kopar.
İz Bağlamı
Bağlam üç değerden oluşur: izin kimliği, üst aralığın kimliği ve örneklenip örneklenmediği. Üçü tek bir başlıkta taşınır, çünkü sınırın karşısına geçebilen tek şey isteğin kendisidir.
// iz.mjs — iz baglami ve aralik; baglam bir baslikta tasinir, aralik dosyaya yazilir import { appendFileSync } from "node:fs"; export const BASLIK = "x-iz"; export const baglamCoz = (deger) => { // "<iz>|<ust aralik>|<orneklendi>" if (!deger) return null; const [iz, ust, o] = deger.split("|"); return { iz, ust, ornek: o === "1" }; }; export const baglamKur = (b) => `${b.iz}|${b.ust}|${b.ornek ? 1 : 0}`; export const karma = (s) => { // kimligi esit dagitan karma; ornekleme karari buna dayanir let h = 2166136261; for (const c of s) { h ^= c.charCodeAt(0); h = Math.imul(h, 16777619) >>> 0; } return h; }; export const orneklemeKarari = (iz, oran) => karma(iz) % 1000 < oran * 1000; export class Iz { constructor(dosya, servis) { this.dosya = dosya; this.servis = servis; this.no = 0; } basla(ad, baglam) { return { iz: baglam.iz, aralik: `${this.servis}${(this.no += 1)}`, ust: baglam.ust, ad, t0: performance.now(), ornek: baglam.ornek }; } bitir(a) { if (!a.ornek) return; // orneklenmemis aralik hic yazilmaz appendFileSync(this.dosya, JSON.stringify({ iz: a.iz, aralik: a.aralik, ust: a.ust, servis: this.servis, ad: a.ad, sure: Math.round(performance.now() - a.t0) }) + "\n"); } }
TL4 — örnekleme kararı yalnız kökte verilir ve bağlamla taşınır. Gerekçe: karar her serviste ayrı verilseydi aynı izin bazı aralıkları kaydedilir, bazıları kaydedilmezdi ve yarım izler tam izlerden ayırt edilemezdi. Kararın kökte verilmesi, kaydedilen her izin bütün olmasını sağlar; bedeli de sonraki bölümde ölçülen şeydir.
Bağlamı Sınırdan Geçirmek
Servisin yaptığı üç şey var: gelen başlıktan bağlamı çözmek, gelen bağlam yoksa kök olup iz kimliği ile örnekleme kararını üretmek, ardıla giderken bağlamı kendi aralık kimliğiyle güncelleyip başlığa koymak. Önceki derslerdeki bildirim çağrı yeri burada da bağlamı taşımıyor.
// servis4.mjs — <ad> <port> [ardil...]; iz baglami surec sinirini gecer; uzun omurlu surec import { createServer } from "node:http"; import { writeFileSync } from "node:fs"; import { Iz, BASLIK, baglamCoz, baglamKur, orneklemeKarari } from "./iz.mjs"; const [ad, port, ...ardil] = process.argv.slice(2); const BILDIRIM = "9405"; const dosya = `iz-${ad}.txt`; writeFileSync(dosya, ""); const iz = new Iz(dosya, ad); let oran = 1; const bekle = (ms) => new Promise((c) => setTimeout(c, ms)); const uc = (p, u) => `http://127.0.0.1:${p}/is?uye=${u.get("uye")}&nusha=${u.get("nusha")}`; createServer(async (istek, yanit) => { const u = new URL(istek.url, "http://y").searchParams; if (istek.url.startsWith("/oran")) { // orani ayarla ve iz dosyasini bosalt oran = Number(u.get("deger")); writeFileSync(dosya, ""); yanit.end("tamam"); return; } const uye = u.get("uye"), nusha = Number(u.get("nusha")); let baglam = baglamCoz(istek.headers[BASLIK]); if (!baglam) { // kok: iz kimligi ve ornekleme karari burada verilir const izNo = `${ad}-${nusha}`; baglam = { iz: izNo, ust: "", ornek: orneklemeKarari(izNo, oran) }; } const a = iz.basla(`${ad}/is`, baglam); await bekle(ad === "katalog" && nusha % 5 === 0 ? 40 : 3); let durum = ad === "ucretlendirme" && uye === "4023" ? 402 : 200; for (const p of ardil) { const bas = p === BILDIRIM ? {} // bildirim cagri yeri baglami tasimiyor : { [BASLIK]: baglamKur({ ...baglam, ust: a.aralik }) }; const y = await fetch(uc(p, u), { headers: bas }); if (y.status !== 200) durum = y.status; } iz.bitir(a); yanit.statusCode = durum; yanit.end("bitti"); }).listen(Number(port));
Bildirim servisi bağlamsız bir istek alınca kendini kök sanıyor ve kendi izini açıyor. Sonuç, kayıp bir aralık değil, fazladan bir iz: ödünç isteğinin izi dört aralıkta bitiyor, bildirim işi ise hiçbir yere bağlanmayan ayrı bir iz olarak duruyor.
Ölçüm
Sorgu aracı iz dosyalarını okuyup aralıkları iz kimliğine göre topluyor. Ödünç kimliğiyle başlayan izler gerçek izlerdir; bildirim kimliğiyle başlayanlar kopuk izlerdir. Sorunlu istek tanımı koddadır: beşin katı nüshalar yavaş raftan geliyor, dört üyeden biri ödenmemiş ücret nedeniyle hata alıyor.
// sor4.mjs — <oran>; iz butunlugu ve ornekleme oraninin sorunlu isteklere etkisi import { readFileSync, statSync } from "node:fs"; const SERVIS = ["odunc", "uyelik", "katalog", "ucretlendirme", "bildirim"]; const N = 100; const sorunlu = (n) => n % 5 === 0 || (n - 1) % 4 === 2; // yavas raf ya da odenmemis ucret const oran = process.argv[2]; const aralik = SERVIS.flatMap((s) => readFileSync(`iz-${s}.txt`, "utf8").split("\n").filter(Boolean).map((l) => JSON.parse(l))); const izler = new Map(); for (const a of aralik) { if (!izler.has(a.iz)) izler.set(a.iz, []); izler.get(a.iz).push(a); } const kok = [...izler.keys()].filter((k) => k.startsWith("odunc")); const kopuk = [...izler.keys()].filter((k) => k.startsWith("bildirim")); const tam = kok.filter((k) => izler.get(k).length === 4).length; const yakalanan = kok.filter((k) => sorunlu(Number(k.split("-")[1]))).length; const hepsi = Array.from({ length: N }, (_, i) => i + 1).filter(sorunlu).length; const bayt = SERVIS.reduce((t, s) => t + statSync(`iz-${s}.txt`).size, 0); console.log(`${oran.padStart(5)}${String(aralik.length).padStart(9)}${String(kok.length).padStart(5)}` + `${String(tam).padStart(8)}${String(kopuk.length).padStart(10)}${`${yakalanan}/${hepsi}`.padStart(12)}` + `${`%${Math.round((100 * yakalanan) / hepsi)}`.padStart(7)}` + `${String(aralik.length ? Math.round(bayt / aralik.length) : 0).padStart(12)}`);
cat > kos.mjs <<'EOF' const UYE = ["4021", "4022", "4023", "4024"]; // 4023 odenmemis ucreti olan uye const PORT = [9401, 9402, 9403, 9404, 9405]; for (const oran of ["1.00", "0.50", "0.10", "0.02"]) { for (const p of PORT) await fetch(`http://127.0.0.1:${p}/oran?deger=${oran}`); for (let i = 1; i <= 100; i += 1) await fetch(`http://127.0.0.1:9401/is?uye=${UYE[(i - 1) % 4]}&nusha=${i}`); await new Promise((c) => setTimeout(c, 100)); const { execSync } = await import("node:child_process"); process.stdout.write(execSync(`node sor4.mjs ${oran}`).toString()); } EOF node servis4.mjs ucretlendirme 9404 & node servis4.mjs katalog 9403 & node servis4.mjs bildirim 9405 & node servis4.mjs uyelik 9402 9404 & node servis4.mjs odunc 9401 9402 9403 9405 & sleep 1 printf '%5s%9s%5s%8s%10s%12s%7s%12s\n' oran aralik iz "tam iz" "kopuk iz" yakalanan yuzde "bayt/aralik" node kos.mjs pkill -f "node servis4.mjs"
oran aralik iz tam iz kopuk iz yakalanan yuzde bayt/aralik 1.00 500 100 100 100 40/40 %100 102 0.50 246 49 49 50 22/40 %55 103 0.10 46 8 8 14 4/40 %10 103 0.02 2 0 0 2 0/40 %0 101
Kopan İz ve Örneklemenin Bedeli
İlk satır bağlamın gerçekten sınırı geçtiğini gösteriyor: yüz dış istek, yüz iz ve her biri tam, yani dört aralıklı. Ödünç aralığı üst, üyelik ve katalog aralıkları onun altında, ücretlendirme aralığı üyeliğin altında. Önceki derslerin yanıtlayamadığı “bekleme nerede oldu” sorusu artık kaydın kendi yapısında duruyor.
Aynı satırın yanında yüz kopuk iz var. Bildirim çağrı yeri bağlamı taşımadığı için o servisteki her iş kendi kökünü açıyor: yüz istek için yüz ayrı, tek aralıklı iz. Ölçünün önemi şudur — kopan iz eksik veri gibi görünmez, fazla veri gibi görünür. Kayıtlarda iki yüz iz duruyor ve hiçbir şey bunların yüzünün aslında diğer yüzün parçası olduğunu söylemiyor. Bir isteğin yolunu izlemeye çalışan kişi, bildirimin hiç çağrılmadığı sonucuna varır.
Alt üç satır örnekleme oranının bedelini veriyor. Oran yarıya inince yazılan aralık beş yüzden iki yüz kırk altıya, onda bire inince kırk altıya, elliye bire inince ikiye düşüyor. Hacim oranla neredeyse doğrusal azalıyor ve aralık başına boy 101–103 bayt aralığında sabit kalıyor: örnekleme kaydın boyunu değil, sayısını değiştiriyor.
Karşılığında kaybedilen şey sağ sütunlarda. Kırk sorunlu isteğin tamamı ancak tam örneklemede yakalandı. Yüzde ellilik oranda kırk sorunlu isteğin yirmi ikisi, onda birlik oranda dördü, ellide birlik oranda hiçbiri yakalandı. Sayılar oranın kendisine yakın çıkıyor ve dersin bulgusu tam olarak budur: kökte verilen örnekleme kararı sorunlu isteği kayırmaz. Karar isteğin başında, henüz yavaşlayacağı ya da hata alacağı bilinmeden veriliyor; dolayısıyla sorunlu istekler de diğerleri gibi oranla seyreltiliyor. Yüzde iki örnekleme, “izlerin yüzde ikisini tutuyorum” demek değil, “sorunların yüzde doksan sekizini hiç görmeyeceğim” demektir.
Örnekleme kararının kökte verilmesi bir şeyi kazandırdı: kaydedilen izlerin tamamı tam. Yüzde elli oranda yazılan kırk dokuz izin kırk dokuzu da dört aralıklı. Karar her serviste ayrı verilseydi aynı hacimde çok daha fazla, ama yarım iz elde edilirdi — ve yarım iz, kopuk iz gibi, veriyi yanıltıcı hâle getirirdi.
Özet
- İz bağlamı üç değerden oluşuyor (iz kimliği, üst aralık, örnekleme kararı) ve tek başlıkla süreç sınırını geçiyor; tam örneklemede 100 istek 100 tam ize ve 400 bağlı aralığa dönüştü.
- Bağlamı taşımayan tek çağrı yeri 100 kopuk iz üretti; kopan iz eksik veri olarak değil, birbirine bağlanmamış fazla iz olarak görünüyor.
- Örnekleme oranı hacmi neredeyse doğrusal düşürüyor: 500, 246, 46 ve 2 aralık; aralık başına boy 101–103 bayt sabit kaldı.
- Kökte verilen örnekleme kararı sorunlu isteği kayırmıyor: 40 sorunlu isteğin yakalananı sırasıyla 40, 22, 4 ve 0 oldu.
- Kararın kökte verilmesi kaydedilen her izin tam olmasını sağladı: her oranda yazılan izlerin tamamı dört aralıklıydı.
Sonraki Adım
Üç sinyal de artık kodda üretiliyor, ama üçü de kendi yolunu izliyor. Günlükçü kendi dosyasına yazıyor, ölçütler bir uçta duruyor, aralıklar başka bir dosyaya düşüyor; her birinin nereye yazacağı servis kodunun içine gömülü. Bu, uygulama kodunu tek bir toplama biçimine bağlar: yazma yeri değişirse beş serviste de arama yapmak gerekir. Sonraki ders bu bağı keser ve bir arayüz tasarlar. Ölçülecek olan tek şeydir: toplayıcı gerçekleştirimi değiştiğinde kaç dosyaya dokunulur.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.