İçeriğe geç
academia.sh

Ders 02 / 19

Yapılandırılmış Günlükleme

Düz cümle günlüğün alan tabanlı kayda dönüştürülmesi ve isteğe süreç sınırını geçen bir bağıntı kimliği takılması: aynı sorunun kaç ayrıştırma ve süzme adımıyla yanıtlandığı, bir isteğin kaç serviste izlenebildiği ve kimliğin taşınmadan bırakıldığı çağrı yerlerinin sayılması.

İçindekiler

Önceki ders beş yanıtsız soru bıraktı ve üçünün ortak nedeni tekti: kayıtlarda isteği tanımlayan bir alan yok. Günlük satırı cümleydi, cümlede yalnız o sürecin bildiği şeyler vardı ve iki servisin satırını birleştirecek hiçbir şey yoktu. Bu ders iki değişiklik yapar: kayıt cümle olmaktan çıkıp alanlara bölünür, isteğe süreç sınırını geçen bir bağıntı kimliği takılır. İkisinin de bedeli ölçülür.

Yapılandırılmış günlükleme, kaydın serbest metin yerine adı belli alanlardan oluşmasıdır. Bağıntı kimliği, bir dış isteğe verilen ve o istekten doğan bütün iç isteklerde taşınan değerdir. İkincisi birincisi olmadan işe yaramaz: kimlik de sonuçta bir alandır ve serbest cümlenin içinde aranması gereken bir dize değil, adıyla süzülebilen bir değer olmalıdır.

Alanlara Bölünen Kayıt

Günlükçü kendi yazılır ve tek işi vardır: sabit alanlarla çağrı anındaki alanları birleştirip tek satır olarak eklemek.

// gunlukcu.mjs — alan tabanli gunlukcu; her kayit tek satir, her alan adiyla durur
import { appendFileSync } from "node:fs";
export const gunlukcu = (dosya, sabit) => (alanlar) =>
  appendFileSync(dosya, JSON.stringify({ ...sabit, ...alanlar }) + "\n");

Servis her istekte iki kayıt yazar: önceki dersin düz cümlesi ve alan tabanlı karşılığı. İkisi de aynı olguyu, aynı koşumda, aynı değerlerle anlatır; aradaki tek fark biçimdir, o yüzden ölçülen adım farkı biçimin farkıdır.

TL2 — bağıntı kimliği, dış isteği ilk karşılayan servis tarafından üretilir; başlıkta gelen bir kimlik varsa üretilmez, olduğu gibi sürdürülür. Gerekçe: kimliğin üretim yeri tek olmalıdır, yoksa aynı istek her serviste yeni bir kimlik alır ve birleştirme yine yapılamaz. Üretim kuralı burada nüsha numarasına bağlanmıştır, böylece ölçümdeki kimlikler koşumdan bağımsızdır.

// servis2.mjs — <ad> <port> [ardil...]; ayni istek icin iki gunluk bicimi yazar
import { createServer } from "node:http";
import { writeFileSync, appendFileSync } from "node:fs";
import { gunlukcu } from "./gunlukcu.mjs";
const [ad, port, ...ardil] = process.argv.slice(2);
const BILDIRIM = "9205";
writeFileSync(`duz-${ad}.txt`, ""); writeFileSync(`alan-${ad}.txt`, "");
const alanYaz = gunlukcu(`alan-${ad}.txt`, { servis: ad });
let sayac = 0;
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")}`;
const cagir = (p, u, baginti) => fetch(uc(p, u), { headers: { "x-baginti": baginti } });
const haberVer = (p, u) => fetch(uc(p, u));      // eski yardimci: baslik parametresi hic olmadi

