Ders 03 / 16
Gevezelik Eden Girdi/Çıktı
Çağrı sayısı ile taşınan bayt arasındaki ödünleşim: aynı panel açılışının üç çağrı taneciğinde ölçülmesi, taşınan baytın işe yarayan payının zarf payı ile kullanılmayan yük payına ayrılması, tek büyük çağrının hangi kullanım oranının altında daha kötü olduğu ve yüksek önbellek isabetinin neden az bayt anlamına gelmediği.
İçindekiler
Önceki ders yükü küçülttü: sunucu 200 kayıt yerine 25 kayıt gönderiyor ve hangi 25 kaydın gösterileceğine kendisi karar veriyor. Bu kararın bir bedeli vardı ve o bedel sayılmadı. Kullanıcı listede ilerledikçe karar yenilenir, bir satır açıldığında ayrıntısı istenir, bir süzme yapıldığında liste yeniden alınır. Çağrı sayısı büyüdü.
Bu, Veri Erişim Katmanı kursunda kurulan N+1 sorgu sorununun servis çağrısı ölçeğindeki karşılığıdır. Oradaki mekanik — sorgu sayısının kayıt sayısıyla büyümesi, toplu getirme ve birleştirme — burada tekrarlanmaz. Buradaki soru başkadır: çağrı sayısını azaltmanın karşılığında taşınan bayt ne kadar büyür ve bu takasın nerede tersine döndüğü hangi ölçümle bulunur.
Belirti: Hiçbir Ölçütte Görünmeyen Yavaşlık
Panel açılışı uçtan uca yavaş. Her çağrının sunucu tarafındaki süresi küçük ve sabit; hiçbir uç eşiğini aşmıyor. Toplam yararlı yük dokuz kilobayt dolayında, yani aktarım açıklama olamaz. Depo tarafında sınır oranı 1,00 — 01. dersin ölçüsü temiz. Belirti her ölçütün arasından geçiyor.
Gevezelik eden girdi/çıktı (chatty I/O), bir işin çok sayıda küçük çağrıya bölünmesidir. Ama adı koymak yine yetmez, çünkü aynı belirtiyi ters bir düzenleme de verir.
Neden A — çağrı sayısı. Her çağrı, gövdesinden bağımsız bir zarf (envelope) taşır: istek satırı, kimlik, izleme bağlamı, içerik başlıkları, yanıt başlıkları. Zarf sabittir; çağrı sayısıyla çarpılır. Ayrıca çağrılar birbirini beklerse tur sayısı büyür.
Neden B — aşırı paketleme. Çağrı sayısı bire indirilmiştir, ama tek çağrı ihtiyaç duyulmayanı da taşır. Zarf payı sıfıra yakındır, taşınanın çoğu hiç kullanılmaz.
İkisi de “çağrılar hızlı ama açılış yavaş” belirtisini verir ve düzeltmeleri birbirinin tersidir.
Ölçüm Düzeneği
Aşağıdaki koşum bir modeldir: tur soyut bir adımdır ve süre ölçülmez. Ölçülen şey bayttır ve zarfın baytı uydurulmaz — kodun içindeki üstbilgi metninden gerçekten hesaplanır.
KK3 — istemcinin aynı anda açık tutabildiği çağrı sayısı: 6. Gerekçe: bağımlı çağrılar bir turda değil, bu sınırın böldüğü kadar turda tamamlanır. Duyarlılığı tur sütununda görünür.
Üç tanecik karşılaştırılıyor. G1, liste, satıcı ve bölge için birer çağrı ve açılan her satır için bir ayrıntı çağrısı yapar. G2, listeyi ve yirmi beş ayrıntıyı tek çağrıda alır, satıcıyı ve bölgeyi ayrı bırakır. G3, hepsini tek çağrıda alır. Değişken , yirmi beş satırın kaçının ayrıntısının gerçekten açıldığıdır.
// cagri/tanecik.mjs — ayni panel acilisinin uc cagri taneciginde olculmesi. Olcu: tasinan // baytin ise yarayan payi ve onun iki bileseni (zarf payi, kullanilmayan yuk payi). // MODELDIR: tur soyut bir adimdir, sure olculmez; zarfin bayti asagidaki ustbilgiden olculur. const GORUNEN = 25, OLAY = 7, TARIFE = 40, BOLGE = 12; // KK2 = 25 satir, K01 V4 = 7 olay const ESZAMAN = 6; // KK3: istemcinin ayni anda actigi cagri const ZARF = ["GET /panel/liste HTTP/1.1", "host: uc", "accept: application/json", "accept-encoding: gzip", "authorization: Bearer <belirtec>", "traceparent: <baglam>", "HTTP/1.1 200 OK", "content-type: application/json", "cache-control: max-age=30", "etag: <surum>", "content-length: <n>", "traceparent: <baglam>"].join("\r\n") + "\r\n\r\n"; const Z = Buffer.byteLength(ZARF); const bayt = (x) => Buffer.byteLength(JSON.stringify(x)); const liste = () => Array.from({ length: GORUNEN }, (_, i) => ({ no: `TR-${i}`, guncelleme: (i * 97) % 100000, durum: i % 5, agirlik: 1 + (i % 30) })); const ayrinti = (i) => ({ no: `TR-${i}`, olaylar: Array.from({ length: OLAY }, (_, j) => ({ kod: (i * 3 + j) % 5, ts: (i * 97 + j * 13) % 100000, rota: `R-${(i + j) % 40}` })) }); const satici = () => ({ ad: "satici-10", sozlesme: 0.07, tarife: Array.from({ length: TARIFE }, (_, i) => ({ id: i, oran: 12 + (i % 7) * 0.5 })) }); const bolge = () => ({ bolgeler: Array.from({ length: BOLGE }, (_, i) => ({ id: i, katsayi: 1 + (i % 5) * 0.1 })) }); const L = bayt(liste()), S = bayt(satici()), B = bayt(bolge()); const A = Array.from({ length: GORUNEN }, (_, i) => bayt(ayrinti(i))); const ilkK = (k) => A.slice(0, k).reduce((a, x) => a + x, 0); const TUM_A = ilkK(GORUNEN); // k: 25 satirin kacinin ayrintisi gercekten aciliyor const PLAN = { "G1 gevezelik": (k) => ({ cagri: 3 + k, tur: 1 + Math.ceil(k / ESZAMAN), yuk: L + S + B + ilkK(k) }), "G2 toplu": (k) => ({ cagri: 3, tur: 1, yuk: L + S + B + TUM_A }), "G3 tek cagri": (k) => ({ cagri: 1, tur: 1, yuk: L + S + B + TUM_A }), }; const olc = (ad, k) => { const p = PLAN[ad](k), zarf = p.cagri * Z, tasinan = p.yuk + zarf; const kullanilan = L + S + B + ilkK(k); return { ...p, zarf, tasinan, kullanilan, zarfPayi: zarf / tasinan, bosPayi: (p.yuk - kullanilan) / tasinan, isePayi: kullanilan / tasinan }; }; const AD = Object.keys(PLAN), say = (x) => x.toLocaleString("tr-TR"); console.log(`model: panel ${GORUNEN} satir gosteriyor, zarf ${Z} bayt (ustbilgi metninden olculdu), ` + `eszamanli cagri siniri ${ESZAMAN} (KK3); liste ${L}, satici ${S}, bolge ${B}, ` + `${GORUNEN} ayrinti ${say(TUM_A)} bayt`); for (const k of [25, 3]) { console.log(`\n${GORUNEN} satirin ${k} tanesinin ayrintisi aciliyor`); console.log(`${"plan".padEnd(16)}${"cagri".padStart(7)}${"tur".padStart(5)}${"tasinan bayt".padStart(14)}` + `${"zarf payi".padStart(11)}${"kullanilmayan yuk".padStart(19)}${"ise yarayan pay".padStart(17)}`); for (const ad of AD) { const o = olc(ad, k); console.log(ad.padEnd(16) + String(o.cagri).padStart(7) + String(o.tur).padStart(5) + say(o.tasinan).padStart(14) + o.zarfPayi.toFixed(4).padStart(11) + o.bosPayi.toFixed(4).padStart(19) + o.isePayi.toFixed(4).padStart(17)); } } console.log(`\nkac ayrinti aciliyorsa hangi plan az tasir (soguk acilis)`); console.log(`${"acilan".padStart(7)}${"G1 bayt".padStart(10)}${"G2 bayt".padStart(10)}` + `${"G1/G2".padStart(8)}${"G1 tur".padStart(8)}${"en az tasiyan".padStart(15)}`); for (const k of [1, 3, 6, 12, 13, 18, 25]) { const g = AD.map((ad) => olc(ad, k)); const en = AD[g.indexOf(g.reduce((a, b) => (b.tasinan < a.tasinan ? b : a)))]; console.log(String(k).padStart(7) + say(g[0].tasinan).padStart(10) + say(g[1].tasinan).padStart(10) + (g[0].tasinan / g[1].tasinan).toFixed(3).padStart(8) + String(g[0].tur).padStart(8) + en.padStart(15)); } const PARCA = { liste: 5, ayrinti: 20, satici: 100, bolge: 1000 }; // kac acilista bir degisiyor const ICERIK = { "G1 gevezelik": [["liste"], ["satici"], ["bolge"], ...Array.from({ length: GORUNEN }, () => ["ayrinti"])], "G2 toplu": [["liste", "ayrinti"], ["satici"], ["bolge"]], "G3 tek cagri": [["liste", "ayrinti", "satici", "bolge"]] }; const GOVDE = { "G1 gevezelik": [L, S, B, ...A], "G2 toplu": [L + TUM_A, S, B], "G3 tek cagri": [L + TUM_A + S + B] }; const T = 100; const sonuc = {}; for (const ad of AD) { let isabet = 0, tasinan = 0, zarf = 0; for (let t = 1; t <= T; t += 1) ICERIK[ad].forEach((parcalar, i) => { const degisti = t === 1 || parcalar.some((p) => t % PARCA[p] === 0); tasinan += Z + (degisti ? GOVDE[ad][i] : 0); // isabet eden cagri da zarf oder: kosullu istek zarf += Z; if (!degisti) isabet += 1; }); sonuc[ad] = { isabet: isabet / (T * ICERIK[ad].length), tasinan, zarf }; } console.log(`\n${T} acilista, tum ayrintilar aciliyor, her acilis kosullu istekle dogruluyor; ` + `parca degisim araligi liste ${PARCA.liste} / ayrinti ${PARCA.ayrinti} / ` + `satici ${PARCA.satici} / bolge ${PARCA.bolge} acilis`); console.log(`${"plan".padEnd(16)}${"isabet".padStart(9)}${"tasinan bayt".padStart(14)}` + `${"bunun zarfi".padStart(13)}${"zarf payi".padStart(11)}${"G3'e gore".padStart(11)}`); for (const ad of AD) { const o = sonuc[ad]; console.log(ad.padEnd(16) + o.isabet.toFixed(4).padStart(9) + say(o.tasinan).padStart(14) + say(o.zarf).padStart(13) + (o.zarf / o.tasinan).toFixed(4).padStart(11) + (o.tasinan / sonuc["G3 tek cagri"].tasinan).toFixed(3).padStart(11)); }
model: panel 25 satir gosteriyor, zarf 280 bayt (ustbilgi metninden olculdu), eszamanli cagri siniri 6 (KK3); liste 1368, satici 869, bolge 286, 25 ayrinti 6.459 bayt
25 satirin 25 tanesinin ayrintisi aciliyor
plan cagri tur tasinan bayt zarf payi kullanilmayan yuk ise yarayan pay
G1 gevezelik 28 6 16.822 0.4661 0.0000 0.5339
G2 toplu 3 1 9.822 0.0855 0.0000 0.9145
G3 tek cagri 1 1 9.262 0.0302 0.0000 0.9698
25 satirin 3 tanesinin ayrintisi aciliyor
plan cagri tur tasinan bayt zarf payi kullanilmayan yuk ise yarayan pay
G1 gevezelik 6 2 4.941 0.3400 0.0000 0.6600
G2 toplu 3 1 9.822 0.0855 0.5825 0.3320
G3 tek cagri 1 1 9.262 0.0302 0.6177 0.3521
kac ayrinti aciliyorsa hangi plan az tasir (soguk acilis)
acilan G1 bayt G2 bayt G1/G2 G1 tur en az tasiyan
1 3.884 9.822 0.395 2 G1 gevezelik
3 4.941 9.822 0.503 2 G1 gevezelik
6 6.531 9.822 0.665 2 G1 gevezelik
12 9.750 9.822 0.993 3 G3 tek cagri
13 10.294 9.822 1.048 4 G3 tek cagri
18 13.014 9.822 1.325 4 G3 tek cagri
25 16.822 9.822 1.713 6 G3 tek cagri
100 acilista, tum ayrintilar aciliyor, her acilis kosullu istekle dogruluyor; parca degisim araligi liste 5 / ayrinti 20 / satici 100 / bolge 1000 acilis
plan isabet tasinan bayt bunun zarfi zarf payi G3'e gore
G1 gevezelik 0.9379 853.506 784.000 0.9186 3.940
G2 toplu 0.9200 250.391 84.000 0.3355 1.156
G3 tek cagri 0.7900 216.622 28.000 0.1293 1.000
İşe Yarayan Pay Nedeni Adlandırıyor
Ayırt eden ölçüm taşınan baytın işe yarayan payıdır: gerçekten kullanılan yararlı baytın, zarfıyla birlikte taşınan toplam bayta bölümü. Kaybın nereye gittiği iki bileşende görünüyor ve büyük olan bileşen nedeni adlandırıyor.
Yirmi beş satırın hepsi açıldığında G1’in işe yarayan payı 0,5339 — taşınanın neredeyse yarısı zarftır (0,4661). Kullanılmayan yük sıfır: hiçbir bayt boşuna taşınmıyor, yine de plan en pahalısı. Tanı gevezeliktir.
Üç satır açıldığında tablo yön değiştiriyor. G3’ün işe yarayan payı 0,3521 ve kaybın kaynağı zarf değil, kullanılmayan yüktür: 0,6177. G1 aynı durumda 0,6600 ile en iyisi. Tanı aşırı paketlemedir.
Tek bir ölçüm, iki bileşen, iki karşıt düzeltme. Zarf payı büyükse çağrılar birleştirilir; kullanılmayan yük payı büyükse çağrılar ayrılır. Toplam bayta bakmak bunu söylemez — k = 3’te G3’ün 9.262 baytı, k = 25’teki 9.262 baytla aynıdır, oysa biri sağlıklı, öteki israftır.
Ödünleşimin Döndüğü Nokta
Üçüncü tablo takası sayıyla sınırlıyor. Yirmi beş satırın kaçının ayrıntısının açıldığı, hangi planın az taşıdığını belirliyor ve dönüm noktası 12 ile 13 arasındadır. On iki satırda G1 9.750 bayt, G3 9.262 bayt taşıyor; on üç satırda G1 10.294’e çıkıp G2’yi 1,048 katıyla geçiyor. Bir satırda oran 0,395 — gevezelik planı üçte birinden az taşıyor.
Bu, karşıt kalıbın doğru olduğu koşulun sayısıdır: taşınan yükün kullanılma oranı 12/25 = 0,48’in altındaysa çok sayıda küçük çağrı daha azını taşır. Aynı panel, aynı sistem, aynı zarf — değişen tek şey kullanıcının kaç satır açtığıdır. Kalıp bir yasak değil, bir orana bağlı bir seçimdir.
Tur sütunu takasın ikinci yüzüdür ve baytla aynı yöne bakmaz. G1 bir satırda 2 tur, yirmi beş satırda 6 tur alıyor; G2 ve G3 her durumda 1 tur. KK3’ün altı çağrılık sınırı burada görünüyor: çağrılar paralel gitse bile sınır onları turlara böler. Az taşıyan plan, daha çok tur bekletebilir.
Yüksek İsabet Az Bayt Demek Değil
Son tablo bir sezgiyi bozuyor. Yüz açılışta gevezelik planının önbellek isabeti en yüksektir: 0,9379, G3’ün 0,7900’üne karşı. Nedeni tanecikliktir — küçük çağrılar birbirinden bağımsız eskir, birleşik yanıt ise parçalarının en sık değişenine göre eskir.
Buna rağmen G1 toplamda 3,94 kat fazla bayt taşıyor: 853.506’ya karşı 216.622. Tablo nedeni veriyor — taşınanın 784.000 baytı zarftır, yani yüzde 91,86’sı. İsabet eden bir çağrı gövdesini taşımaz ama zarfını yine öder; koşullu istek de bir istektir.
Kural şudur: isabet oranı bir tanecik ölçüsü değildir. Yirmi sekiz çağrılık bir plan, isabetini 0,94’e çıkarsa bile tek çağrılık plandan dört kat fazla taşıyabilir. İki sayı birlikte okunmalıdır: isabet ve zarf payı.
Karşılığında Büyüyen
G1’den G2’ye geçmek taşınanı 3,94 kattan 1,156 kata indiriyor. Karşılığında üç şey büyüyor.
Önbellek isabeti düşüyor: 0,9379’dan 0,9200’e, tek çağrıda 0,7900’e. Liste beş açılışta bir değiştiği için ona bağlanan her parça da beş açılışta bir bayatlar; ayrıntının kendi yirmi açılışlık ömrü kayboluyor.
Sunucu tarafında birleştirme işi doğuyor: tek çağrı yirmi beş ayrıntıyı toplamak zorunda ve bu toplama K01’in tepe yükünde her istekte yapılır. 01. dersin sorusu burada geri geliyor — bu birleştirmenin hangi katmanda yapılacağı ayrı bir karardır.
Arıza yalıtımı kayboluyor: yirmi sekiz çağrılık planda bir ayrıntı çağrısının düşmesi bir satırı eksik bırakır, tek çağrılık planda aynı arıza panelin tamamını boş bırakır. Dayanıklılık ve Güvenilirlik kursundaki zarif bozulma bu yüzden tanecikliğe bağlıdır; burada yeniden anlatılmaz, ama toplu çağrıya geçerken ödenen bedelin bir parçasıdır.
Özet
- Belirti hiçbir çağrı ölçütünde görünmez: her çağrı hızlıdır, toplam yararlı yük küçüktür, açılış yine de yavaştır.
- Ayırt eden ölçüm taşınan baytın işe yarayan payıdır; kaybın büyük bileşeni nedeni adlandırır. k = 25’te G1 için zarf payı 0,4661 (gevezelik), k = 3’te G3 için kullanılmayan yük payı 0,6177 (aşırı paketleme).
- Düzeltmeler karşıttır: zarf payı büyükse çağrılar birleştirilir, kullanılmayan yük payı büyükse ayrılır. Toplam bayt tek başına ikisini ayırmaz.
- Takasın dönüm noktası ölçülür: kullanım oranı 12/25 = 0,48’in altında çok sayıda küçük çağrı az taşır (bir satırda oran 0,395), üstünde tek çağrı kazanır (yirmi beş satırda 1,713).
- Tur sayısı baytla aynı yöne bakmaz: G1 yirmi beş satırda 6 tur alır, KK3’ün altı çağrılık eşzamanlılık sınırı yüzünden.
- Yüksek isabet az bayt demek değildir: G1’in isabeti 0,9379 ama taşıdığı bayt G3’ün 3,94 katı, çünkü taşınanın yüzde 91,86’sı zarftır ve koşullu istek de zarf öder.
Sonraki Adım
Toplu çağrı taşınanı üçte bire indirdi, ama taşıdığı şeyin içine hiç bakılmadı. Yirmi beş ayrıntı 6.459 bayt tutuyor ve bu baytın içinde panelin hiç okumadığı alanlar var: her olayın taşıyıcı kodu, bölge kimliği, tam zaman damgası. Kullanılmayan yük payı ölçüsü bunları göremez, çünkü o ölçü kayıt tanecikliğinde çalışır; israf alan tanecikliğinde de olur. Sonraki ders o katmanı ele alır: ihtiyaçtan fazla okunan alanın uçtan uca ne büyüttüğü — önbelleğe sığan giriş, isabet oranı, depoya ulaşan istek, seri hâle getirilen alan ve ağ çıkışı — ve gereksiz baytı kesmenin karşılığında hangi kararın sertleştiği.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.