İçeriğe geç
academia.sh

Ders 02 / 14

Haber Akışı

Aynı akışın iki dağıtım modeliyle kurulması: yazmada dağıtma ile okumada toplamanın iş miktarının ölçülmesi, takipçi dağılımının ucunun ortalamayla yapılan hesabı yanıltması, karma modelin eşiğinin dağıtım kapasitesi ile görünme penceresinden bir sayı olarak türetilmesi ve dağıtım tüketicisi yavaşladığında pencerenin aşılma anının hesaplanması.

İçindekiler

Önceki vakada okumanın yanıtı tek bir kayıttı: anahtar verilir, hedef döner. Bu vakada okuma oranı benzer kalır ama yanıt değişir — haber akışı çok sayıda kaynaktan derlenmiş sıralı bir listedir. Soru bu yüzden “kaydı nerede önbelleğe alayım” olmaktan çıkar ve şuna döner: liste ne zaman derlenir? İki seçenek vardır — yazmada dağıtma, gönderiyi yayımlandığı anda her takipçinin kutusuna yazar; okumada toplama, akış istendiği anda takip edilen hesapların gönderilerini birleştirir. Bu ders ikisinin iş miktarını ölçer ve karma modelin eşiğini bir sayı olarak verir.

Kısıtlar

İşlevsel gereksinimler. I1: kullanıcı gönderi yayımlar. I2: kullanıcı, takip ettiklerinin gönderilerini zamana göre sıralı olarak ister. I3: kullanıcı takip eder ve takibi bırakır. I4: silinen gönderi akışta görünmez.

Kapsam daraltması. İlgi puanına göre sıralama, ortam dosyaları, bildirim ve arama bu tasarımın dışındadır; sıralama modeli akışın içeriğini değiştirir, dağıtım kararını değil.

Kod Eşik Eşiğin kaynağı
G1 akışın ilk sayfası (20 gönderi) 200 ms’de döner akış açılışında beklenen ilk görüntü
G2 yayımlanan gönderi takipçi akışında 60 saniye içinde görünür akışın canlı sayılması
G3 akış kutusu deposu 20 GB’yi geçmez kutunun bellekte tutulması

Varsayımlar

Kod Varsayım Değer Gerekçe
VH1 günlük etkin kullanıcı 1.000.000 akışı günde en az bir kez açan kullanıcı
VH2 kullanıcı başına günlük gönderi 0,2 çoğu kullanıcı okur, azı yazar
VH3 kullanıcı başına günlük akış isteği 8 akış gün içinde birkaç kez tazelenir
VH4 tepe çarpanı 3 tepe saatteki hızın gün ortalamasına oranı
VH5 takipçi dağılımı taban 33, üs 1,16 takipçi az sayıda hesapta yoğunlaşır; üs kuyruğun kalınlığıdır
VH6 kutuda tutulan girdi 400 yirmi sayfalık geçmiş yeter, gerisi gönderi deposundan gelir
VH7 kutu girdisi 32 bayt gönderi kimliği ve zaman damgası

VH5 bir sayı değil bir dağılımdır; ortalaması modelden çıkar ve bir varsayım olarak seçilmemiştir. Bir hesabın takipçi sayısı kullanıcı sayısını geçemez, model bu tavanı uygular.

Kabaca Büyüklük Hesabı ve İki Modelin İş Miktarı

// akis/hesap.mjs — VH1–VH7'den cikan hesap ve iki dagitim modelinin is miktari.
// Takipci dagilimi kendi yazilmis uretecle uretilir (tohum sabit), tavan kullanici sayisidir. MODELDIR.
const VH = { kullanici: 1_000_000, gonderi: 0.2, akisIstegi: 8, tepe: 3, taban: 33, us: 1.16, kutuGirdi: 400, girdiBayt: 32 };
const GUN = 86_400;
const gonderiTepe = (VH.kullanici * VH.gonderi / GUN) * VH.tepe;
const akisTepe = (VH.kullanici * VH.akisIstegi / GUN) * VH.tepe;