createServer(async (istek, yanit) => {
  const u = new URL(istek.url, "http://y").searchParams;
  const uye = u.get("uye"), nusha = Number(u.get("nusha"));
  const baginti = istek.headers["x-baginti"] ?? `${ad}-${nusha}`;   // kimlik yoksa uretilir
  const t0 = performance.now(); sayac += 1;
  await bekle(ad === "katalog" && nusha % 5 === 0 ? 45 : 5);
  let durum = ad === "ucretlendirme" && uye === "4023" ? 402 : 200;
  for (const p of ardil) {
    const y = p === BILDIRIM ? await haberVer(p, u) : await cagir(p, u, baginti);
    if (y.status !== 200) durum = y.status;
  }
  const sure = Math.round(performance.now() - t0);
  appendFileSync(`duz-${ad}.txt`, `${ad}: uye ${uye} icin ${nusha} numarali nusha istegi ` +
    `${sure} ms surdu, sonuc ${durum}\n`);
  alanYaz({ baginti, uye, nusha, sure, durum });
  yanit.statusCode = durum; yanit.end("bitti");
}).listen(Number(port));

Kimliğin sınırı geçmesi tek satırda olur: cagir başlığı ekler. Yanındaki haberVer ise başlık parametresi hiç almamış eski bir yardımcıdır ve bildirim çağrısı onunla yapılır. Bu, kurgu bir hata değil, kimlik eklemenin tipik sonucudur: kimlik istemci yardımcısına eklenir, ama sistemde o yardımcıyı kullanmayan bir çağrı yolu kalır. Ölçümün işi o yolu saymaktır.

Ölçüm

Sorgu aracı iki soruyu iki biçimde sorar. Adım tanımı önceki dersle aynıdır: bir dosyayı okumak, ayrıştırmak, süzmek ya da iki depoyu birleştirmeyi denemek birer adımdır. Düz metinde her servisin satır kalıbı ayrıdır, bu yüzden ayrıştırma servis başına bir geçiştir; alan tabanlı kayıtta biçim genel olduğu için ayrıştırma tek geçiştir.

// sor2.mjs — ayni sorular iki gunluk biciminde; adim = bir depo gecisi
import { readFileSync } from "node:fs";
const SERVIS = ["odunc", "uyelik", "katalog", "ucretlendirme", "bildirim"];
const CAGRI = [["odunc", "uyelik"], ["odunc", "katalog"], ["odunc", "bildirim"], ["uyelik", "ucretlendirme"]];
const RE = /uye (\d+) icin (\d+) numarali nusha istegi (\d+) ms surdu, sonuc (\d+)/;
let adim = 0; const say = () => { adim += 1; };
const satir = (on, s) => { say(); return readFileSync(`${on}-${s}.txt`, "utf8").split("\n").filter(Boolean); };
const duzOku = () => {                       // her servisin satir kalibi ayri: servis basina bir ayristirma
  const o = {};
  for (const s of SERVIS) o[s] = satir("duz", s).map((l) => { const m = l.match(RE); return m && { uye: m[1], nusha: +m[2], sure: +m[3], durum: +m[4] }; });
  for (const s of SERVIS) say();
  return o;
};
const alanOku = () => {                      // alan tabanli: bicim genel, tek ayristirma gecisi
  const o = {}; for (const s of SERVIS) o[s] = satir("alan", s);
  say(); for (const s of SERVIS) o[s] = o[s].map((l) => JSON.parse(l));
  return o;
};
const yaz = (soru, bicim, yanit) => console.log(`${soru.padEnd(11)}${bicim.padEnd(13)}${String(adim).padStart(5)}  ${yanit}`);
console.log(`${"soru".padEnd(11)}${"bicim".padEnd(13)}${"adim".padStart(5)}  yanit`);

// S3 — hangi uye etkilendi (iki bicim de yanitlayabiliyor)
adim = 0; const d = duzOku(); say();
yaz("S3 kim", "duz metin", `uye ${[...new Set(d.odunc.filter((r) => r.durum !== 200).map((r) => r.uye))].join(",")}`);
adim = 0; const a = alanOku(); say();
yaz("S3 kim", "alan tabanli", `uye ${[...new Set(a.odunc.filter((r) => r.durum !== 200).map((r) => r.uye))].join(",")}`);

