İçeriğe geç
academia.sh

Ders 02 / 16

Meşgul Ön Yüz

Sunucunun gönderdiği yükün istemcideki işi büyütmesi: sunucu yanıt süresi değişmezken kullanıcıya görünen sürenin büyümesi, aynı belirtiyi veren yük büyümesi ile istemci kodu büyümesinin kayıt başına iş ölçüsüyle ayrılması, sıralamayı sunucuya taşımanın 26,8 katlık kazancı ve biçimlendirmeyi taşımanın önbellek anahtarına ödettiği bedel.

İçindekiler

Sınır oranı işin hangi katmanda yapılacağını depo ile uygulama arasında çözdü. Zincirin bir ucu daha var ve orada takas kural değiştirir: uygulama katmanında iş düğüm eklenerek dağıtılır, istemcide dağıtılacak düğüm yoktur. Bu ders o üçüncü katmanı ele alır ve aynı üç adımı yürütür — belirti bir sayıdır, iki neden bir ölçümle ayrılır, düzeltmenin karşılığında büyüyen sayılır.

Ana iş parçacığı, uzun görev ve kullanıcı merkezli ölçütler Frontend Kalitesi kursunda tanımlandı ve ölçüldü; burada yeniden tanımlanmaz. Buradaki soru sistem tasarımı sorusudur: sunucunun gönderdiği yük, istemcideki işi hangi çarpanla büyütür ve bu çarpan hangi ölçümle görülür.

Belirti: Sunucu Sakin, Kullanıcı Bekliyor

Satıcı panelinde açık gönderi listesi açılıyor. Kullanıcıya görünen süre son iki sürümde büyüdü. Sunucu tarafındaki yanıt süresi ölçütü değişmedi; uç, K01’in tepe yükünü aynı sayılarla karşılıyor. Yanıtın baytı da denetlendi ve aktarımın payı değişmemiş. Belirti şu hâle geliyor: işin tamamı istemciye geçtikten sonra büyüyor.

Bu noktada iki neden vardır ve ikisi de sunucu ölçütlerinde görünmez.

Neden A — sunucu gönderdiği kayıt sayısını artırdı. İstemci kodu değişmedi, ama yük büyüdü; aynı kod daha çok kayıt üzerinde koşuyor.

Neden B — istemci kayıt başına iş ekledi. Yük aynı, ama yeni sürüm her kayıtta iki adım fazla yapıyor.

Ölçmeden verilen yanıt tahmindir ve tahminin iki yönü de vardır: “sunucu çok veri gönderiyor” ile “ön yüz ağırlaştı” cümleleri aynı belirtiden çıkarılır ve biri yanlıştır.

Ölçüm Düzeneği

Aşağıdaki koşum bir modeldir. Tarayıcı süresi ölçülmüyor ve uydurulmuyor; sayılan şey istemcinin tek bir iş parçacığında yapmak zorunda olduğu adımlardır: ayrıştırılan alan, dönüşüm adımı ve sıralama karşılaştırması. Üçü de gerçekten koşuyor, sayaçlar gerçek çağrıları sayıyor ve sıralamanın girdisi tohumlu bir üreteçten geliyor.

KK2 — panelde bir ekranda görünen satır: 25. Gerekçe: liste görünümü sayfa boyu kadar satır gösterir, kalanı kaydırmayla gelir. Duyarlılığı bir sonraki derste çağrı sayısına dönüşür.

// onyuz/isparcacigi.mjs — sunucunun gonderdigi yukun on yuzdeki isi nasil buyuttugu.
// MODELDIR: is birimi soyut bir adimdir, sure olculmez; sayilan sey ayristirilan alan,
// donusum adimi ve siralama karsilastirmasidir. Ucu de gercekten kosar.
const OLAY = 7, GORUNEN = 25, BOLGE = 12, ROTA = 40;      // K01 V4 = 7 durum olayi; KK2 = 25 satir