let s = 20260730 % 2147483647;
const rast = () => (s = (s * 48271) % 2147483647) / 2147483647;
const takipci = new Float64Array(VH.kullanici);
let tavanda = 0;
for (let i = 0; i < VH.kullanici; i += 1) {
  const v = Math.round(VH.taban * Math.pow(rast(), -1 / VH.us));
  if (v > VH.kullanici - 1) tavanda += 1;
  takipci[i] = Math.min(VH.kullanici - 1, v);
}
const toplamBag = takipci.reduce((a, b) => a + b, 0), ortalama = toplamBag / VH.kullanici;
const sirali = Float64Array.from(takipci).sort();
const y = (p) => sirali[Math.floor(p * VH.kullanici)];
const ustPay = sirali.slice(VH.kullanici - VH.kullanici / 1000).reduce((a, b) => a + b, 0) / toplamBag;

const b = (x, n = 2) => x.toFixed(n);
console.log(`tepe gonderi/s = ${b(gonderiTepe)}   tepe akis istegi/s = ${b(akisTepe)}   okuma/yazma orani = ${b(akisTepe / gonderiTepe)}`);
console.log(`kutu deposu = ${b(VH.kullanici * VH.kutuGirdi * VH.girdiBayt / 1e9)} GB (kullanici basina ${VH.kutuGirdi} girdi)`);
console.log(`\ntakipci dagilimi: ortalama ${b(ortalama)}  p50 ${y(0.5)}  p90 ${y(0.9)}  p99 ${y(0.99)}  ` +
  `p99,9 ${y(0.999)}  en buyuk ${sirali[VH.kullanici - 1]}  tavandaki hesap ${tavanda}`);
console.log(`en ustteki binde birlik dilim baglantilarin ${b(ustPay, 4)}'ini tutuyor`);

const yazmaIs = gonderiTepe * ortalama, okumaIs = akisTepe * ortalama;
console.log(`\n${"model".padEnd(20)}${"yazma basina is".padStart(17)}${"okuma basina is".padStart(17)}${"tepede is/s".padStart(14)}`);
console.log(`${"yazmada dagitma".padEnd(20)}${b(ortalama).padStart(17)}${"1.00".padStart(17)}${b(yazmaIs + akisTepe).padStart(14)}`);
console.log(`${"okumada toplama".padEnd(20)}${"1.00".padStart(17)}${b(ortalama).padStart(17)}${b(okumaIs + gonderiTepe).padStart(14)}`);
console.log(`oran = ${b((okumaIs + gonderiTepe) / (yazmaIs + akisTepe))}`);

const iki = ((VH.kullanici * VH.akisIstegi * 2 / GUN) * VH.tepe) * ortalama;
console.log(`\nVH3 duyarliligi: gunluk akis istegi 8 -> 16 ise okumada toplama ${b(okumaIs)} -> ${b(iki)} is/s, ` +
  `yazmada dagitma ${b(yazmaIs)} is/s degismiyor`);
tepe gonderi/s = 6.94   tepe akis istegi/s = 277.78   okuma/yazma orani = 40.00
kutu deposu = 12.80 GB (kullanici basina 400 girdi)

takipci dagilimi: ortalama 203.63  p50 60  p90 241  p99 1763  p99,9 12474  en buyuk 999999  tavandaki hesap 7
en ustteki binde birlik dilim baglantilarin 0.2753'ini tutuyor

model                 yazma basina is  okuma basina is   tepede is/s
yazmada dagitma                203.63             1.00       1691.87
okumada toplama                  1.00           203.63      56570.71
oran = 33.44

VH3 duyarliligi: gunluk akis istegi 8 -> 16 ise okumada toplama 56563.76 -> 113127.53 is/s, yazmada dagitma 1414.09 is/s degismiyor