// S1 — bu istek nerede yavasladi; hedef: 5 numarali nushanin istegi
const HEDEF = "odunc-5";
adim = 0; duzOku(); say(); say();
yaz("S1 nerede", "duz metin", "yanitlanamadi (satirda kimlik alani yok, servisler baglanamiyor)");
adim = 0; const a2 = alanOku(); say();
const yol = SERVIS.filter((s) => a2[s].some((r) => r.baginti === HEDEF));
const enUzun = yol.filter((s) => s !== "odunc").sort((x, y) =>
  a2[y].find((r) => r.baginti === HEDEF).sure - a2[x].find((r) => r.baginti === HEDEF).sure)[0];
say(); yaz("S1 nerede", "alan tabanli", `${HEDEF}: ${yol.join(" ")} (${yol.length}/${SERVIS.length} servis), en uzun ardil ${enUzun}`);

console.log(`\n${"cagri yeri".padEnd(26)}${"kimlik".padEnd(8)}kimlikli kayit`);
let tasiyan = 0;
for (const [ust, alt] of CAGRI) {
  const kume = new Set(a[ust].map((r) => r.baginti));
  const ortak = a[alt].filter((r) => kume.has(r.baginti)).length;
  if (ortak > 0) tasiyan += 1;
  console.log(`${`${ust} -> ${alt}`.padEnd(26)}${(ortak > 0 ? "evet" : "hayir").padEnd(8)}${ortak}/${a[alt].length}`);
}
console.log(`kimlik tasiyan cagri yeri ${tasiyan}/${CAGRI.length}, tasimayan ${CAGRI.length - tasiyan}; ` +
  `bir istek ${yol.length}/${SERVIS.length} serviste izlenebiliyor`);
const bayt = (on) => Math.round(SERVIS.reduce((t, s) => t + readFileSync(`${on}-${s}.txt`, "utf8").length, 0) / 100);
console.log(`kayit basina ort. boy: duz metin ${bayt("duz")} bayt, alan tabanli ${bayt("alan")} bayt (100 kayit)`);
cat > kos.mjs <<'EOF'
const UYE = ["4021", "4022", "4023", "4024"];      // 4023 odenmemis ucreti olan uye
for (let i = 1; i <= 20; i += 1)
  await fetch(`http://127.0.0.1:9201/is?uye=${UYE[(i - 1) % 4]}&nusha=${i}`);
EOF
node servis2.mjs ucretlendirme 9204 &
node servis2.mjs katalog 9203 &
node servis2.mjs bildirim 9205 &
node servis2.mjs uyelik 9202 9204 &
node servis2.mjs odunc 9201 9202 9203 9205 &
sleep 1

node kos.mjs
node sor2.mjs
pkill -f "node servis2.mjs"
soru       bicim         adim  yanit
S3 kim     duz metin       11  uye 4023
S3 kim     alan tabanli     7  uye 4023
S1 nerede  duz metin       12  yanitlanamadi (satirda kimlik alani yok, servisler baglanamiyor)
S1 nerede  alan tabanli     8  odunc-5: odunc uyelik katalog ucretlendirme (4/5 servis), en uzun ardil katalog

cagri yeri                kimlik  kimlikli kayit
odunc -> uyelik           evet    20/20
odunc -> katalog          evet    20/20
odunc -> bildirim         hayir   0/20
uyelik -> ucretlendirme   evet    20/20
kimlik tasiyan cagri yeri 3/4, tasimayan 1; bir istek 4/5 serviste izlenebiliyor
kayit basina ort. boy: duz metin 71 bayt, alan tabanli 88 bayt (100 kayit)

Dört Adım ve Bir Kör Nokta

