Ders 06 / 15
Mesaj Kuyrukları
Kuyruğun bir kapasite kararı olarak ele alınması: eşzamanlı yazma yolunda kapasitenin anlık tepeye göre seçilmesi, kuyruk konduğunda işleme kapasitesinin ortalamaya yaklaştırılması, kazancın birikme ve bekleme cinsinden ödenmesi, deponun kısa bir kesintisinde iki tasarımın kayıp sayısıyla karşılaştırılması ve depoya ulaşan istek hızının yeniden hesaplanması.
İçindekiler
Önceki konu eşzamanlı çağrıyı tasarladı: çağıran bekler, bir zaman aşımı bütçesi işler, sürenin sonunda ya bir yanıt gelir ya bir hata. Konunun son dersi o bütçenin aritmetiğini çıkardı ve bir sınırı görünür kıldı — zincir uzadıkça ya bütçe parçalara bölünür ve her adım daralır, ya da uçtan uca süre büyür. İki seçenek de aynı varsayımın içindedir: çağıran işin sonucunu bekliyor. Bazı işler için üçüncü bir yol vardır ve o yol varsayımı kaldırır — çağıran hiç beklemez. Taşıyıcıdan gelen durum olayının deponun içine yazılması, isteği yanıtlamak için tamamlanmak zorunda değildir.
Bu konu o yolu ele alır. Mesaj kuyruğu (message queue) mekaniği Önbellekleme, Kuyruklar ve Eşzamansız İşleme kursunda kuruldu ve ölçüldü: üreticinin yazması, tüketicinin alması, onay, görünmezlik süresi, teslim garantileri, tüketici grupları, sıralama ve ölü mektup kuyruğu. Burada hiçbiri yeniden anlatılmaz. Bu kursun sorusu başkadır ve tek cümleyle söylenir: kuyruk bir kapasite kararıdır. Yük zamana yayıldığında ne kazanılır, ne ödenir ve giriş kursunun hangi sayısı değişir.
Kapasite Hangi Sayıya Göre Seçilir
Eşzamanlı yazma yolunda bir kural vardır ve tartışılmaz: kapasite anlık tepe hızına göre seçilir. Taşıyıcı bir durum olayı gönderip yanıt bekliyorsa, o anda gelen her isteğin işlenecek bir yeri olmalıdır; olmayan istek reddedilir. Sistem Tasarımına Giriş kursunun Kabaca Büyüklük Hesabı dersi bu sayıyı veriyor: tepe yazma 97,22 istek/s — hesap sınıfı, günde 2.800.000 durum olayından, gün ortalaması 32,41 istek/s ve V8 tepe çarpanı 3’ten çıkıyor.
Kuyruk bu kuralı ikiye böler. Taşıyıcının konuştuğu uç yine tepeyi kabul etmek zorundadır, ama orada yaptığı iş tek bir ekleme işlemine iner: olayı kuyruğa yaz, onayı dön. Olayın asıl işlenmesi — gönderi kaydının güncellenmesi, rota adımının yazılması, ücretlendirme satırının hazırlanması — kuyruğun arkasına geçer ve orada kapasite seçilebilir hale gelir. Seçim özgürlüğü kuyruğun kazancıdır; sınırı da kuyruğun kendisidir, çünkü tüketilmeyen her olay birikir.
Birikmeyi hesaplamak için K01’in tablosunda olmayan bir sayı gerekir. V8 tepenin ne kadar yüksek olduğunu söylüyor, ne kadar sürdüğünü söylemiyor.
K1 — tepe penceresi: 3 saat/gün. Bu kursun kendi varsayımıdır ve K01’in tablosuna eklenmez. Gerekçe: durum olaylarını taşıyıcıların dağıtım turu üretir ve tur günün belirli bir dilimine sıkışır. Günlük toplam K01’den geldiği için tepe dışı hız artık serbest değildir: üç saat 97,22 istek/s ile 1.050.000 olay üretir, kalan 1.750.000 olay yirmi bir saate yayılır ve tepe dışı hız 23,15 istek/s olur. Duyarlılığı aşağıdaki ikinci tabloda ölçülüyor.
Birikme
Aşağıdaki düzenek kuyruğu, üreticiyi ve tüketiciyi süreç içi birer modül olarak kurar. Tur bir saniyedir; bir günü saniye saniye yürütür ve seçilen işleme kapasitesine göre birikmeyi izler. Bu bir modeldir, ölçüm değildir: içindeki bütün sayılar K01’den ve K1’den aritmetikle çıkar, sonuçlar belirlenimlidir ve makineden bağımsızdır — yani hesap sınıfındadır. Başlangıç birikmesi dönemsel olsun diye dört gün koşulur ve sayılar üçüncü günden alınır.
// kuyruk/yayma.mjs — gunluk olay profilinin sabit kapasiteli bir tuketiciye verilmesi. // MODEL: kuyruk ve tuketici surec ici birer modul, tur bir saniyedir. Sonuclar belirlenimlidir // (hesap sinifi) ve makineden bagimsizdir; olculmus sure yoktur. const GUN = 86_400; const GUNLUK_OLAY = 2_800_000; // K01 Kabaca Buyukluk Hesabi: gunluk durum olayi const TEPE_CARPANI = 3; // K01 varsayim V8 const TEPE_BAS = 9 * 3600; // tepe penceresinin gun icindeki yeri const ESIK = 300; // "geciken olay" esigi (s) function profil(tepeSaat) { const ortalama = GUNLUK_OLAY / GUN; const tepe = ortalama * TEPE_CARPANI; const tepeSn = tepeSaat * 3600; const disi = (GUNLUK_OLAY - tepe * tepeSn) / (GUN - tepeSn); return { ortalama, tepe, disi, varis: (t) => (t >= TEPE_BAS && t < TEPE_BAS + tepeSn ? tepe : disi) }; } function kosum(kapasite, p) { // dort gun kosar; ilk iki gun baslangic birikmesini let birikme = 0; // donemsel hale getirir, sayilar ucuncu gunden alinir let enYuksek = 0, toplam = 0, geciken = 0, bosalma = -1; const OLC = 2 * GUN; // olcum gununun baslangici for (let t = 0; t < 4 * GUN; t += 1) { const gelen = p.varis(t % GUN); if (t >= OLC && t < OLC + GUN) { if (birikme / kapasite > ESIK) geciken += gelen; toplam += gelen; } birikme += gelen; birikme -= Math.min(birikme, kapasite); if (t >= OLC && t < OLC + GUN && birikme > enYuksek) enYuksek = birikme; if (bosalma < 0 && t > OLC + TEPE_BAS && birikme < 1) bosalma = t - OLC - TEPE_BAS; } return { enYuksek, bosalma, geciken, oran: geciken / toplam, bekleme: enYuksek / kapasite }; } function tablo(tepeSaat) { const p = profil(tepeSaat); console.log(`K1 = ${tepeSaat} saat tepe penceresi -> tepe ${p.tepe.toFixed(2)}/s, ` + `tepe disi ${p.disi.toFixed(2)}/s, ortalama ${p.ortalama.toFixed(2)}/s`); console.log("kapasite tepeye oran en yuksek birikme en uzun bekleme bosalma geciken olay geciken oran"); for (const oran of [1, 2 / 3, 0.5, 0.4, 0.35, 1 / 3]) { const k = p.tepe * oran; const r = kosum(k, p); const b = r.bekleme < 3600 ? `${(r.bekleme / 60).toFixed(1)} dk` : `${(r.bekleme / 3600).toFixed(2)} sa`; const s = r.bosalma < 0 ? "yok" : `${(r.bosalma / 3600).toFixed(2)} sa`; console.log(`${k.toFixed(2).padStart(8)} ${oran.toFixed(2).padStart(11)} ` + `${r.enYuksek.toFixed(0).padStart(17)} ${b.padStart(15)} ${s.padStart(7)} ` + `${r.geciken.toFixed(0).padStart(12)} ${r.oran.toFixed(3).padStart(11)}`); } } console.log(`gunde ${GUNLUK_OLAY} durum olayi (K01), tepe carpani ${TEPE_CARPANI} (V8)`); console.log(`"geciken olay" = ${ESIK} saniyeden uzun bekleyen olay\n`); tablo(3); console.log(); tablo(6);
gunde 2800000 durum olayi (K01), tepe carpani 3 (V8) "geciken olay" = 300 saniyeden uzun bekleyen olay K1 = 3 saat tepe penceresi -> tepe 97.22/s, tepe disi 23.15/s, ortalama 32.41/s kapasite tepeye oran en yuksek birikme en uzun bekleme bosalma geciken olay geciken oran 97.22 1.00 0 0.0 dk 0.00 sa 0 0.000 64.81 0.67 350000 1.50 sa 5.33 sa 1175324 0.420 48.61 0.50 525000 3.00 sa 8.73 sa 1484861 0.530 38.89 0.40 630000 4.50 sa 14.12 sa 1939884 0.693 34.03 0.35 682500 5.57 sa 20.43 sa 2464667 0.880 32.41 0.33 700000 6.00 sa 24.00 sa 2761134 0.986 K1 = 6 saat tepe penceresi -> tepe 97.22/s, tepe disi 10.80/s, ortalama 32.41/s kapasite tepeye oran en yuksek birikme en uzun bekleme bosalma geciken olay geciken oran 97.22 1.00 0 0.0 dk 0.00 sa 0 0.000 64.81 0.67 700000 3.00 sa 9.60 sa 2177789 0.778 48.61 0.50 1050000 6.00 sa 13.71 sa 2366670 0.845 38.89 0.40 1260000 9.00 sa 18.46 sa 2560693 0.915 34.03 0.35 1365000 11.14 sa 22.33 sa 2714390 0.969 32.41 0.33 1400000 12.00 sa 24.00 sa 2780556 0.993
Ne Kazanıldı, Ne Ödendi
Birinci tablonun ilk satırı eşzamanlı tasarımın kendisidir: kapasite tepeye eşitse birikme sıfır, bekleme sıfır. Aşağı inen her satır kapasiteden vazgeçip bekleme satın alıyor.
Kazanç doğrusal. Kapasite tepenin yarısına indiğinde işleme tarafı yarı yarıya küçülür; üçte birine indiğinde üçte birine iner. Alt sınır tesadüf değildir: tepenin üçte biri gün ortalamasıdır ve V8’in tanımı gereği tepe ortalamanın üç katıdır. Kuyruğun kapasite kazancının üst sınırı tepe çarpanının kendisidir — tepeye göre değil ortalamaya göre boyutlanmak, V8 kadar kazanç demektir ve daha fazlası yoktur.
Bedel doğrusal değil. Kapasite 1,00’den 0,67’ye inerken en yüksek birikme 0’dan 350.000’e çıkıyor; 0,50’ye inerken 525.000’e, 0,40’a inerken 630.000’e. Kapasitenin son yüzde on yediyi vermesi birikmeye 105.000 eklerken, ilk yüzde otuz üç 350.000 ekledi — ama beklemeye bakıldığında sıra tersine döner: en uzun bekleme 1,50 saatten 3,00, sonra 4,50 saate çıkıyor. Bekleme, birikmenin kapasiteye bölümü olduğu için pay büyürken payda küçülüyor ve iki etki çarpışıyor. Ortalamaya en yakın satırda bekleme altı saattir.
Boşalma sütunu bir sınır çiziyor. Kapasite 0,35 katında birikme tepeden 20,43 saat sonra sıfırlanıyor; 0,33 katında tam 24,00 saat sürüyor, yani ertesi günün tepesi başladığı anda sıfırlanıyor. Bu bir bıçak sırtıdır: ortalamaya eşit kapasite seçen bir tasarımda tek bir yoğun gün, ertesi güne devreden bir birikme bırakır ve birikme günden güne büyür. Ortalamaya göre boyutlanmak kuramsal sınırdır, seçilebilir bir tasarım değildir.
Geciken olay sütunu bedelin kimi etkilediğini söylüyor. Beş dakikadan uzun bekleyen olay sayısı 0,67 katında 1.175.324, yani günün olaylarının yüzde 42’si. Yarım kapasitede yüzde 53’e, 0,40 katında yüzde 69’a çıkıyor. Bu sayı doğrudan takip sorgusunun gördüğü tazeliktir: taşıyıcı “dağıtıma çıktı” olayını gönderdiğinde alıcının takip sayfası onu ancak birikme boşaldığında gösterir.
İkinci tablo K1’in duyarlılığıdır. Tepe penceresi üç saatten altı saate çıktığında tepe hızı değişmiyor — o sayı V8’den geliyor — ama tepe dışı hız 23,15’ten 10,80 istek/s’ye iniyor ve birikmeler tam iki katına çıkıyor: 0,50 katında 525.000 yerine 1.050.000, bekleme üç saat yerine altı saat. Varsayımın tasarıma etkisi doğrudandır ve tek yönlüdür: tepenin süresi, tepenin yüksekliği kadar belirleyicidir ve K01’in tablosu bu bilgiyi taşımıyordu.
Kesinti Karşısında İki Tasarım
Kapasite kararı tabloya bakarak kuyruk aleyhine sonuçlanabilir. Eksik olan bir ölçü var: kuyruk yalnız yükü zamana yaymaz, üreticiyi tüketicinin arızasından da ayırır. İkinci düzenek deponun tepe penceresinin ortasında on beş dakika yazma kabul etmediği durumu ekler. Kesinti bir parametredir.
// kuyruk/kesinti.mjs — deponun 15 dakika yazma kabul etmedigi durumda iki tasarimin // karsilastirilmasi. MODEL: tur bir saniyedir, kesinti parametreyle tetiklenir. // Sonuclar belirlenimlidir (hesap sinifi). const GUN = 86_400; const GUNLUK_OLAY = 2_800_000; // K01: gunluk durum olayi const TEPE = (GUNLUK_OLAY / GUN) * 3; // K01 hesabi + V8: tepe yazma /s const TEPE_BAS = 9 * 3600, TEPE_SN = 3 * 3600; // K1: 3 saatlik tepe penceresi const DISI = (GUNLUK_OLAY - TEPE * TEPE_SN) / (GUN - TEPE_SN); const KESINTI_BAS = 10 * 3600, KESINTI_SN = 900; // kesinti tepe penceresinin ortasinda const varis = (t) => (t >= TEPE_BAS && t < TEPE_BAS + TEPE_SN ? TEPE : DISI); const kesik = (t) => t >= KESINTI_BAS && t < KESINTI_BAS + KESINTI_SN; function kosum(kapasite, kuyrukVar, kesintiVar) { let birikme = 0, kayip = 0, enYuksek = 0, bosalma = -1; for (let t = 0; t < 2 * GUN; t += 1) { const gun = t % GUN; const gelen = varis(gun); const kap = kesintiVar && kesik(gun) ? 0 : kapasite; if (kuyrukVar) birikme += gelen; else if (gelen > kap) { kayip += (gelen - kap) * (t >= GUN ? 1 : 0); } birikme -= Math.min(birikme, kap); if (t >= GUN) { if (birikme > enYuksek) enYuksek = birikme; if (bosalma < 0 && gun > TEPE_BAS && birikme < 1) bosalma = gun - TEPE_BAS; } } return { kayip, enYuksek, bosalma, bekleme: enYuksek / kapasite }; } const bicim = (r, kapasite) => `${kapasite.toFixed(2).padStart(8)} ` + `${r.kayip.toFixed(0).padStart(15)} ${r.enYuksek.toFixed(0).padStart(17)} ` + `${(r.bekleme / 3600).toFixed(2).padStart(13)} ` + `${(r.bosalma < 0 ? NaN : r.bosalma / 3600).toFixed(2).padStart(13)}`; console.log(`kesinti: ${KESINTI_SN} s, tepe penceresinin icinde; o sirada gelen olay ` + `${(TEPE * KESINTI_SN).toFixed(0)}\n`); console.log("tasarim kapasite kaybedilen olay en yuksek birikme bekleme (sa) bosalma (sa)"); console.log(`${"eszamanli yazma".padEnd(22)} ${bicim(kosum(TEPE, false, false), TEPE)}`); console.log(`${"eszamanli + kesinti".padEnd(22)} ${bicim(kosum(TEPE, false, true), TEPE)}`); for (const oran of [0.5, 0.4]) { const k = TEPE * oran; console.log(`${`kuyruk ${oran.toFixed(2)}x tepe`.padEnd(22)} ${bicim(kosum(k, true, false), k)}`); console.log(`${`kuyruk ${oran.toFixed(2)}x + kesinti`.padEnd(22)} ${bicim(kosum(k, true, true), k)}`); } const ONBELLEK_ARKASI = 41.67; // K01: onbellek arkasi okuma/s const DEPO_TABAN = 138.89; // K01: depoya ulasan istek/s console.log(`\nK01 donusu: depoya ulasan istek/s, taban ${DEPO_TABAN} (okuma ${ONBELLEK_ARKASI} + yazma ${TEPE.toFixed(2)})`); for (const oran of [1, 0.5, 0.4]) { const k = TEPE * oran, depo = ONBELLEK_ARKASI + k; console.log(` isleme kapasitesi ${k.toFixed(2).padStart(6)}/s -> depoya ulasan ${depo.toFixed(2).padStart(6)}/s, ` + `tabanin ${(depo / DEPO_TABAN).toFixed(2)} kati, depoda yazma/okuma ${(k / ONBELLEK_ARKASI).toFixed(2)}`); }
kesinti: 900 s, tepe penceresinin icinde; o sirada gelen olay 87500 tasarim kapasite kaybedilen olay en yuksek birikme bekleme (sa) bosalma (sa) eszamanli yazma 97.22 0 0 0.00 0.00 eszamanli + kesinti 97.22 87500 0 0.00 0.00 kuyruk 0.50x tepe 48.61 0 525000 3.00 8.73 kuyruk 0.50x + kesinti 48.61 0 568750 3.25 9.20 kuyruk 0.40x tepe 38.89 0 630000 4.50 14.12 kuyruk 0.40x + kesinti 38.89 0 665000 4.75 14.73 K01 donusu: depoya ulasan istek/s, taban 138.89 (okuma 41.67 + yazma 97.22) isleme kapasitesi 97.22/s -> depoya ulasan 138.89/s, tabanin 1.00 kati, depoda yazma/okuma 2.33 isleme kapasitesi 48.61/s -> depoya ulasan 90.28/s, tabanin 0.65 kati, depoda yazma/okuma 1.17 isleme kapasitesi 38.89/s -> depoya ulasan 80.56/s, tabanin 0.58 kati, depoda yazma/okuma 0.93
On beş dakikalık kesinti eşzamanlı tasarımda 87.500 olay düşürüyor. Sayı doğrudan aritmetiktir: 900 saniye × 97,22 istek/s. Taşıyıcı yeniden denese bile o denemeler ayrı bir tasarım kararıdır ve Dayanıklılık ve Güvenilirlik kursunda ele alındı; burada ölçülen, tasarımın kendi başına kaybettiğidir.
Aynı kesinti kuyruklu tasarımda sıfır olay düşürüyor. Ödenen şey birikmeye eklenen 43.750 olay ve beklemeye eklenen on beş dakikadır: yarım kapasitede en yüksek birikme 525.000’den 568.750’ye, bekleme 3,00 saatten 3,25 saate, boşalma 8,73 saatten 9,20 saate çıkıyor. Bu bir kıyaslama değil bir dönüştürmedir: kuyruk kaybı gecikmeye çevirir. Kaybın kabul edilemez olduğu ve gecikmenin taşınabildiği akışlarda kazanç, tersi durumda zarar.
Hesaba Geri Dönüş
Son üç satır K01’in bir hesabını yeniden yazıyor. Kabaca Büyüklük Hesabı dersi depoya ulaşan tepe istek hızını 138,89 istek/s bulmuştu: önbelleğin arkasındaki 41,67 okuma artı 97,22 yazma. O toplamdaki yazma payı eşzamanlı tasarımın varsayımıydı — her olay geldiği anda depoya yazılıyor.
Kuyruk yazma payını seçilebilir yapıyor. İşleme kapasitesi tepenin yarısında seçildiğinde depoya ulaşan istek hızı 90,28 istek/s’ye iniyor, tabanın 0,65 katı; 0,40 katında 80,56 istek/s, tabanın 0,58 katı. Depodaki yazma/okuma oranı da değişiyor: K01’in 2,33 değeri sırasıyla 1,17 ve 0,93 oluyor. K01, deponun gördüğü yerde sistemin yazma ağırlıklı olduğunu bulmuştu; kuyruk bu dengeyi tersine çevirebilecek bir karardır ve deponun boyutlandırılması bu yeni sayıdan başlar.
Karar tek satıra iner. Kuyruk üç koşul birlikte sağlandığında kapasite kazandırır: tepe ile ortalama arasında gerçek bir fark vardır (V8 birden büyüktür), çağıranın sonucu beklemesi gerekmez, ve işin etkisinin gecikmesi taşınabilir. Üçüncüsü en sık atlanandır; tablodaki “geciken olay” sütunu onun bedelidir.
Özet
- Eşzamanlı yazma yolunda kapasite anlık tepeye göre seçilir; K01’in tepe yazma hızı 97,22 istek/s bu tasarımın gereksinimidir.
- Kuyruk kapasite kararını ikiye böler: kabul yolu tepeyi almak zorundadır, işleme kapasitesi seçilebilir; kazancın üst sınırı tepe çarpanı V8’dir, yani üç kat.
- Kazanç doğrusal, bedel değil: kapasite tepenin 0,67 / 0,50 / 0,40 katına indiğinde en yüksek birikme 350.000 / 525.000 / 630.000, en uzun bekleme 1,50 / 3,00 / 4,50 saat oluyor.
- Ortalamaya eşit kapasite bir bıçak sırtıdır: birikme tam 24,00 saatte boşalıyor ve olayların yüzde 98,6’sı beş dakikadan uzun bekliyor.
- K1 varsayımı duyarlıdır: tepe penceresi üç saatten altı saate çıktığında bütün birikmeler ve beklemeler iki katına çıkıyor, tepe hızı ise değişmiyor.
- Kuyruk kaybı gecikmeye çevirir: on beş dakikalık depo kesintisi eşzamanlı tasarımda 87.500 olay düşürürken kuyruklu tasarımda sıfır olay düşürüyor, karşılığında bekleme 3,00 saatten 3,25 saate çıkıyor.
Sonraki Adım
Bu dersin bütün olayları birbirinin eşiydi: her durum olayı aynı büyüklükte, aynı işi yapan, milisaniyeler süren bir kayıttı. Birikme hesabı da buna dayanıyordu — kapasite bir sayıdır çünkü işler tek tiptir. Aynı sistemde bu varsayımın tutmadığı bir iş var: satıcının istediği dönem raporu, binlerce gönderi kaydını tarar ve saniyelerce sürer. Böyle bir iş isteğin içinde çalıştırıldığında yalnız kendi süresini harcamaz, arkasındaki kısa istekleri de bekletir. Sonraki ders bu iki işi ayırır: uzun işin istek yolunda kalmasının kısa isteklere ne yaptığını ölçer ve görev kuyruğuna alındığında hangi ölçünün düzeldiğini sayar.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.