Üç sayı kararı taşıyor. Birincisi oran: aynı akış yazmada dağıtmayla tepede 1691,87, okumada toplamayla 56.570,71 iş/s istiyor — 33,44 kat. Fark okuma/yazma oranının kendisidir; iş, hangi tarafta çoğaltılırsa o tarafın hızıyla çarpılır ve okuma tarafı kırk kat daha sık koşar. İkincisi kutu deposu 12,80 GB, G3’ün 20 GB’lik eşiğinin altında. Üçüncüsü duyarlılık: akış isteği ikiye katlandığında okumada toplama 113.127,53’e çıkarken yazmada dağıtma 1414,09’da kalıyor. Yazmada dağıtma okuma hacminden bağımsızdır; asıl özelliği budur.

Dağılım satırı bir uyarı taşıyor. Ortalama 203,63 ama ortanca 60, yüzde 99,9’luk değer 12.474 ve en büyük hesap tavanda. En üstteki binde birlik dilim bağlantıların 0,2753’ünü tutuyor. Ortalamayla yapılan hesap kararlı hızı doğru veriyor, ama tek bir gönderinin ne istediğini hiç göstermiyor.

Karma Modelin Eşiği

// akis/karma.mjs — karma modelin esigi: esigin ustundeki hesaplar okuma aninda toplanir.
// Ayni dagilim ureteci ve ayni tohum; takip olasiligi populerlikle orantili kabul edilir. MODELDIR.
const VH = { kullanici: 1_000_000, gonderi: 0.2, akisIstegi: 8, tepe: 3, taban: 33, us: 1.16 };
const GUN = 86_400, PENCERE = 60;                       // PENCERE: G2 esigi
const gonderiTepe = (VH.kullanici * VH.gonderi / GUN) * VH.tepe;
const akisTepe = (VH.kullanici * VH.akisIstegi / GUN) * VH.tepe;

let s = 20260730 % 2147483647;
const rast = () => (s = (s * 48271) % 2147483647) / 2147483647;
const takipci = new Float64Array(VH.kullanici);
for (let i = 0; i < VH.kullanici; i += 1)
  takipci[i] = Math.min(VH.kullanici - 1, Math.round(VH.taban * Math.pow(rast(), -1 / VH.us)));
const sirali = Float64Array.from(takipci).sort();
const ortalama = sirali.reduce((a, b) => a + b, 0) / VH.kullanici;

// Esigin altinda kalan baglantilar yazmada dagitilir, ustundekiler okumada toplanir.
function is(esik) {
  let alt = 0, ust = 0, enBuyukAlt = 0, ustHesap = 0;
  for (const f of sirali) {
    if (f <= esik) { alt += f; enBuyukAlt = f; } else { ust += f; ustHesap += 1; }
  }
  return { yazma: gonderiTepe * (alt / VH.kullanici), okuma: akisTepe * (ust / VH.kullanici), enBuyukAlt, ustHesap };
}

const b = (x, n = 2) => x.toFixed(n);
const tamYazma = is(VH.kullanici), KAPASITE = 3 * tamYazma.yazma;    // kapasite kararli hizin uc kati kurulur
const ESIK = Math.round(PENCERE * (KAPASITE - tamYazma.yazma));      // tek gonderiye kalan pay penceredeki paydir
console.log(`kararli yazma hizi = ${b(tamYazma.yazma)} kutu yazmasi/s, kapasite ${b(KAPASITE)}/s, ` +
  `tek gonderiye kalan ${b(KAPASITE - tamYazma.yazma)}/s -> ${PENCERE} s penceresinde esik = ${ESIK.toLocaleString("tr-TR")} takipci`);
console.log(`\n${"esik".padStart(9)}${"ustteki hesap".padStart(15)}${"yazma is/s".padStart(13)}${"okuma is/s".padStart(13)}` +
  `${"toplam is/s".padStart(14)}${"en buyuk patlama/s".padStart(20)}`);