Birinci karşılaştırma sıradan bir kazançtır. Aynı soru düz metinde on bir, alan tabanlı kayıtta yedi adım sürdü. Dört adımın tamamı ayrıştırmadan geldi: düz metinde her servisin satır kalıbı ayrı olduğu için düzenli ifade servis başına bir kez uygulanmak zorunda, alan tabanlı kayıtta biçim genel olduğu için tek geçiş yetiyor. Bu fark servis sayısıyla büyür — beş yerine yirmi servis olsa düz metin yirmi beş, alan tabanlı kayıt yirmi iki adım harcardı ve aradaki fark on dokuza çıkardı. Ayrıştırmanın kırılganlığı da buradadır: satır kalıbı değişince düzenli ifade sessizce eşleşmeyi bırakır, alan adı değişince kayıt açıkça o alanı kaybeder.

İkinci karşılaştırma dersin asıl noktasıdır. Önceki derste hiçbir sinyalin yanıtlayamadığı soru, alan tabanlı kayıtla sekiz adımda yanıtlandı: odunc-5 kimliği dört serviste görünüyor ve ardıllardan en uzunu katalog. Düz metin aynı soruda hâlâ yanıtsız — sorun sürelerin eksikliği değildi, süreleri birbirine bağlayacak alanın eksikliğiydi.

Üçüncü tablo kazancın sınırını gösteriyor. Dört çağrı yerinin üçü kimliği taşıyor, biri taşımıyor; bildirim servisinin yirmi kaydının hiçbirinde ödünç isteğinin kimliği yok. Bunun ölçülen sonucu şudur: bir istek beş servisten dördünde izlenebiliyor. Kimlik eklemenin bedeli tek satır, ama kapsamı çağrı yolu sayısıyla ölçülür ve tek bir atlanan çağrı yeri bütün bir servisi haritanın dışında bırakır. Bildirim burada rastgele seçilmedi: “bu bildirim neden gitmedi” bu sistemin en sık sorulan sorusudur ve tam da o servis kör noktada kaldı.

Son satır bedeli veriyor. Alan tabanlı kayıt kayıt başına 88 bayt, düz metin 71 bayt: alan adları her satırda yeniden yazıldığı için hacim dörtte bir arttı. Karşılığında sorgu adımı azaldı ve yanıtlanamaz bir soru yanıtlanır oldu. Ölçülen takas budur — yazma tarafında bayt, okuma tarafında adım.

Özet

  • Alan tabanlı kayıt aynı soruyu 11 yerine 7 adımda yanıtladı; farkın tamamı ayrıştırmadan geliyor ve servis sayısıyla büyüyor.
  • Bağıntı kimliği, önceki dersin hiçbir sinyalle yanıtlanamayan sorusunu 8 adımda yanıtlanır hâle getirdi: odunc-5 kimliği dört serviste izlendi, en uzun ardıl katalog çıktı.
  • Kimliği taşıyan çağrı yeri 4’te 3; taşımayan tek çağrı yeri bildirim servisini bütünüyle haritanın dışında bıraktı, bir istek 5 servisten 4’ünde izlenebiliyor.
  • Atlanan çağrı yeri kimlik eklemenin doğal artığıdır: kimlik istemci yardımcısına eklenir, o yardımcıyı kullanmayan eski çağrı yolu sessizce kimliksiz kalır.
  • Bedel yazma tarafındadır: kayıt başına 71 bayttan 88 bayta çıkış, alan adlarının her satırda yinelenmesinden geliyor.

Sonraki Adım

Günlük artık alanlara bölündü ve bir isteğin yolu dört serviste izlenebiliyor. Ama her sorgu hâlâ bütün kayıtları okuyup süzmekle başlıyor: yirmi istek için yüz kayıt okundu, iki yüz istek için bin kayıt okunacak. Önceki dersin ölçüt sinyali bu maliyetin dışındaydı, çünkü sorulacak soru kayıt yazılırken seçilmişti. Sonraki ders o seçimin kendisine bakar: aynı olgu sayaç, ölçer ve histogram olarak üç ayrı tiple ölçülür, her tipin yanıtlayabildiği ve kapattığı soru sayılır, histogramın kova sayısına göre bellek maliyeti ile yüzdelik hatası karşılaştırılır.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat