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.