for (const e of [1000, 10_000, 84_846, ESIK, VH.kullanici]) {
  const r = is(e);
  console.log(`${e.toLocaleString("tr-TR").padStart(9)}${String(r.ustHesap).padStart(15)}${b(r.yazma).padStart(13)}` +
    `${b(r.okuma).padStart(13)}${b(r.yazma + r.okuma).padStart(14)}${b(r.enBuyukAlt / PENCERE).padStart(20)}`);
}

const secilen = is(ESIK);
console.log(`\nsecilen esikte: ustteki ${secilen.ustHesap} hesap okuma aninda toplaniyor, ` +
  `akis istegi basina ${b(secilen.okuma / akisTepe, 3)} ek kaynak, toplam is ${b(secilen.yazma + secilen.okuma)}/s`);
console.log(`esiksiz tasarimda en buyuk tek gonderi ${b(sirali[VH.kullanici - 1] / PENCERE)} kutu yazmasi/s istiyor = ` +
  `kararli hizin ${b(sirali[VH.kullanici - 1] / PENCERE / tamYazma.yazma)} kati`);

// Dagitim tuketicisi yavaslarsa: birikim / kapasite, G2 penceresini ne zaman asar.
console.log(`\n${"tuketici kapasitesi".padEnd(21)}${"birikim/s".padStart(11)}${"G2'yi asma ani".padStart(16)}`);
for (const k of [1200, 1000, 700]) {
  const birikim = secilen.yazma - k;
  console.log(`${`${k} kutu yazmasi/s`.padEnd(21)}${b(birikim).padStart(11)}${`${b(PENCERE * k / birikim)} s`.padStart(16)}`);
}
console.log(`ortalama takipci ${b(ortalama)}; esigin ustundeki baglanti payi ${b(secilen.okuma / akisTepe / ortalama, 4)}`);
kararli yazma hizi = 1414.09 kutu yazmasi/s, kapasite 4242.28/s, tek gonderiye kalan 2828.19/s -> 60 s penceresinde esik = 169.691 takipci

     esik  ustteki hesap   yazma is/s   okuma is/s   toplam is/s  en buyuk patlama/s
    1.000          19203       700.05     28561.87      29261.92               16.67
   10.000           1308      1000.84     16530.11      17530.95              166.52
   84.846            115      1193.70      8815.60      10009.30             1393.05
  169.691             52      1247.71      6655.38       7903.09             2820.88
1.000.000              0      1414.09         0.00       1414.09            16666.65

secilen esikte: ustteki 52 hesap okuma aninda toplaniyor, akis istegi basina 23.959 ek kaynak, toplam is 7903.09/s
esiksiz tasarimda en buyuk tek gonderi 16666.65 kutu yazmasi/s istiyor = kararli hizin 11.79 kati

tuketici kapasitesi    birikim/s  G2'yi asma ani
1200 kutu yazmasi/s        47.71       1509.13 s
1000 kutu yazmasi/s       247.71        242.22 s
700 kutu yazmasi/s        547.71         76.68 s
ortalama takipci 203.63; esigin ustundeki baglanti payi 0.1177

Tablonun son satırı ilk bakışta tartışmayı bitiriyor: eşiksiz yazmada dağıtma toplam işi 1414,09/s ile en aza indiriyor ve her eşik bu sayıyı büyütüyor. Eşiği 1000’e çekmek toplam işi 29.261,92’ye, yirmi katına yakın çıkarıyor. Nedeni değişmiyor — eşiğin üstüne taşınan her bağlantı, kırk kat daha sık koşan tarafta yeniden sayılıyor.

Eşiği koyan şey toplam iş değil, son sütundur. Eşiksiz tasarımda en büyük hesabın tek bir gönderisi, 60 saniyelik G2 penceresi içinde saniyede 16.666,65 kutu yazması istiyor: kararlı hızın 11,79 katı. Bu talep ortalamayla yapılan hesapta görünmez, çünkü ortalama 203,63’tür ve tek bir kaydın büyüklüğü hakkında hiçbir şey söylemez.