function uret(N) {                                        // sunucunun elindeki tam veri
  let s = 20260801 % 2147483647;                          // tohum gorunur; siralama sirasi belirlenimli
  const rast = () => (s = (s * 48271) % 2147483647) % 100000;
  const g = [];
  for (let i = 0; i < N; i += 1) {
    const olaylar = [];
    for (let j = 0; j < OLAY; j += 1)
      olaylar.push({ kod: (i * 3 + j) % 5, ts: rast(),
        bolge: (i + j) % BOLGE, rota: `R-${(i + j) % ROTA}`, tasiyici: (i + j) % 6 });
    g.push({ no: `TR-${i}`, satici: i % 20, agirlik: 1 + (i % 30), guncelleme: rast(), olaylar });
  }
  return g;
}

const AD = ["kabul", "aktarma", "dagitimda", "teslim", "bekliyor"];

function sunucu(N, plan) {                                // uctan cikan yuk
  const g = uret(N);
  if (plan === "ham") return g;
  const ust = [...g].sort((a, b) => b.guncelleme - a.guncelleme).slice(0, GORUNEN);
  return ust.map((x) => ({ no: x.no, guncelleme: x.guncelleme,
    olaylar: [...x.olaylar].sort((a, b) => b.ts - a.ts).slice(0, 3)
      .map((o) => (plan === "bicimli"
        ? { satir: `${AD[o.kod]} / ${o.rota} / ${o.ts}` }
        : { kod: o.kod, ts: o.ts, rota: o.rota })) }));
}

const alanSay = (x) => Array.isArray(x) ? x.reduce((a, y) => a + alanSay(y), 0)
  : (x && typeof x === "object") ? Object.values(x).reduce((a, y) => a + alanSay(y), 0) : 1;

function istemci(metin, { donusum, sirala }) {
  const s = { ayristirma: 0, donusum: 0, karsilastirma: 0, cizim: 0 };
  const veri = JSON.parse(metin);
  s.ayristirma = alanSay(veri);
  let liste = veri;
  if (sirala) {                                           // gonderi listesi: N log N
    liste = [...veri].sort((a, b) => { s.karsilastirma += 1; return b.guncelleme - a.guncelleme; });
    for (const x of liste) [...x.olaylar].sort((a, b) => { s.karsilastirma += 1; return b.ts - a.ts; });
  }
  if (donusum > 0) for (const x of veri) for (const o of x.olaylar) {   // once bicimlendir, sonra dilimle
    s.donusum += donusum;
    void [AD[o.kod] ?? "", String(o.ts).padStart(6, "0"), `${o.rota}`, `${o.bolge ?? 0}`,
      `${o.tasiyici ?? 0}`, `${o.kod ?? 0}`].slice(0, donusum);
  }
  for (const x of liste.slice(0, GORUNEN)) { s.cizim += 3; void x.no; }
  s.toplam = s.ayristirma + s.donusum + s.karsilastirma + s.cizim;
  return s;
}

const DURUM = [
  ["ham yuk, istemci s1", 200, "ham", { donusum: 4, sirala: true }],
  ["ham yuk, istemci s1", 400, "ham", { donusum: 4, sirala: true }],
  ["ham yuk, istemci s2", 200, "ham", { donusum: 6, sirala: true }],
  ["sirali yuk, istemci s1", 200, "sirali", { donusum: 4, sirala: false }],
  ["bicimli yuk", 200, "bicimli", { donusum: 0, sirala: false }],
];
const say = (x) => x.toLocaleString("tr-TR");
console.log(`model: bir gonderi ${OLAY} durum olayi tasiyor (K01 V4), ekranda ${GORUNEN} satir ` +
  `gorunuyor (KK2); is birimi soyut bir adimdir, sure olculmez`);
console.log(`\n${"durum".padEnd(24)}${"N".padStart(6)}${"gecen kayit".padStart(13)}` +
  `${"ayristirma".padStart(12)}${"donusum".padStart(10)}${"karsilastirma".padStart(15)}` +
  `${"toplam is".padStart(12)}${"kayit basina".padStart(14)}${"gorunen satir basina".padStart(22)}`);
