Ders 01 / 19
Gözlemlenebilirliğin Üç Sinyali
Günlük, ölçüt ve iz sinyallerinin bölünmüş bir ödünç sisteminde gerçekten üretilmesi: aynı üç sorunun her sinyalle kaç adımda yanıtlandığı, hangi sorunun hangi sinyalde yanıtsız kaldığı ve üç sinyalin istek sayısıyla nasıl büyüdüğü.
İçindekiler
Önceki kurs sistemi beş sürece böldü ve her sınırın yeni bir arıza kipi doğurduğunu ölçtü. Kapanışta duran soru şuydu: bir isteğin nerede yavaşladığını ya da nerede kaybolduğunu görmenin aracı hiç kurulmadı. Bu ders o aracın üç parçasını kurar ve her parçanın neyi yanıtlayıp neyi yanıtlayamadığını sayar.
Üç sinyal şunlardır. Günlük, tek tek olayların metin kaydıdır. Ölçüt, olayların önceden seçilmiş bir toplama indirgenmiş hâlidir. İz, bir işin adımlarını süreleriyle birlikte tutan kayıttır ve her adımı bir aralıktır. Üçü de ölçmek içindir, ama üçü farklı soruya yanıt verir; ders bunu iddia olarak değil, adım sayısı olarak gösterir.
İzleme kategorileri ve uyarı eşiği Başarım Karşıt Kalıpları ve İzleme kursunda kuruldu; burada kategori listesi tekrarlanmaz. Bu kursun konusu üretim tarafıdır: sinyal uygulama kodunda nasıl doğar, hangi alanı taşır, hangi alanı taşımaz.
Üç Sinyali Üreten Servis
Düzenek önceki kursun bıraktığı hâliyle sürüyor: katalog, üyelik, ödünç, bildirim ve ücretlendirme ayrı süreçler. Ödünç servisi üyelik, katalog ve bildirim servislerini çağırır; üyelik servisi ücretlendirmeyi çağırır. Aşağıdaki tek kaynak dosya hepsinin rolünü üstlenir ve her istekte üç sinyali de üretir.
TL1 — üç sinyal de isteği işleyen sürecin içinde, ek bir toplayıcı süreç olmadan üretilir. Gerekçe: ölçülecek olan sinyalin taşınması değil, doğduğu yerde hangi alanı taşıdığıdır. Toplayıcıya bağlanma sorunu beşinci derste ayrı ele alınır.
// servis.mjs — <ad> <port> [ardil...]; her istek uc sinyali de uretir; uzun omurlu surec import { createServer } from "node:http"; import { appendFileSync, writeFileSync } from "node:fs"; const [ad, port, ...ardil] = process.argv.slice(2); writeFileSync(`gunluk-${ad}.txt`, ""); writeFileSync(`iz-${ad}.txt`, ""); const olcut = { istek: 0, hata: 0, sure: 0 }; // olcut: yalniz toplam, kimlik yok let yerelNo = 0; // iz numarasi yalniz bu surecte anlamli const bekle = (ms) => new Promise((c) => setTimeout(c, ms)); createServer(async (istek, yanit) => { const u = new URL(istek.url, "http://y"); if (u.pathname === "/olcut") { yanit.end(JSON.stringify({ ad, ...olcut, sure: Math.round(olcut.sure) })); return; } const uye = u.searchParams.get("uye"), nusha = Number(u.searchParams.get("nusha")); const t0 = performance.now(), no = ++yerelNo; olcut.istek += 1; await bekle(ad === "katalog" && nusha % 5 === 0 ? 100 : 5); // her besinci nusha yavas raf let durum = ad === "ucretlendirme" && uye === "4023" ? 402 : 200; // odenmemis ucret for (const p of ardil) { const y = await fetch(`http://127.0.0.1:${p}/is?uye=${uye}&nusha=${nusha}`); if (y.status !== 200) durum = y.status; } const sure = performance.now() - t0; olcut.sure += sure; if (durum !== 200) olcut.hata += 1; appendFileSync(`gunluk-${ad}.txt`, `[${new Date().toISOString()}] ${ad}: uye ${uye} icin ${nusha} numarali nusha istegi ` + `${Math.round(sure)} ms surdu, sonuc ${durum}\n`); appendFileSync(`iz-${ad}.txt`, `${no} ${ad} ${Math.round(sure)}\n`); yanit.statusCode = durum; yanit.end(JSON.stringify({ ad, durum })); }).listen(Number(port));
Üç sinyalin bu ilk hâli kasten yalındır. Günlük satırı düz cümledir. Ölçüt üç toplamdır. Aralık yalnız sürecin kendi içinde artan bir numara taşır. Sonraki dört ders bu üç eksikliği tek tek kapatır.
Yavaşlık ve hata kaynağı sabittir: numarası beşin katı olan her nüshada katalog servisi yüz milisaniye harcar, üye 4023’ün her isteği ücretlendirme servisinden 402 ile döner ve bu kod üyelik üzerinden ödünç servisine kadar yükselir. İstemci ödünç ucuna sırayla istek gönderir, üyeleri dört üyelik bir döngüden seçer.
// kos.mjs — <istek sayisi>; odunc ucuna sirayla istek gonderir const n = Number(process.argv[2]); const UYE = ["4021", "4022", "4023", "4024"]; // 4023 odenmemis ucreti olan uye for (let i = 1; i <= n; i += 1) { await fetch(`http://127.0.0.1:9101/is?uye=${UYE[(i - 1) % 4]}&nusha=${i}`); } console.log(`${n} istek gonderildi`);
Aynı Soru, Üç Sinyal
Sorgu aracı üç soruyu üç sinyalle yanıtlamayı dener. Sorular gerçek sorulardır: bu istek nerede yavaşladı, kaç istek hata aldı, hangi kullanıcı etkilendi.
Adım, sorgunun bir kayıt deposu üzerinde yaptığı geçidir: bir dosyayı ya da bir ucu okumak bir adım, düz metni ayrıştırmak bir adım, süzmek bir adım, iki depoyu birleştirmeyi denemek bir adım. Sayaç kodun içindedir, tahmin değildir. Yanıtlanamayan soruda da adımlar sayılır — vazgeçmeden önce harcanan iş de bir maliyettir.
// sor.mjs — uc soruyu uc sinyalle yanitlamayi dener; her depo gecisi bir adimdir import { readFileSync, statSync } from "node:fs"; const SERVIS = ["odunc", "uyelik", "katalog", "ucretlendirme", "bildirim"]; const PORT = { odunc: 9101, uyelik: 9102, katalog: 9103, ucretlendirme: 9104, bildirim: 9105 }; const RE = /uye (\d+) icin (\d+) numarali nusha istegi ([\d.]+) ms surdu, sonuc (\d+)/; let adim = 0; const say = () => { adim += 1; }; const satirlar = (on, s) => { say(); return readFileSync(`${on}-${s}.txt`, "utf8").split("\n").filter(Boolean); }; const gunlukAyristir = () => { // duz metin: servis basina bir ayristirma gecisi const o = {}; for (const s of SERVIS) o[s] = satirlar("gunluk", s).map((l) => { const m = l.match(RE); return m && { uye: m[1], sure: +m[3], durum: +m[4] }; }); for (const s of SERVIS) say(); return o; }; const izOku = () => { const o = {}; for (const s of SERVIS) o[s] = satirlar("iz", s).map((l) => { const [no, , sure] = l.split(" "); return { no: +no, sure: +sure }; }); return o; }; const olcutOku = async () => { const o = {}; for (const s of SERVIS) { say(); o[s] = await (await fetch(`http://127.0.0.1:${PORT[s]}/olcut`)).json(); } return o; }; const yaz = (soru, sinyal, yanit) => console.log(`${soru.padEnd(12)}${sinyal.padEnd(9)}${String(adim).padStart(5)} ${yanit}`); console.log(`${"soru".padEnd(12)}${"sinyal".padEnd(9)}${"adim".padStart(5)} yanit`); const olcut = await olcutOku(); const bosAdim = adim; // S1 — bu istek nerede yavasladi (hedef: odunc ucundaki en yavas istek) const ESIK = 60; // 60 ms ustu = yavas kabul edilen istek adim = 0; const g = gunlukAyristir(); say(); const yavas = g.odunc.filter((r) => r.sure > ESIK).length; say(); yaz("S1 nerede", "gunluk", `yanitlanamadi (${ESIK} ms ustunde ${yavas} odunc kaydi; ardil kayitla ortak kimlik yok)`); adim = bosAdim; say(); yaz("S1 nerede", "olcut", `yanitlanamadi (istek basina ayrim yok; servis ortalamasi var)`); adim = 0; const iz = izOku(); say(); const yavasAralik = iz.odunc.filter((a) => a.sure > ESIK).length; say(); yaz("S1 nerede", "iz", `kismi (${ESIK} ms ustunde ${yavasAralik} aralik; hangi ardilda gectigi yok)`); // S2 — kac istek hata aldi adim = 0; const g2 = gunlukAyristir(); say(); yaz("S2 kac hata", "gunluk", `${g2.odunc.filter((r) => r.durum !== 200).length} istek`); adim = bosAdim; say(); yaz("S2 kac hata", "olcut", `${olcut.odunc.hata} istek`); adim = 0; izOku(); say(); yaz("S2 kac hata", "iz", `yanitlanamadi (iz kaydinda durum alani yok)`); // S3 — hangi kullanici etkilendi adim = 0; const g3 = gunlukAyristir(); say(); yaz("S3 kim", "gunluk", `uye ${[...new Set(g3.odunc.filter((r) => r.durum !== 200).map((r) => r.uye))].join(",")}`); adim = bosAdim; say(); yaz("S3 kim", "olcut", `yanitlanamadi (toplam sayac, kimlik tasimaz)`); adim = 0; izOku(); say(); yaz("S3 kim", "iz", `yanitlanamadi (aralikta kimlik alani yok)`); const bayt = (on) => SERVIS.reduce((t, s) => t + statSync(`${on}-${s}.txt`).size, 0); const kayit = (on) => SERVIS.reduce((t, s) => t + satirlar(on, s).length, 0); const ort = (on) => Math.round(bayt(on) / kayit(on)); // kayit basina bayt: basamak gurultusune dayanikli const olcutBayt = SERVIS.reduce((t, s) => t + JSON.stringify(olcut[s]).length, 0); console.log(`\nhacim: gunluk ${kayit("gunluk")} kayit x ort. ${ort("gunluk")} bayt, ` + `iz ${kayit("iz")} aralik x ort. ${ort("iz")} bayt, ` + `olcut ${SERVIS.length * 4} alan (istek sayisindan bagimsiz) / ${olcutBayt} bayt`);
Aşağıdaki blok beş süreci ayağa kaldırır, yirmi istek gönderip üç soruyu sorar, sonra yüz seksen istek daha gönderip yalnız hacim satırını yeniden basar.
node servis.mjs ucretlendirme 9104 & node servis.mjs katalog 9103 & node servis.mjs bildirim 9105 & node servis.mjs uyelik 9102 9104 & node servis.mjs odunc 9101 9102 9103 9105 & sleep 1 node kos.mjs 20 node sor.mjs node kos.mjs 180 node sor.mjs | tail -1 pkill -f "node servis.mjs"
20 istek gonderildi soru sinyal adim yanit S1 nerede gunluk 12 yanitlanamadi (60 ms ustunde 4 odunc kaydi; ardil kayitla ortak kimlik yok) S1 nerede olcut 6 yanitlanamadi (istek basina ayrim yok; servis ortalamasi var) S1 nerede iz 7 kismi (60 ms ustunde 4 aralik; hangi ardilda gectigi yok) S2 kac hata gunluk 11 5 istek S2 kac hata olcut 6 5 istek S2 kac hata iz 6 yanitlanamadi (iz kaydinda durum alani yok) S3 kim gunluk 11 uye 4023 S3 kim olcut 6 yanitlanamadi (toplam sayac, kimlik tasimaz) S3 kim iz 6 yanitlanamadi (aralikta kimlik alani yok) hacim: gunluk 100 kayit x ort. 98 bayt, iz 100 aralik x ort. 14 bayt, olcut 20 alan (istek sayisindan bagimsiz) / 240 bayt 180 istek gonderildi hacim: gunluk 1000 kayit x ort. 99 bayt, iz 1000 aralik x ort. 15 bayt, olcut 20 alan (istek sayisindan bagimsiz) / 253 bayt
Dokuz Denemenin Beşi Boş Döndü
Tablo dokuz satırdır ve beşi yanıtsızdır. Yanıtlananlar şunlar: hata sayısını ölçüt altı adımda, günlük on bir adımda veriyor; etkilenen üyeyi yalnız günlük veriyor, o da on bir adımda. Birinci soruyu hiçbir sinyal tam yanıtlamıyor.
Aradaki fark sinyalin kalitesi değil, taşıdığı alandır. Ölçüt en ucuz yanıtı verir çünkü sorulacak
soru ölçüt yazılırken zaten seçilmiştir: hata sayacı “kaç istek hata aldı” sorusunun kodda
donmuş hâlidir. Aynı nedenle “hangi üye” sorusunu yanıtlayamaz — toplamın içinde kimlik yoktur ve
sonradan çıkarılamaz. Günlük tersidir: her olayın kendi satırı vardır, o yüzden kimlik sorusuna
yanıt verir, ama düz cümle olduğu için her sorguda beş ayrı ayrıştırma geçişi ister ve düzenli
ifade satır biçimine bağlıdır.
Birinci sorunun yanıtsız kalması bu dersin asıl bulgusudur. Yirmi istekten dördü altmış milisaniyenin üstünde kaldı — bu sayı koşumdan bağımsızdır, çünkü yavaşlığı üreten kural nüsha numarasına bağlıdır. Koşumdan bağımsız olan asıl şey ise o dört isteğin süresinin nerede harcandığının hiçbir depodan okunamamasıdır. Günlükte katalog servisinin de yüz milisaniyelik satırları duruyor, ama o satırı bu ödünç satırıyla birleştirecek ortak bir alan yok. İz kaydı en yakınına geliyor: ödünç sürecindeki aralığın süresini veriyor, ama numarası yalnız kendi sürecinde artan bir sayaç olduğu için katalog sürecindeki aralıkla eşleşmiyor. Beş süreç, beş ayrı numara uzayı, sıfır bağ.
Hacim
Son iki satır üçüncü ölçüdür. Yirmi dış istek beş süreçte yüz günlük kaydı ve yüz aralık üretti; iki yüz dış istek bin kayıt ve bin aralık üretti. Oran sabittir: dış istek başına beş kayıt, çünkü çağrı çarpanı beştir ve her süreç kendi kaydını yazar. Kayıt başına ortalama boy günlükte doksan sekiz-doksan dokuz bayt, izde on dört-on beş bayt; aradaki yedi katlık fark, günlüğün her satırda zaman damgasını ve cümle kalıbını yeniden taşımasından geliyor.
Ölçüt bu büyümenin dışındadır. Beş servis, servis başına dört alan: yirmi alan, istek sayısı ne olursa olsun. Toplam yük 240 bayttan 253 bayta çıktı; artışın tamamı biriken süre toplamının basamak sayısından geliyor, kayıt sayısından değil. Ölçütün maliyeti istek sayısıyla değil, alan sayısıyla büyür; günlük ve izin maliyeti istek sayısıyla büyür. Üç sinyalin bir arada tutulmasının nedeni de budur: aynı soruyu farklı maliyetlerle ve farklı çözünürlükte yanıtlarlar.
Özet
- Günlük tek tek olayları, ölçüt önceden seçilmiş toplamları, iz bir işin adımlarını aralık olarak kaydeder; üçü aynı istekten aynı süreçte doğdu.
- Dokuz denemenin beşi yanıtsız kaldı: ölçüt kimlik sorusuna, iz durum ve kimlik sorusuna yanıt veremedi, “bu istek nerede yavaşladı” sorusunu hiçbiri tam yanıtlamadı.
- Ölçüt en az adımı harcadı (6), günlük en çoğunu (11–12); fark düz metnin servis başına bir ayrıştırma geçişi istemesinden geliyor.
- Ölçüt sorulacak soruyu yazılırken seçer:
hatasayacı bir soruyu ucuzlatır, kimlik sorusunu ise sonsuza kadar kapatır. - Hacim iki farklı yasaya uyuyor: 20 istek 100 kayıt, 200 istek 1000 kayıt üretti; ölçüt her iki durumda da 20 alan kaldı ve yalnız basamak sayısı kadar büyüdü.
Sonraki Adım
Beş yanıtsız sorunun üçünün tek bir ortak nedeni var: kayıtlarda isteği tanımlayan bir alan yok. Ödünç servisinin yazdığı satırla katalog servisinin yazdığı satırı birbirine bağlayacak hiçbir şey bulunmuyor, çünkü satırlar cümle olarak yazılıyor ve cümlede yalnız o sürecin bildiği şeyler var. Sonraki ders günlüğü düz cümle olmaktan çıkarıp alanlara böler ve isteğe süreç sınırını geçen bir kimlik takar. Ölçülecek olanlar bellidir: aynı sorunun süzme ve ayrıştırma adımlarının kaç tanesinin düştüğü, bir isteğin kaç serviste izlenebilir hâle geldiği ve kimliğin taşınmadan bırakıldığı çağrı yerlerinin sayısı.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.