Eşik buradan hesaplanır. Dağıtım kapasitesi kararlı hızın üç katına kurulursa (4242,28/s) tek bir gönderiye 2828,19 yazma/s kalır; 60 saniyelik pencerede bu 169.691 takipçi eder. Eşik budur ve iki girdisi vardır: pencere ve kapasite. Seçilen eşikte 52 hesap okuma tarafına geçiyor, akış isteği başına 23,959 ek kaynak okunuyor ve toplam iş 7903,09/s oluyor — eşiksiz tasarımın 5,59 katı. Bu, tek bir gönderinin bütün dağıtım kapasitesini kaçırmasını önlemek için ödenen bedeldir.

Eşiğin üstündeki 52 hesap bağlantıların yalnızca 0,1177’sini tutuyor. Karma modelin çalışmasının nedeni budur: kuyruğun ucundaki elli iki hesap bağlantıların yalnızca bu payını taşıyor ama patlamanın tamamını üretiyor.

Tasarım

  • Somutlaştırılmış görünüm (Veri Katmanı Ölçekleme, Somutlaştırılmış Görünümler). Akış kutusu bir izdüşümdür; parametre kutu başına 400 girdi ve girdi başına 32 bayttır, toplam 12,80 GB.
  • Komut ve sorgu sorumluluğu ayrımı (Veri Katmanı Ölçekleme, Komut ve Sorgu Ayrımı). Yazma yolu gönderi deposuna, okuma yolu kutuya bakar; parametre, kutunun gönderi metnini değil kimliğini tutmasıdır.
  • Mesaj kuyruğu ve rekabet eden tüketiciler (Uygulama Katmanı, Mesaj Kuyrukları; Rekabet Eden Tüketiciler). Dağıtım işleri kuyruğa düşer; parametre kapasitedir: kararlı 1414,09/s’nin üç katı, 4242,28 kutu yazması/s.
  • Parçalama (Veri Dağıtımı, Parçalama). Kutu deposu kullanıcı kimliğine göre parçalanır; parametre, akış isteğinin tek düğüme dokunmasıdır — dağıt–topla yalnız eşiğin üstündeki 52 hesap için oluşur.
  • Geri basınç (Dayanıklılık ve Güvenilirlik, Geri Basınç). Kuyruk birikince yayımlama yoluna sinyal gider; parametre, G2’nin 60 saniyelik penceresidir.

Bilerek kullanılmayan iki kalıp. Kenar önbelleği (Trafik Katmanı, İçerik Dağıtım Ağları) kullanılmıyor: akış kişiye özeldir, iki kullanıcı aynı yanıtı almaz ve paylaşılan önbellek isabet üretmez. Sıralı konvoy (Uygulama Katmanı, Sıralı Konvoy) kullanılmıyor: dağıtım işlerinin sırası önemsizdir, çünkü akış kutuda zaman damgasına göre sıralanır.

Elenen Alternatif: Saf Okumada Toplama

Alternatif tasarım kutuyu hiç kurmaz; akış istendiğinde takip edilen hesapların son gönderileri birleştirilir. Kazandığı iki şey ölçüldü: kutu deposu 12,80 GB’den sıfıra iner ve G2 penceresi ortadan kalkar — gönderi yayımlandığı anda görünür, çünkü görünmesi için taşınması gerekmez. Takip bırakma da anında yansır.

Eleme sayısı iş miktarındadır: tepede 56.570,71 iş/s, yazmada dağıtmanın 33,44 katı. Dahası bu sayı okuma hacmine bağlıdır; VH3 ikiye katlandığında 113.127,53’e çıkarken yazmada dağıtma 1414,09’da kalır. Alternatif hangi kısıt değişirse kazanır: okuma/yazma oranı 40’tan 1’e yaklaşırsa iki modelin işi eşitlenir ve kutu tutmanın gerekçesi kalmaz. Bu, gönderi başına takipçi sayısının değil, akış isteğinin gönderiye oranının kararı belirlediği anlamına gelir.