const r = {};
for (const [ad, N, plan, ist] of DURUM) {
  const yuk = sunucu(N, plan), metin = JSON.stringify(yuk);
  const s = istemci(metin, ist);
  r[`${ad}|${N}`] = { ...s, gecen: yuk.length, bayt: Buffer.byteLength(metin) };
  console.log(ad.padEnd(24) + String(N).padStart(6) + say(yuk.length).padStart(13) +
    say(s.ayristirma).padStart(12) + say(s.donusum).padStart(10) + say(s.karsilastirma).padStart(15) +
    say(s.toplam).padStart(12) + (s.toplam / yuk.length).toFixed(2).padStart(14) +
    (s.toplam / GORUNEN).toFixed(1).padStart(22));
}

console.log(`\nolcekleme (ham yuk, istemci s1): N iki katina cikarken toplam is kac kat`);
console.log(`${"N".padStart(6)}${"toplam is".padStart(12)}${"kat".padStart(8)}` +
  `${"kayit basina".padStart(14)}${"karsilastirma/N".padStart(17)}`);
let onceki = 0;
for (const N of [50, 100, 200, 400, 800]) {
  const s = istemci(JSON.stringify(sunucu(N, "ham")), { donusum: 4, sirala: true });
  console.log(String(N).padStart(6) + say(s.toplam).padStart(12) +
    (onceki ? (s.toplam / onceki).toFixed(3) : "-").padStart(8) +
    (s.toplam / N).toFixed(2).padStart(14) + (s.karsilastirma / N).toFixed(2).padStart(17));
  onceki = s.toplam;
}

const a = r["ham yuk, istemci s1|200"], b = r["ham yuk, istemci s1|400"];
const c = r["ham yuk, istemci s2|200"], d = r["sirali yuk, istemci s1|200"], e = r["bicimli yuk|200"];
console.log(`\nayirt eden olcum — kayit basina is: A (sunucu N'i 200 -> 400) ` +
  `${(a.toplam / a.gecen).toFixed(2)} -> ${(b.toplam / b.gecen).toFixed(2)}, ` +
  `B (istemci s1 -> s2, N sabit) ${(a.toplam / a.gecen).toFixed(2)} -> ${(c.toplam / c.gecen).toFixed(2)}`);
console.log(`duzeltme: sirali yukta toplam is ${say(a.toplam)} -> ${say(d.toplam)} ` +
  `(${(a.toplam / d.toplam).toFixed(1)} kat), bicimli yukta ${say(e.toplam)} ` +
  `(${(a.toplam / e.toplam).toFixed(1)} kat); gecen bayt ${say(a.bayt)} -> ${say(d.bayt)} -> ${say(e.bayt)}`);

const UC = 513.89, ISABET = 0.9;                          // K01 tepe uctaki istek/s, K02 onbellek isabeti
console.log(`\nisi sunucuya tasimanin kosulu: onbelleklenebilir is ${(UC * (1 - ISABET)).toFixed(2)} kez/s, ` +
  `onbelleklenemez is ${UC.toFixed(2)} kez/s kosar (oran ${(1 / (1 - ISABET)).toFixed(2)}); ` +
  `istemcide her iki durumda da kullanici basina 1 kez`);
model: bir gonderi 7 durum olayi tasiyor (K01 V4), ekranda 25 satir gorunuyor (KK2); is birimi soyut bir adimdir, sure olculmez

durum                        N  gecen kayit  ayristirma   donusum  karsilastirma   toplam is  kayit basina  gorunen satir basina
ham yuk, istemci s1        200          200       7.800     5.600          3.920      17.395         86.97                 695.8
ham yuk, istemci s1        400          400      15.600    11.200          8.243      35.118         87.80                1404.7
ham yuk, istemci s2        200          200       7.800     8.400          3.920      20.195        100.97                 807.8
sirali yuk, istemci s1     200           25         275       300              0         650         26.00                  26.0
bicimli yuk                200           25         125         0              0         200          8.00                   8.0

olcekleme (ham yuk, istemci s1): N iki katina cikarken toplam is kac kat
     N   toplam is     kat  kayit basina  karsilastirma/N
    50       4.321       -         86.42            17.92
   100       8.649   2.002         86.49            18.74
   200      17.395   2.011         86.97            19.60
   400      35.118   2.019         87.80            20.61
   800      70.963   2.021         88.70            21.61

