Ders 07 / 18
Dağıtık İzleme Altyapısı
Bir isteğin beş servis arasında izlediği yolun iz ağacı olarak kurulması: on bir işletim sorusunun ölçüt, günlük ve iz görünümlerine ayrı ayrı sorulması, adım başına baytın ölçülmesi ve örnekleme oranının kaçırdığı yavaş isteğin sayılması.
İçindekiler
Ölçüt toplandı, günlük saklandı, ikisinin de bedeli çıkarıldı. Gecikme eğrisinin yükseldiği de görülüyor. Görülmeyen şey, eğrinin arkasındaki tek bir isteğin beş servis arasında izlediği yol: hangi adımda beklendi, hangi çağrı yeniden denendi, hangi servis hiç çağrılmadı. Bu üç soru ne ölçüt kovalarında ne günlük satırlarında karşılık bulur; ikisi de isteği değil olayı sayar.
Dağıtık izlemenin üç parçası vardır. Adım kaydı bir iş parçasının kaydıdır: kendi kimliği, üst adımın kimliği, servis adı, başlangıç, süre. Bağlam taşıma isteğin kimliğini çağrıdan çağrıya geçirir; taşınmazsa ağaç beş parçaya bölünür. Toplayıcı adım kayıtlarını iz deposuna yazar, depo kayıtları iz kimliğine göre gruplar: zaman serisi deposu zamana göre, iz deposu kimliğe göre dizinlenir; ölçüt kaydı dakikada bir, iz kaydı her adımda bir yazılır.
Bir İsteğin Ağacı
Model bölgesel ölçüm ağının bir saatlik trafiğini üretir; üreteç kodda yazılıdır, ölçüm değil kurgudur.
// iz/model.mjs — bolgesel olcum aginin (kurgu) bir saatlik trafik MODELI. Her istek bes servisten // gecer; her adim bir kayittir: kimlik, ust kimlik, servis, baslangic, sure, hata, deneme. // IG1: cagri grafigi sabit — gecit okuma ile faturayi, okuma dogrulamayi, fatura isemrini cagirir. // IG2: adimin kendi suresi taban + gurultu, hata orani binde. IG3: isteklerin bir kisminda okuma // kendi onbelleginden yanitlar ve dogrulama hic cagrilmaz. IG4: isemri cagrisinin bir kismi zaman // asimina duser, bir kez yeniden denenir. IG5: bazi istekler yavastir, yavaslik tek adimda toplanir. export const TOHUM = 20260802, ISTEK = 24000, DK = 60; export const SERVIS = ["gecit", "okuma", "dogrulama", "fatura", "isemri"]; export const PROFIL = { gecit: [4, 2, 0], okuma: [18, 6, 2], dogrulama: [26, 9, 3], fatura: [31, 11, 4], isemri: [44, 15, 9] }; export const ATLAMA = 0.11, DENEME = 0.06, YAVAS = 0.02, ZAMAN_ASIMI = 120; export function uret() { let s = TOHUM; const r = () => ((s = (s * 1664525 + 1013904223) >>> 0) / 2 ** 32); const gur = (ad) => { const [t, g] = PROFIL[ad]; return Math.max(1, Math.round(t + (r() * 2 - 1) * g)); }; const izler = []; for (let i = 0; i < ISTEK; i += 1) { const t0 = Math.floor(r() * DK * 60000), adimlar = []; let no = 0; const ekle = (ust, servis, bas, sure, hata, deneme) => (adimlar.push({ id: (no += 1), ust, servis, bas, sure, hata, deneme }), no); const yavasAdim = r() < YAVAS ? (r() < 0.6 ? "isemri" : SERVIS[1 + Math.floor(r() * 4)]) : null; const kat = yavasAdim ? 8 + Math.floor(r() * 13) : 1; const uzat = (ad, d) => (ad === yavasAdim ? d * kat : d); const atlandi = r() < ATLAMA, dogSure = atlandi ? 0 : uzat("dogrulama", gur("dogrulama")); const okumaKendi = uzat("okuma", gur("okuma")); const okumaSure = okumaKendi + (atlandi ? 0 : dogSure + 2); const yenidenDendi = r() < DENEME, isemriSure = uzat("isemri", gur("isemri")); const isemriToplam = isemriSure + (yenidenDendi ? ZAMAN_ASIMI + 2 : 0); const faturaSure = uzat("fatura", gur("fatura")) + isemriToplam + 2; const faturaBas = PROFIL.gecit[0] + okumaSure + 2; const kok = ekle(0, "gecit", 0, faturaBas + faturaSure, false, 1); const okuma = ekle(kok, "okuma", PROFIL.gecit[0], okumaSure, false, 1); if (!atlandi) ekle(okuma, "dogrulama", PROFIL.gecit[0] + okumaKendi + 1, dogSure, false, 1); const fatura = ekle(kok, "fatura", faturaBas, faturaSure, false, 1); let ib = faturaBas + 2; if (yenidenDendi) { ekle(fatura, "isemri", ib, ZAMAN_ASIMI, true, 1); ib += ZAMAN_ASIMI + 2; } ekle(fatura, "isemri", ib, isemriSure, r() < PROFIL.isemri[2] / 1000, yenidenDendi ? 2 : 1); izler.push({ iz: `iz-${(i + 1).toString(36).padStart(4, "0")}`, t0, toplam: faturaBas + faturaSure, adimlar, atlandi, yenidenDendi, yavasAdim }); } return izler; } // Adimin kendi suresi: adim suresi eksi cocuklarinin suresi. export const kendi = (iz, a) => a.sure - iz.adimlar.filter((c) => c.ust === a.id).reduce((x, c) => x + c.sure, 0); if (import.meta.filename === process.argv[1]) { const I = uret(), adim = I.reduce((x, z) => x + z.adimlar.length, 0); console.log(`tohum ${TOHUM}, ${ISTEK} istek, ${adim} adim, ${SERVIS.length} servis, ${DK} dakika`); console.log(`dogrulamasi atlanan ${I.filter((z) => z.atlandi).length}, yeniden denenen cagri ` + `${I.filter((z) => z.yenidenDendi).length}, yavas istek ${I.filter((z) => z.yavasAdim).length}`); const o = I.find((z) => z.yenidenDendi && z.yavasAdim === "isemri" && !z.atlandi); console.log(`\niz agaci — ${o.iz} (${o.toplam} ms)`); const yaz = (ust, gir) => o.adimlar.filter((x) => x.ust === ust).forEach((a) => { console.log(`${gir}${a.servis.padEnd(14 - gir.length)}${String(a.sure).padStart(6)} ms` + ` kendi ${String(kendi(o, a)).padStart(4)} ms deneme ${a.deneme}${a.hata ? " zaman asimi" : ""}`); yaz(a.id, gir + " "); }); yaz(0, ""); }
tohum 20260802, 24000 istek, 118794 adim, 5 servis, 60 dakika
dogrulamasi atlanan 2624, yeniden denenen cagri 1418, yavas istek 477
iz agaci — iz-008m (889 ms)
gecit 889 ms kendi 6 ms deneme 1
okuma 47 ms kendi 16 ms deneme 1
dogrulama 31 ms kendi 31 ms deneme 1
fatura 836 ms kendi 44 ms deneme 1
isemri 120 ms kendi 120 ms deneme 1 zaman asimi
isemri 672 ms kendi 672 ms deneme 2
Girinti bağı gösteriyor. Kök adım 889 ms sürdü, kendi işi 6 ms. Fatura adımı 836 ms sürdü, kendi işi 44 ms — kalanı iş emri çağrısını beklemekle geçti ve o çağrı iki kez yapıldı; ilk deneme 120 ms’de zaman aşımına düştü. Bunların hiçbiri tek bir süre ölçütünden okunamaz: üst-alt bağı olmadan 889 ms yalnız bir sayıdır.
On Bir Soru, Üç Görünüm
Aynı olaylardan üç görünüm üretilir: dakikalık ölçüt kovaları, adım başına bir günlük satırı, iz ağaçları. Sorular üçüne de aynı biçimde sorulur; görünüm gerekli alanı taşımıyorsa arama boş döner ve yanıt yazılmaz.
// iz/soru.mjs — ayni olaylardan uc gorunum uretilir: dakikalik olcut kovalari, adim basina bir // gunluk satiri, iz agaclari. On iki soru ucune de sorulur; gorunum gerekli alani tasimiyorsa // arama bos doner ve yanit yazilmaz. import { uret, kendi, ISTEK } from "./model.mjs"; const I = uret(), KOVA = [25, 50, 100, 200, 400, 800, 1600, Infinity], ESIK = 400; // IG6 (varsayim): olcut kovasi ve gunluk satiri istek kimligi tasimaz; iz adimi hata bayragi // tasir, hata metnini tasimaz. const OLCUT = new Map(), GUNLUK = [], METIN = ["zaman asimi", "baglanti reddedildi", "gecersiz okuma"]; for (const z of I) for (const a of z.adimlar) { const k = `${a.servis}|${Math.floor((z.t0 + a.bas) / 60000)}`; let o = OLCUT.get(k); if (!o) OLCUT.set(k, (o = { n: 0, hata: 0, kova: new Array(KOVA.length).fill(0) })); o.n += 1; o.hata += a.hata ? 1 : 0; o.kova[KOVA.findIndex((b) => a.sure <= b)] += 1; GUNLUK.push({ servis: a.servis, sure: a.sure, hata: a.hata, mesaj: a.hata ? METIN[(a.sure + a.deneme) % 3] : "tamam" }); } const p95 = (x) => x.slice().sort((a, b) => a - b)[Math.floor(x.length * 0.95)]; const sik = (m) => [...m].sort((a, b) => b[1] - a[1])[0]; const say = (dizi, f) => dizi.reduce((m, x) => m.set(f(x), (m.get(f(x)) || 0) + 1), new Map()); const kok = [...OLCUT].filter(([x]) => x.startsWith("gecit|")).map(([, o]) => o); const gk = GUNLUK.filter((l) => l.servis === "gecit"), yavas = I.filter((z) => z.toplam > ESIK); const enYavas = (z) => z.adimlar.slice().sort((a, b) => kendi(z, b) - kendi(z, a))[0]; const ort = (f) => { const g = I.filter(f); return g.reduce((x, z) => x + z.toplam, 0) / g.length; }; const yuzde = (a, b) => `%${(a / b * 100).toFixed(a / b < 0.1 ? 2 : 0)}`; // bildirilen yavas istek: kimligi elde olan tek istek const HEDEF = I.filter((z) => z.yavasAdim && z.yenidenDendi)[0], H = HEDEF.iz; const oyok = () => OLCUT.get(H) ?? null; // kova kimlikle aranmaz const gyok = () => GUNLUK.find((l) => l.iz === H) ?? null; // satirda iz alani yok const SORU = [ ["istek suresi p95", () => { const t = kok.reduce((a, o) => a.map((v, i) => v + o.kova[i]), KOVA.map(() => 0)), n = t.reduce((a, b) => a + b, 0); let s = 0; for (let i = 0; ; i += 1) if ((s += t[i]) >= n * 0.95) return `<=${KOVA[i]} ms`; }, () => `${p95(gk.map((l) => l.sure))} ms`, () => `${p95(I.map((z) => z.toplam))} ms`], ["hata orani", () => yuzde([...OLCUT.values()].reduce((a, o) => a + o.hata, 0), [...OLCUT.values()].reduce((a, o) => a + o.n, 0)), () => yuzde(GUNLUK.filter((l) => l.hata).length, GUNLUK.length), () => yuzde(I.reduce((x, z) => x + z.adimlar.filter((a) => a.hata).length, 0), GUNLUK.length)], [`${ESIK} ms ustu istek`, () => kok.reduce((a, o) => a + o.kova[5] + o.kova[6] + o.kova[7], 0), () => gk.filter((l) => l.sure > ESIK).length, () => yavas.length], [`${H}: hangi adim yavas`, oyok, gyok, () => enYavas(HEDEF).servis], [`${H}: yavas adimin payi`, oyok, gyok, () => yuzde(kendi(HEDEF, enYavas(HEDEF)), HEDEF.toplam)], [`${H}: yeniden denenen cagri`, oyok, gyok, () => { const d = HEDEF.adimlar.find((a) => a.deneme > 1); return d ? `${d.servis} x${d.deneme}` : "yok"; }], [`${H}: dogrulama cagrildi mi`, oyok, gyok, () => (HEDEF.adimlar.some((a) => a.servis === "dogrulama") ? "evet" : "hayir")], ["yavas isteklerde en sik yavas adim", oyok, gyok, () => { const [ad, n] = sik(say(yavas, (z) => enYavas(z).servis)); return `${ad} ${yuzde(n, yavas.length)}`; }], ["dogrulamasi atlanan istek ne kadar hizli", oyok, gyok, () => `${(ort((z) => !z.atlandi) - ort((z) => z.atlandi)).toFixed(0)} ms`], ["en sik hata metni", () => null, () => sik(say(GUNLUK.filter((l) => l.hata), (l) => l.mesaj))[0], () => null], ["yavas adimin icindeki sorgu", () => null, () => null, () => HEDEF.adimlar.find((a) => a.ust === enYavas(HEDEF).id && a.servis === enYavas(HEDEF).servis) ?? null], ]; console.log(`${"soru".padEnd(42)}${"olcut".padStart(12)}${"gunluk".padStart(22)}${"iz".padStart(14)}`); const n = [0, 0, 0]; for (const [ad, ...f] of SORU) { const y = f.map((fn) => { const v = fn(); return v === null || v === undefined ? "-" : String(v); }); y.forEach((v, i) => { if (v !== "-") n[i] += 1; }); console.log(`${ad.padEnd(42)}${y[0].padStart(12)}${y[1].padStart(22)}${y[2].padStart(14)}`); } const disi = SORU.filter(([, o, g]) => (o() ?? null) === null && (g() ?? null) === null); console.log(`\n${ISTEK} istek, ${SORU.length} soru — olcut ${n[0]}, gunluk ${n[1]}, iz ${n[2]}`); console.log(`olcut ve gunlukle yanitlanamayan ${disi.length} soru — izle yanitlanan ` + `${disi.filter(([, , , z]) => (z() ?? null) !== null).length}, hicbiriyle yanitlanamayan ` + `${SORU.filter(([, ...f]) => f.every((fn) => (fn() ?? null) === null)).length}`);
soru olcut gunluk iz istek suresi p95 <=400 ms 248 ms 248 ms hata orani %1.38 %1.38 %1.38 400 ms ustu istek 411 411 411 iz-0024: hangi adim yavas - - okuma iz-0024: yavas adimin payi - - %45 iz-0024: yeniden denenen cagri - - isemri x2 iz-0024: dogrulama cagrildi mi - - evet yavas isteklerde en sik yavas adim - - isemri %82 dogrulamasi atlanan istek ne kadar hizli - - 27 ms en sik hata metni - baglanti reddedildi - yavas adimin icindeki sorgu - - - 24000 istek, 11 soru — olcut 3, gunluk 4, iz 9 olcut ve gunlukle yanitlanamayan 7 soru — izle yanitlanan 6, hicbiriyle yanitlanamayan 1
İlk üç soru üç görünümde de yanıtlanıyor ama aynı çözünürlükte değil: ölçüt görünümü p95 için
<=400 ms diyor, gerçek değer 248 ms — kova sınırı 200 ile 400 arasını tek hücrede topluyor. Soru
yanıtlanıyor, yanıt karara yetmiyor; bu yanıtsızlıktan farklı bir kusurdur.
Sonraki dört soru bildirilen tek bir isteğe bakıyor; iki görünümde arama boş dönüyor, çünkü kova da
günlük satırı da iz kimliği taşımıyor. iz-0024 isteğinde en uzun kendi süresi okuma adımında
(toplam sürenin yüzde 45’i), iş emri çağrısı iki kez yapılmış, doğrulama çağrılmış. Küme sorusuyla
tek istek sorusu farklı yanıt veriyor: 400 ms’yi aşan 411 isteğin yüzde 82’sinde yavaş adım iş
emriydi, bildirilen istekte değildi.
Son iki soru izin sınırını çiziyor. Hata metnini yalnız günlük taşıyor; iz adımı hata bayrağı tutuyor, metni tutmuyor. Yavaş adımın içindeki sorgu ise üç görünümün hiçbiriyle yanıtlanamıyor: enstrümantasyon o derinlikte adım açmıyor. Yedi sorunun altısı izle yanıtlandı, yanıtsız kalanın nedeni toplama değil enstrümantasyon derinliği.
Adım Başına Bayt
// iz/bedel.mjs — izin bayt bedeli ve kokte verilen ornekleme kararinin taranmasi. Kayit boyutu // gercek kayittan olculur, sikistirma uygulanmaz. IZ_SORU: yalniz izle yanitlanan soru sayisi. import { uret, ISTEK } from "./model.mjs"; const I = uret(), ESIK = 400, IZ_SORU = 6, gun = (b) => (b * 24 / 1e6).toFixed(1); const adim = I.reduce((x, z) => x + z.adimlar.length, 0); const izBayt = I.reduce((x, z) => x + z.adimlar.reduce((y, a) => y + JSON.stringify({ iz: z.iz, ...a }).length, 0), 0); console.log(`${adim} adim, adim basina ${(izBayt / adim).toFixed(0)} bayt, istek basina ` + `${(izBayt / ISTEK).toFixed(0)} bayt (${(adim / ISTEK).toFixed(1)} adim), gunde ${gun(izBayt)} MB`); const kirp = (s) => { let h = 7; for (const c of s) h = (h * 31 + c.charCodeAt(0)) >>> 0; return h; }; const yavas = I.filter((z) => z.toplam > ESIK), H = I.filter((z) => z.yavasAdim && z.yenidenDendi)[0].iz; console.log(`\n${"oran".padEnd(7)}${"kayitli iz".padStart(11)}${"gun/MB".padStart(8)}` + `${"yakalanan".padStart(11)}${"kacan yavas".padStart(13)}${"MB/soru".padStart(9)} bildirilen istek`); for (const n of [1, 2, 5, 20, 100]) { const tut = (z) => kirp(z.iz) % n === 0, k = I.filter(tut), b = izBayt / ISTEK * k.length; console.log(`${`1/${n}`.padEnd(7)}${String(k.length).padStart(11)}${gun(b).padStart(8)}` + `${String(yavas.filter(tut).length).padStart(11)}` + `${String(yavas.length - yavas.filter(tut).length).padStart(13)}` + `${(+gun(b) / IZ_SORU).toFixed(1).padStart(9)} ${k.some((z) => z.iz === H) ? "kayitli" : "kayitsiz"}`); }
118794 adim, adim basina 92 bayt, istek basina 455 bayt (4.9 adim), gunde 262.2 MB oran kayitli iz gun/MB yakalanan kacan yavas MB/soru bildirilen istek 1/1 24000 262.2 411 0 43.7 kayitli 1/2 12001 131.1 209 202 21.8 kayitsiz 1/5 4800 52.4 82 329 8.7 kayitli 1/20 1200 13.1 27 384 2.2 kayitsiz 1/100 245 2.7 6 405 0.5 kayitsiz
Bir istek 4,9 adım ve 455 bayt; adım başına 92 bayt, günde 262 MB. Yalnız izle yanıtlanan altı sorunun her biri bu ölçekte 43,7 MB’ye mal oluyor. Örnekleme çarpanı doğrudan bölüyor: 1/20 oranında hacim 13 MB’ye, soru başına maliyet 2,2 MB’ye iniyor. Bedel sağ sütunlarda — 411 yavaş isteğin 384’ü kayıtsız kalıyor. Karar kökte, sonuç görülmeden verildiği için yavaş olduğu için tutulan istek yok; bildirilen isteğin elde olması kura işidir.
Yanıtsız sorunun bedeli buradan okunur. “Hangi adım yavaş” yanıtlanamadığında müdahale beş servise birden bakar, iz varken bakılacak yer tek bir adımdır. Örnekleme yüzünden yanıtsız kalındığında bedel eşit bölünmez: 1/20 oranında yavaş isteklerin yüzde 93’ü için soru yanıtsızdır.
Özet
- İz ağacı adımların üst-alt bağıdır; bağ olmadan toplam süre yalnız bir sayıdır.
- On bir sorudan ölçüt görünümü 3’ünü, günlük görünümü 4’ünü, iz görünümü 9’unu yanıtladı; ölçüt ve günlükle yanıtlanamayan 7 sorunun 6’sı izle yanıtlandı.
- Bir soru üç görünümün hiçbiriyle yanıtlanmadı — yavaş adımın içindeki sorgu; eksik olan toplama değil enstrümantasyon derinliğidir.
- İzin bedeli adım sayısıyla ölçülür: adım başına 92 bayt, günde 262 MB, yalnız izle yanıtlanan soru başına 43,7 MB.
- Kökte verilen 1/20 örnekleme hacmi 13 MB’ye indiriyor ama 411 yavaş isteğin 384’ünü kayıtsız bırakıyor; hangi isteğin kaldığı sorunun önemine bakmaz.
Sonraki Adım
Bu derste beş servisin adım kayıtları tek ağaçta birleşti, çünkü hepsi aynı alan adlarını
kullanıyordu: servis, sure, deneme. Gerçek bir ağda bu birleşme kendiliğinden olmaz; beş ekip
kendi adlandırmasını yazar, biri süreyi milisaniye öteki mikrosaniye tutar, aynı kavram üç ada
bölünür ve iki adım yan yana geldiğinde karşılaştırılamaz.
Sonraki ders ortak bir telemetri sözleşmesini alan alan tanımlar ve bedelini sayar: kaç alan ve kaç
servis değişikliği karşılığında kaç soru birleştirilebiliyor, kaç servis uyumsuz kalıyor.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.