Ders 16 / 16
Kapasite Planlama
Ölçülen yükten süreç ve düğüm sayısına giden hesap: tek sürecin havuz boyuna göre ölçülen kapasitesi, buradan çıkan süreç, bağlantı ve düğüm sayısı, depo bağlantı sınırının havuz boyunu geriye doğru belirlemesi ve az ile çok tahminin hedef yükte hiç görünmeyip yığılmada ürettiği fark.
İçindekiler
Bu konu boyunca sayılan her şey — düşen istek, kesinti saniyesi, geri alma adımı — kaynağın verili olduğu bir düzende ölçüldü; düğümler, yayında ikiye katlanan süreçler ve göçün koştuğu depo hep vardı. Bu ders o düzeni kuran sayıyı üretir: bir süreç hangi yükü taşır, kaç süreç gerekir, yayın ve arıza payı ne ekler, kaç düğüm eder.
Yük testinin yöntemi Başarım Karşıt Kalıpları ve İzleme, kapasite sınırının sınamayla bulunması İşlevsel Olmayan Test kursunda kuruldu; burada o ölçüm girdidir. Kursun kuralı sürer: yanlış tahmin hata vermez, ölçü farkı gösterir.
- DP19. Hedef yük saniyede 150 istektir; yığılmada dört katı gelir.
- DP20. Depo eş zamanlı 16 bağlantı kabul eder, üstündeki yuva okunmadan kapatılır.
- DP21. Bir düğümde uygulamaya 256 MiB ayrılır.
Ölçülen Düzenek
Depo bağlantı sınırını gerçekten uygular.
// depo.mjs — odunc kaydini veren depo; es zamanli acik baglanti sayisi SINIR ile kisitlidir import { createServer } from 'node:http'; const { SINIR = 16, GECIKME = 12, PORT = 8830 } = process.env; // plan.mjs bunlari gecer let acik = 0, enYuksek = 0; const depo = createServer((istek, yanit) => { if (istek.url === '/olcum') return yanit.end(JSON.stringify({ enYuksek })); setTimeout(() => yanit.end('{"kayit":1}'), Number(GECIKME)); }); depo.on('connection', (yuva) => { // sinirin ustundeki yuva okunmadan kapatilir if (acik >= Number(SINIR)) return yuva.destroy(); acik += 1; enYuksek = Math.max(enYuksek, acik); yuva.on('close', () => { acik -= 1; }); }); depo.listen(Number(PORT), () => console.log('depo hazir'));
Ödünç ucunda havuz hem depo yuvasını hem çalışma tamponunu tutar; istek başına iş sabittir.
// servis.mjs — odunc ucunun bir kopyasi; HAVUZ yuva ve HAVUZ tampon bastan ayrilir import { createServer, Agent, get } from 'node:http'; import { createHash } from 'node:crypto'; const { HAVUZ = 2, KUYRUK = 32, TUR = 40000, TAMPON = 4194304, DEPO = 8830, PORT = 8831 } = process.env; // plan.mjs bunlari gecer const havuz = Number(HAVUZ), tur = Number(TUR); const baglantilar = new Agent({ keepAlive: true, maxSockets: havuz }); const tamponlar = Array.from({ length: havuz }, () => Buffer.allocUnsafe(Number(TAMPON)).fill(0x2d)); const bos = tamponlar.map((_, i) => i); const bekleyen = []; let enUzunKuyruk = 0; function al() { // bos is yeri yoksa kuyruga gir, kuyruk da doluysa -1 if (bos.length) return Promise.resolve(bos.pop()); if (bekleyen.length >= Number(KUYRUK)) return Promise.resolve(-1); enUzunKuyruk = Math.max(enUzunKuyruk, bekleyen.length + 1); return new Promise((c) => bekleyen.push(c)); } const birak = (y) => { const s = bekleyen.shift(); if (s) s(y); else bos.push(y); }; const depoSorgu = () => new Promise((c, r) => get({ port: Number(DEPO), path: '/kayit', agent: baglantilar }, (y) => { y.resume(); y.on('end', c); }).on('error', r)); function isle(no, yer) { // istek basina sabit is: tur kadar ozet turu let ozet = createHash('sha256').update(String(no)).digest(); for (let i = 0; i < tur; i += 1) ozet = createHash('sha256').update(ozet).digest(); tamponlar[yer].write(ozet.toString('hex')); return tamponlar[yer].subarray(0, 8).toString('hex'); } createServer(async (istek, yanit) => { if (istek.url === '/olcum') { return yanit.end(JSON.stringify({ enUzunKuyruk, rss: process.memoryUsage().rss })); } const yer = await al(); if (yer < 0) return yanit.writeHead(503).end('{"durum":"kuyruk dolu"}'); try { await depoSorgu(); yanit.end(JSON.stringify({ damga: isle(istek.url, yer) })); } catch (e) { yanit.writeHead(502).end(JSON.stringify({ hata: e.code ?? e.message })); } finally { birak(yer); } }).listen(Number(PORT), () => console.log('servis hazir'));
Ölçümden Tahmine
Sürücü tek süreci havuz boyunu değiştirerek doyurur, kapasiteden süreç ve düğüm sayısını hesaplar, üç tahmini aynı yük ve yığılma altında koşturur. 40.000 özet turu, 4 MiB tampon ve depo sınırı koşumdan bağımsızdır; hız ve bellek bu koşumundur.
// plan.mjs — tek surecin kapasitesi havuz basina olculur, surec ve dugum sayisi hesaplanir, // uc tahmin ayni hedef yuk ve ayni yigilma altinda kosturulur import { spawn } from 'node:child_process'; const HEDEF = 150, YIGILMA = 4; // DP: saniyede gelen istek ve yigilma kati const SURE = 1800, YSURE = 1000; // olcum ve yigilma sureleri (ms) const TUR = 40000, TAMPON = 4 * 1024 * 1024, KUYRUK = 32; const DEPO_SINIR = 16, DEPO_GECIKME = 12; // DP: deponun kabul ettigi es zamanli baglanti const DUGUM_BELLEGI = 256, PAY = 2; // DP: dugum bellegi (MiB); yayin + ariza payi let taban = 8830; const cocuklar = []; const baslat = (dosya, env) => new Promise((k, r) => { const c = spawn(process.execPath, [dosya], { stdio: ['ignore', 'pipe', 'inherit'], env: { ...process.env, ...env } }); cocuklar.push(c); c.stdout.once('data', k); c.once('exit', (kod) => r(new Error(`${dosya} ${kod} ile cikti`))); }); const kapat = () => { cocuklar.forEach((c) => c.kill('SIGKILL')); cocuklar.length = 0; }; async function kur(surec, havuz) { const depo = taban; taban += 20; await baslat('depo.mjs', { PORT: depo, SINIR: DEPO_SINIR, GECIKME: DEPO_GECIKME }); const portlar = []; for (let i = 1; i <= surec; i += 1) { await baslat('servis.mjs', { PORT: depo + i, DEPO: depo, HAVUZ: havuz, KUYRUK, TUR, TAMPON }); portlar.push(depo + i); } return { depo, portlar }; } const olcum = (p) => fetch(`http://127.0.0.1:${p}/olcum`).then((y) => y.json()); const iste = (p, i) => fetch(`http://127.0.0.1:${p}/odunc/${i}`) .then((y) => { y.body.cancel().catch(() => {}); return y.status; }).catch(() => 0); function tablo(bas, satirlar) { const g = bas.map((b, i) => Math.max(b.length, ...satirlar.map((s) => String(s[i]).length))); for (const r of [bas, ...satirlar]) console.log(r.map((v, i) => String(v).padEnd(g[i])).join(' ').trimEnd()); } // acik dongu: sabit hizda gonderilir, yanit kodlari sayilir async function dene(surec, havuz, hiz, sure) { const { depo, portlar } = await kur(surec, havuz); const say = { tamam: 0, kuyruk: 0, kopan: 0 }, ucusta = []; let borc = 0, n = 0; for (let t = 0; t < sure; t += 20) { for (borc += (hiz * 20) / 1000; borc >= 1; borc -= 1) { n += 1; ucusta.push(iste(portlar[n % portlar.length], n) .then((k) => { say[k === 200 ? 'tamam' : k === 503 ? 'kuyruk' : 'kopan'] += 1; })); } await new Promise((k) => setTimeout(k, 20)); } await Promise.all(ucusta); const ic = await Promise.all(portlar.map((x) => olcum(x))); cocuklar.slice(1).forEach((c) => c.kill('SIGKILL')); // depo olcumu bir baglanti ister await new Promise((k) => setTimeout(k, 150)); const d = await olcum(depo); kapat(); return [say.tamam, say.kuyruk, say.kopan, Math.max(...ic.map((x) => x.enUzunKuyruk)), d.enYuksek, (ic.reduce((t, x) => t + x.rss, 0) / 2 ** 20).toFixed(0)]; } // 1. asama: kapali dongu; istemci 16 istek ucusta tutar, sinirlayan sey havuzdur const plan = []; for (const h of [1, 2, 4, 8]) { const { depo, portlar } = await kur(1, h); const bit = Date.now() + SURE; let n = 0; await Promise.all(Array.from({ length: 16 }, async () => { while (Date.now() < bit) if (await iste(portlar[0], n) === 200) n += 1; })); const { rss } = await olcum(portlar[0]); const { enYuksek } = await olcum(depo); kapat(); const hiz = n / (SURE / 1000), yuk = Math.ceil(HEDEF / hiz); plan.push({ h, hiz, mib: rss / 2 ** 20, yuva: enYuksek, yuk, surec: yuk + PAY, baglanti: (yuk + PAY) * h }); } tablo(['havuz', 'tamamlanan/sn', 'depo yuvasi', 'rss (MiB)', 'yuku karsilayan surec', '+pay', 'toplam baglanti', 'depo siniri'], plan.map((p) => [p.h, p.hiz.toFixed(1), p.yuva, p.mib.toFixed(0), p.yuk, p.surec, p.baglanti, p.baglanti <= DEPO_SINIR ? 'gecer' : 'asilir'])); const sec = plan.filter((p) => p.baglanti <= DEPO_SINIR).sort((a, b) => a.surec - b.surec)[0]; const dBasina = Math.floor(DUGUM_BELLEGI / sec.mib); console.log(`\nsecilen plan: havuz ${sec.h}, ${sec.surec} surec (${sec.yuk} yuk + ${PAY} pay), ` + `${sec.baglanti} baglanti, ${Math.ceil(sec.surec / dBasina)} dugum (dugume ${dBasina} surec)`); // 2. asama: uc tahmin, once hedef yuk sonra yigilma const ayarlar = [['az tahmin', sec.yuk - 1, sec.h], ['secilen plan', sec.surec, sec.h], ['cok tahmin, havuz dokunulmamis', sec.surec + 2, 4]]; for (const [ad, hiz, sure] of [['hedef yuk', HEDEF, SURE], ['yigilma', HEDEF * YIGILMA, YSURE]]) { const satirlar = []; for (const [etiket, surec, havuz] of ayarlar) { satirlar.push([etiket, surec, havuz, surec * havuz, ...await dene(surec, havuz, hiz, sure)]); } console.log(`\n${ad} — ${hiz} istek/sn`); tablo(['ayar', 'surec', 'havuz', 'havuz carpimi', '200', '503', '502', 'en uzun kuyruk', 'depo yuvasi', 'rss'], satirlar); }
havuz tamamlanan/sn depo yuvasi rss (MiB) yuku karsilayan surec +pay toplam baglanti depo siniri 1 41.7 2 83 4 6 6 gecer 2 69.4 3 90 3 5 10 gecer 4 68.9 5 98 3 5 20 asilir 8 70.6 9 112 3 5 40 asilir secilen plan: havuz 2, 5 surec (3 yuk + 2 pay), 10 baglanti, 3 dugum (dugume 2 surec) hedef yuk — 150 istek/sn ayar surec havuz havuz carpimi 200 503 502 en uzun kuyruk depo yuvasi rss az tahmin 2 2 4 270 0 0 23 4 181 secilen plan 5 2 10 270 0 0 0 6 449 cok tahmin, havuz dokunulmamis 7 4 28 270 0 0 0 7 642 yigilma — 600 istek/sn ayar surec havuz havuz carpimi 200 503 502 en uzun kuyruk depo yuvasi rss az tahmin 2 2 4 279 321 0 32 4 183 secilen plan 5 2 10 460 140 0 32 10 520 cok tahmin, havuz dokunulmamis 7 4 28 351 0 249 17 16 683
Havuzun Üç Faturası
Havuz bir iken süreç saniyede 41,7 istek tamamladı; ikide 69,4’e sıçradı, dört ve sekizde aynı bantta kaldı. Havuz bir iken depo beklemesi hiçbir işle örtüşemez, ikide bir istek beklerken diğeri işlenir. İkinin üstünde örtüşecek bekleme kalmaz.
Sonraki iki sütun ayarın fiyatıdır: depoda açılan yuva 2, 3, 5 ve 9’a, süreç belleği 83’ten 112 MiB’a çıktı. Havuz bir üst sınır değil, doyumda gerçekleşen bağlantı sayısıdır.
Süreçten Düğüme
Havuz iki iken 150 isteği karşılamak için tavan(150 / 69,4) = 3 süreç yeter; yayın ve arıza
payı eklenince beş olur. Beş süreç havuz iki ile depoya on bağlantı düşürür. Havuz dört
seçilseydi süreç sayısı yine beş olurdu ama bağlantı yirmiye, havuz sekizde kırka çıkardı.
Bu, hesabın yönünü tersine çevirir: depo sınırı bölü süreç sayısı havuza tavan koyar, tavan kapasiteyi, kapasite de süreç sayısını belirler.
Düğüm sayısı bir bölmedir: 90 MiB’lik süreç 256 MiB’lik düğüme ikişer sığar, beş süreç üç düğüm eder. Burada bir kayma var — pay süreç cinsinden hesaplandı, arıza düğüm cinsinden gelir: bir düğüm gidince iki süreç birden gider.
İki Yönlü Yanlış Tahmin
Hedef yükte üç ayar da aynı görünüyor: 270 isteğin 270’i tamamlandı, üçünde de hata sıfır. Yanlış tahminin sessizliği burasıdır.
Az tahminin izi tek sütunda: iki süreçle en uzun kuyruk 23’e çıktı, doğru planda sıfır kaldı. İki süreç saniyede yaklaşık 139 istek taşır, hedef 150’dir; biriken istek kuyruğu birkaç saniyede doldurur, ölçüm penceresi bundan kısadır. Yığılmada aynı ayar 321 istek kaybetti. Kuyruk derinliği, düşen istekten önce gelen ve tek başına hiçbir hata üretmeyen göstergedir.
Çok tahminin hedef yükteki tek izi bellektedir: yedi süreç 642 MiB, beş süreç 449 MiB tuttu. Yığılmada yedi süreç depoya yirmi sekiz bağlantı istedi; depo yuvası on altıda doydu ve 249 istek 502 aldı. Tamamlanan 351 istek, doğru planın 460’ından azdır. Doğru plan taşıyamadığı 140 isteği kendi kuyruğunda 503 ile reddetti; çok tahmin paylaşılan kaynağı harcadı.
Üç girdi üç ayrı katmanda durur: süreç sayısı süreç yöneticisinde, havuz uygulamanın ortamında, bağlantı sınırı depoda. Ölçülen şey ilk ikisinin çarpımıdır ve hiçbir dosyada yazmaz.
Özet
- Havuz boyu kapasiteyi (41,7’den 69,4 isteğe), süreç belleğini (83’ten 112 MiB’a) ve depoya düşen yuva sayısını (2’den 9’a) birden belirler; ikinin üstünde yalnız fiyat arttı.
- Ölçümden plana giden hesap üç adımdır — yükü karşılayan süreç, yayın ve arıza payı, bellekle bölünen düğüm: havuz 2, 5 süreç, 10 bağlantı, 3 düğüm.
- Depo bağlantı sınırı havuza tavan koyar: aynı süreç sayısıyla havuz 4 yirmi, havuz 8 kırk bağlantı ederdi.
- Pay süreç cinsinden hesaplanırken arıza düğüm cinsinden gelir; düğüme iki süreç sığdığı için bir düğümün kaybı, tek süreçlik arıza payının iki katıdır.
- Hedef yükte üç ayar da 270 isteğin 270’ini karşıladı; az tahminin izi 23’e çıkan kuyruk, çok tahminin izi boşta duran 193 MiB oldu. Yığılmada az tahmin 321 isteği reddetti, çok tahmin depo yuvası dolduğu için 249 istek kopardı.
Kurs Kapanışı
| Ders | Doğru ayarın ölçüsü | Yanlış ayarın sessiz sonucu | Ayarın yeri |
|---|---|---|---|
| Girdi Doğrulama | İzin listesinde 0 ve 1, engellemede 11 ve 4 yanlış | Üç yanlış ayarda hata 0 | Uçta 0, depoda 22 boşa adım |
| Enjeksiyon Sınıfı | Ayrı işlenen yolunda bozulma 0 | Kaçan birleştirme 10 girdinin 4’ünde sapıyor | Karar 12 çağrı yerinde ayrı |
| Kaynaklar Arası Erişim | Köken listesi 2 dış kökene açık | Joker ayar 200 döndürdü, istemci attı | 5 uçta ayrı, tek katmanda 1 |
| Güvenlik Başlıkları | Tek katmanda 1 kayıt noktası, yeni uç için 0 satır | Tek yanlış değer 10/10’u 0/10’a düşürdü | Uç başına 10 dosya, tek katmanda 1 |
| Gizli Değer Yönetimi | Pencere 390 saniye; penceresiz döndürme 36 ret | Sızan anahtar 900. saniyede 200 aldı | Anahtar 14 yerde yılda 56 adım |
| Bağımlılık Riski | Günlük yükseltme 113 açık gün, 237 müdahale günü | Gevşek aralıkta iki dağıtım 14 pakette ayrıştı | Sürüm aralığı bildirimde |
| Denetim Kaydı | Bütün karar noktaları 8 sorunun 8’ini yanıtladı | Son 25 kaydın silinmesi yakalanmadı | Çapa dönemi: çapasız 3.012, 500’de birle 499 |
| Kötüye Kullanım | Katmanlı anahtar 2.446 istek durdurdu, yanlış kalma 0 | Normalize edilmeyince durdurulan 2.266’ya indi | Anahtar seçimi sınırlayıcıda |
| Ters Vekil | Aktarım açıkken 16 isteğin 11’i geçti, kayıtta 4 kimlik | Kapalıyken 6 iyi istek durduruldu | Aktarım vekilde, gövde sınırı iki katmanda |
| Süreç Yöneticileri | Anında başlatma 24 isteğin 20’sini yanıtladı; 500 ms bekleme 6 isteği boşalttı | Başlamayan süreçte onlarca yeniden başlatma (o koşumda 43) | Politika ve bekleme süreç yöneticisinde |
| Yatay/Dikey Ölçekleme | Havuz ikiye inince 60 isteğin 60’ı yanıtlandı | Havuza dokunulmayınca bağlantı 32’ye çıktı, 30 istek yanıtsız | Süreç sayısı ve havuz ayrı |
| Durumsuz Uygulama | Tek kopyada 21 isteğin 21’i doğru | İki kopyada 12 istek yanlışlandı, kod değişmedi | 55 satırda 12 çağrı yeri |
| Oturum ve Yapışkanlık | Paylaşılan depoda kaybolan oturum 0 | Süreç belleğinde 8 ekleme biriktirmedi, hepsi 200 döndü | İmza anahtarı düğüm başına |
| Sıfır Kesintili Yayın | Mavi–yeşil ve aşamalı yayın 0 istek düşürdü | Sert kapanmada uçuştaki 8 istek düştü, sayaçta görünmedi | Yayın düzeni dağıtımda |
| Göç ile Yayın Sırası | Genişlet–daralt 120 isteğin 120’sini 5 adımda karşıladı | Çift yazma atlanınca alan boş kaldı, istek 200 döndü | Sıra yazılı listede |
| Kapasite Planlama | Havuz 2 ile 5 süreç, 10 bağlantı, 3 düğüm | Az tahminde kuyruk 23’e çıktı, 193 MiB boşta durdu | Süreç yöneticisi, uygulama ve depo |
Orta sütun kursun kuralını on altı kez yazıyor: yanlış yapılandırma hata vermez; ölçü farkı gösterir. Eksik kalan başlık, kapatılmayan anahtar, atlanan çift yazma, fazla tutulan süreç — hiçbiri istisna fırlatmadı. Doğruyu yanlıştan ayıran şey her derste sayılan bir fark oldu: geçen istek, açılan köken, kurtarılamayan kayıt, boşta duran bellek. Her ayarın yanına ölçüsü de yazılır.
Bu ders Sunucu Güvenliği ve Üretime Alma kursunu ve müfredatı kapatıyor. Sunucu Tarafı Temelleri isteğin sunucudaki yolunu, Web API Tasarımı sözleşmeyi, Kimlik ve Yetki erişimi, Veri Erişim Katmanı kalıcılığı, Önbellekleme ve Eşzamansız İşleme isteğin dışına çıkan işi, Servis Mimarileri sınırı, Gözlemlenebilirlik ve Güvenilirlik görünürlüğü kurdu; bu kurs da üretime alma kararlarını. Sırada okurun kendi sistemi var: hangi ucun doğrulayıcısı eksik, kaç süreç koşuyor ve o sayı hangi ölçümden çıktı.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.