ayirt eden olcum — kayit basina is: A (sunucu N'i 200 -> 400) 86.97 -> 87.80, B (istemci s1 -> s2, N sabit) 86.97 -> 100.97
duzeltme: sirali yukta toplam is 17.395 -> 650 (26.8 kat), bicimli yukta 200 (87.0 kat); gecen bayt 95.028 -> 3.771 -> 3.775

isi sunucuya tasimanin kosulu: onbelleklenebilir is 51.39 kez/s, onbelleklenemez is 513.89 kez/s kosar (oran 10.00); istemcide her iki durumda da kullanici basina 1 kez

Kayıt Başına İş Nedeni Ayırıyor

Ayırt eden ölçüm kayıt başına iş birimidir: toplam işin, sunucunun gönderdiği kayıt sayısına bölümü. İki neden bu tek sayıda ayrışıyor.

Neden A’da sunucu 200 kayıttan 400’e çıkıyor. Toplam iş 17.395’ten 35.118’e gidiyor — kullanıcı belirtiyi hisseder. Ama kayıt başına iş 86,97’den 87,80’e çıkıyor; yüzde 0,95. İstemci kodu değişmemiştir ve ölçüm bunu söylüyor.

Neden B’de gönderilen kayıt 200’de kalıyor, toplam iş 17.395’ten 20.195’e çıkıyor. Kayıt başına iş 86,97’den 100,97’ye gidiyor; yüzde 16,1. Yük değişmemiştir, kod değişmiştir.

Aynı belirti, aynı yönde büyüyen toplam, ters yönde işaret eden bölüm. Toplam işe bakmak tanı vermiyor; bölmek veriyor.

Ölçekleme tablosu üçüncü bir şey söylüyor. N iki katına çıktığında toplam iş 2,002’den 2,021’e tırmanan bir katsayıyla büyüyor ve kayıt başına iş 86,42’den 88,70’e çıkıyor. Bu, kodun değiştiği anlamına gelmez; karşılaştırma sütunu nedeni veriyor: kayıt başına karşılaştırma 17,92’den 21,61’e çıkıyor. İstemci listeyi kendi sıralıyor ve sıralama kayıt sayısıyla doğrusal büyümüyor. Yani yük büyümesi, kayıt başına işi de bir miktar büyütür — bu payın logaritmik, kod değişikliğinin payının basamaklı olduğu bilinirse iki neden yine ayrılabilir.

İki Düzeltme, Bir Farkla

Belirtinin asıl büyüklüğü son sütunda. Ham yükte görünen satır başına ana iş parçacığı işi 695,8 birim; ekranda 25 satır var ve istemci 200 kaydın hepsini ayrıştırıp biçimlendiriyor, sonra dilimliyor. Bu, karşıt kalıbın kendisidir: iş, gösterilecek olanla değil, gönderilenle ölçekleniyor.

Sunucu sıralamayı ve seçmeyi üstlendiğinde toplam iş 17.395’ten 650’ye iniyor — 26,8 kat. Görünen satır başına iş 695,8’den 26,0’a düşüyor. Sunucu ayrıca biçimlendirmeyi de üstlenirse iş 200’e iniyor; ham yüke göre 87,0 kat, sıralı yüke göre 3,25 kat.

İki düzeltme aynı şey değil ve son satır farkı veriyor. Sıralı yüke geçiş sınırdan geçen baytı 95.028’den 3.771’e indiriyor. Biçimli yüke geçiş baytı 3.771’den 3.775’e çıkarıyor — biçimlendirme sunucuda yapıldığında yanıt küçülmüyor. Yani ikinci adım yalnız iş birimi kazandırıyor, üçüncü bir maliyeti var ve o maliyet bir sonraki bölümdedir.

İşi İstemcide Bırakmanın Doğru Olduğu Koşul

İstemcide iş yapmak bir hata değildir; sunucuya taşımanın da bir fiyatı vardır ve fiyat sayıyla yazılabilir. İstemcide bir iş kullanıcı başına bir kez koşar ve kullanıcının kendi donanımında koşar. Sunucuya taşındığında aynı iş K01’in tepe yükünde saniyede 513,89 kez koşar — tek bir kaynak havuzunda.

Koşulu belirleyen şey işin önbelleklenebilir olmasıdır. K02’nin kenar önbelleği okumaların onda dokuzunu karşılıyor; sunucuya taşınan iş önbelleklenebiliyorsa saniyede 51,39 kez koşar, önbelleklenemiyorsa 513,89 kez. Oran 10,00. Sıralama ile seçme kullanıcıya özel değildir; aynı satıcının aynı listesi herkes için aynıdır, önbelleklenebilir, taşınmalıdır. Biçimlendirme kullanıcının yerel ayarına bağlıdır; taşınırsa yanıt kullanıcıya özelleşir ve önbellek anahtarı o boyut sayısı kadar parçaya bölünür. Aynı isabeti korumak için önbellek o kat kadar büyütülür.

Bu yüzden ölçüm iki düzeltmeyi ayırıyor: 26,8 katlık kazanç alınır, 3,25 katlık kazanç alınmaz. İkincisinin karşılığında büyüyen şey ölçülebilir ve büyük; birincisinin karşılığı yalnız sunucuda 51,39 kez/s koşan bir sıralamadır.

Karşılığında Büyüyen

Sıralamanın ve seçmenin sunucuya taşınmasının üç karşılığı var. Sunucu artık saniyede 51,39 kez sıralama yapıyor — önbellek arkasında, K01’in depoya ulaşan 138,89 istek/s’lik yükünün üstüne biner. İkincisi, hangi 25 kaydın gösterileceği kararı sunucuya geçti; kullanıcı listede ilerledikçe bu karar yeni bir istek doğurur. Üçüncüsü, istemcinin elinde artık tam liste yok: yerel bir süzme ya da yeniden sıralama da sunucuya gitmek zorunda.

Son iki karşılık aynı yöne bakıyor ve bir sonraki dersin konusudur.

Özet

  • Belirti sunucu ölçütlerinde görünmez: yanıt süresi ve bayt sabitken kullanıcıya görünen süre büyür. Aynı belirtiyi yük büyümesi de istemci kodu büyümesi de verir.
  • Ayırt eden ölçüm kayıt başına iş birimidir: yük 200’den 400’e çıkarken 86,97 → 87,80 (%0,95), kod s1’den s2’ye geçerken 86,97 → 100,97 (%16,1).
  • Yük büyümesi kayıt başına işi de bir miktar büyütür ama logaritmik olarak: kayıt başına karşılaştırma 17,92’den 21,61’e çıkıyor, çünkü sıralama istemcide yapılıyor.
  • Karşıt kalıbın ölçüsü görünen satır başına iştir: ham yükte 695,8 birim, oysa ekranda 25 satır var. Sıralama ve seçme sunucuya taşındığında toplam iş 26,8 kat düşüyor.
  • Biçimlendirmenin de taşınması 3,25 kat daha kazandırır ama baytı 3.771’den 3.775’e çıkarır ve yanıtı kullanıcıya özelleştirir; önbellek anahtarı bölünür.
  • Koşul önbelleklenebilirliktir: sunucuya taşınan iş önbelleklenebiliyorsa 51,39 kez/s, değilse 513,89 kez/s koşar (oran 10,00); istemcide her iki durumda da kullanıcı başına bir kez.

Sonraki Adım

Sunucu artık 200 kayıt yerine 25 kayıt gönderiyor ve hangi 25 kaydın gösterileceğine kendisi karar veriyor. Kullanıcı listede ilerledikçe bu karar yenilenmek zorunda: her kaydırma bir istek, her süzme bir istek, satır açıldığında ayrıntı için bir istek daha. Yük küçüldü, çağrı sayısı büyüdü ve bu sayı hiç ölçülmedi. Sonraki ders o sayıyı ele alır: çağrı sayısı ile taşınan bayt arasındaki ödünleşim, sabit çağrı maliyetinin hangi kayıt sayısında taşınan bayta baskın geldiği ve tek büyük çağrının ne zaman çok sayıda küçük çağrıdan daha kötü olduğu.

İ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