Arıza Davranışı ve Feda Edilen

Dağıtım tüketicisi yavaşlarsa (Dayanıklılık ve Güvenilirlik, Arıza Kipleri) kuyruk birikir ve görünme gecikmesi büyür. Tablo aşılma anını veriyor: kapasite 1200 kutu yazması/s’ye düşerse birikim saniyede 47,71 olur ve G2’nin 60 saniyelik penceresi 1509,13 saniyede aşılır; 1000’e düşerse 242,22 saniyede, 700’e düşerse 76,68 saniyede. Kısmi yavaşlama akışı durdurmaz, geciktirir; bu yüzden arıza belirtisi hata değil, tazeliğin kaymasıdır.

Kutu deposunun bir parçası düşerse o parçadaki kullanıcıların akışı boş döner. Zarif bozulma (Dayanıklılık ve Güvenilirlik, Zarif Bozulma) burada okumada toplamaya geçmektir: eşiğin üstündeki 52 hesap için zaten kurulmuş olan yol, düşen parçadaki kullanıcılar için geçici olarak bütün takip edilenlere açılır — pahalıdır ama boş akıştan iyidir.

Feda edilen yazma amplifikasyonudur. Her gönderi ortalama 203,63 kutuya yazılıyor ve bu iş, okuma hiç gelmese bile yapılıyor. Tasarım okumayı ucuzlatmak için yazmayı iki yüz katına yakın çoğalttı; kararın tamamı okuma/yazma oranının 40 olmasına dayanıyor.

Özet

  • Aynı akış yazmada dağıtmayla tepede 1691,87, okumada toplamayla 56.570,71 iş/s istiyor: 33,44 kat. Fark okuma/yazma oranının kendisidir.
  • Yazmada dağıtma okuma hacminden bağımsız: akış isteği ikiye katlandığında okumada toplama 113.127,53’e çıkarken yazmada dağıtma 1414,09’da kalıyor.
  • Takipçi dağılımının ortalaması 203,63 ama ortancası 60, p99,9 değeri 12.474; en üstteki binde birlik dilim bağlantıların 0,2753’ünü tutuyor.
  • Eşiği toplam iş koymuyor — eşiksiz tasarım 1414,09/s ile en ucuzu. Eşiği koyan, en büyük hesabın tek gönderisinin 60 saniyelik pencerede istediği 16.666,65 yazma/s, yani kararlı hızın 11,79 katı.
  • Eşik pencere ile kapasiteden hesaplanıyor: kapasite kararlı hızın üç katıysa eşik 169.691 takipçi, 52 hesap okuma tarafına geçiyor ve toplam iş 7903,09/s oluyor.
  • Tüketici kapasitesi 1200/1000/700’e düştüğünde G2 penceresi 1509,13 / 242,22 / 76,68 saniyede aşılıyor; feda edilen, gönderi başına 203,63 kutu yazmasıdır.

Sonraki Adım

Bu vakada okumanın maliyeti derlemeydi ve derleme yazma anına kaydırılarak ucuzlatıldı. Kaydırmayı mümkün kılan bir özellik vardı: yanıtın kimin için hazırlanacağı yazma anında biliniyordu, çünkü takipçi listesi yazıldığı anda bellidir. Bir sonraki vakada bu özellik kaybolur. Kullanıcı bir metin yazmaya başlar ve her tuş vuruşunda bir öneri listesi ister; sorgunun ne olacağı önceden bilinmez ve olası sorgu sayısı bir kullanıcı listesi gibi sonlu değildir. Yanıt yazma anında hazırlanamıyorsa okuma anında hazırlanmak zorundadır — ve bu kez okuma eşiği 200 milisaniye değil, tuş vuruşları arasına sığacak kadar küçüktür. Soru, hangi veri yapısının bu eşiği tutabildiğine ve tazeliğin ne kadarının feda edileceğine döner.

İ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