İçeriğe geç
academia.sh

Ders 08 / 13

Yatay ve Dikey Ölçekleme

Kaynağı değiştirmenin iki yolu ve her birinin sert sınırı: aynı işin bir işçiden on altı işçiye bölünmesiyle hızlanmanın ve verimliliğin ölçülmesi, bölümleme kararının seri payı ve üst sınırı nasıl değiştirdiğinin sayılması, çekirdek sayısının dikey ölçeklemeye koyduğu sınırın ölçülmesi ve iki yolun maliyet eğrilerinin aynı hedef altında karşılaştırılması.

İçindekiler

Önceki ders yükü değiştirdi ve kaynağı sabit tuttu; bütün ölçümler tek bir işçiyle alındı. Kapasite hesabı da bu varsayımla geçerliydi. Kaynak ise değiştirilebilir bir büyüklüktür ve değiştirilmesinin iki yolu vardır: aynı birimi büyütmek ya da birim sayısını artırmak. Bu ders iki yolu ayırır, ikisinin de nerede durduğunu ölçer ve maliyetlerini aynı hedef altında karşılaştırır.

Ayrımın adları katalogda geçtiği gibidir. Dikey ölçekleme (vertical scaling) ölçekleme biriminin kendisini büyütmektir: aynı birime daha çok işlemci, daha çok bellek. Yatay ölçekleme (horizontal scaling) birim sayısını artırmaktır. Sunucu Tarafı Temelleri kursunda ölçekleme biriminin süreç olduğu ve süreçlerin bellek paylaşmadığı kurulmuştu; bu ders o kuralın sonucunu bir ölçüye çevirir.

Aynı İş, Artan İşçi Sayısı

Ölçülen iş gün sonu ücretlendirmesidir: otuz günü kapsayan 12 milyon kayıt fiyatlanır ve her fatura satırı için bir denetim izi üretilir. Önceki derste kurulan gruplama birimi geçerlidir — bir bölüm bir satıcının bir günüdür, dolayısıyla otuz günlük taramada 120 000 bölüm vardır (4000 satıcı çarpı 30 gün). İş bir işçiden on altı işçiye kadar bölünür ve her düzeyde toplam süre ölçülür. Kayıtlar okunmaz, deterministik olarak üretilir; ölçülen şey hesap payının paralelleşmesidir, girdi-çıktı değildir.

// olcek/isci.mjs — bir isci: kendi payindaki kalemleri fiyatlar, imzalar ve bolum toplamlarini dondurur
import { parentPort, workerData } from "node:worker_threads";

const TARIFE = { kademe: [[1, 3000], [5, 4800], [20, 9600]], bolge: { "34": 100, "06": 115, "35": 125 }, asgari: 2500 };
const BOLGE = ["34", "06", "35"];

function net(agirlik, bolge, oran) {
  const kademe = TARIFE.kademe.find(([s]) => agirlik <= s) ?? [0, 9600];
  const taban = Math.max(TARIFE.asgari, Math.round((kademe[1] * TARIFE.bolge[bolge]) / 100));
  return taban - Math.round(taban * Math.min(oran, 0.4));
}

function imza(metin) {              // fatura satirinin denetim izi
  let h = 2166136261;
  for (let i = 0; i < metin.length; i += 1) h = Math.imul(h ^ metin.charCodeAt(i), 16777619) >>> 0;
  return h;
}

function pay({ bas, bit, adim, bolum }) {
  const toplam = new Map();
  for (let i = bas; i < bit; i += adim) {
    const b = i % bolum;
    const deger = net((i % 20) + 1, BOLGE[i % 3], (i % 5) / 20);
    const iz = imza(`G${i};${BOLGE[i % 3]};${deger}`);
    const onceki = toplam.get(b) ?? { net: 0, iz: 0 };
    toplam.set(b, { net: onceki.net + deger, iz: (onceki.iz ^ iz) >>> 0 });
  }
  return [...toplam];
}

if (workerData === null) console.log("bu dosya olcek/kos.mjs tarafindan isci olarak calistirilir");
else parentPort.postMessage(pay(workerData));
bu dosya olcek/kos.mjs tarafindan isci olarak calistirilir

Kalem sayısı V3 ile V11’in çarpımıdır ve Kabaca Büyüklük Hesabı dersindeki tablodan gelir; bu ders kendi hacim sayısını seçmez. Bölüm sayısı V13’ten türetilir: fatura satırına yüz kalem toplandığına göre on iki milyon kalem 120 000 bölüme düşer. Bölüm sayısı satıcı sayısı değildir — 4000 satıcının otuz günü 120 000 satıcı-gün eder.

Sürücü işi iki farklı biçimde böler. İndis bölümlemesi kalemleri bitişik bloklara ayırır; bir bölümün kalemleri bütün işçilere dağılır. Anahtar bölümlemesi adımlı ilerler; her bölüm tek bir işçiye düşer. İkisi de aynı sayıda kalem işler ve aynı sonucu üretmelidir.

// olcek/kos.mjs — ayni is 1..16 isciye bolunur; hizlanma, verimlilik ve birlestirme payi olculur
import { Worker } from "node:worker_threads";
import { availableParallelism } from "node:os";

const KALEM = 12_000_000;          // V3 x V11: otuz gunu kapsayan kayit sayisi
const BOLUM = KALEM / 100;         // varsayim: bir fatura satirina 100 kalem -> 4000 satici x 30 gun
const ISCI = [1, 2, 4, 8, 12, 16];

function isci(veri) {
  return new Promise((coz, sap) => {
    const w = new Worker(new URL("./isci.mjs", import.meta.url), { workerData: veri });
    w.on("message", coz);
    w.on("error", sap);
  });
}

function bolumler(n, kip) {        // "indis": bitisik blok, "anahtar": adimli
  return kip === "indis"
    ? Array.from({ length: n }, (_, s) => ({ bas: Math.floor((s * KALEM) / n), bit: Math.floor(((s + 1) * KALEM) / n), adim: 1, bolum: BOLUM }))
    : Array.from({ length: n }, (_, s) => ({ bas: s, bit: KALEM, adim: n, bolum: BOLUM }));
}

async function kos(n, kip) {
  const bas = performance.now();
  const paylar = await Promise.all(bolumler(n, kip).map(isci));
  const paralel = performance.now() - bas;
  const birlesikBas = performance.now();
  const fatura = new Map();
  let cakisma = 0;
  for (const pay of paylar) {
    for (const [b, v] of pay) {
      const onceki = fatura.get(b);
      if (onceki === undefined) fatura.set(b, { ...v });
      else { onceki.net += v.net; onceki.iz = (onceki.iz ^ v.iz) >>> 0; cakisma += 1; }
    }
  }
  const birlestirme = performance.now() - birlesikBas;
  const imza = [...fatura].reduce((a, [, v]) => (a ^ v.iz) >>> 0, 0);
  return { paralel, birlestirme, toplam: paralel + birlestirme, bolum: fatura.size, cakisma, imza };
}

console.log(`cekirdek = ${availableParallelism()}, kalem = ${KALEM}, bolum = ${BOLUM}`);
for (const kip of ["indis", "anahtar"]) {
  console.log(`\nbolumleme = ${kip}`);
  console.log("isci   paralel(ms)  birlestirme(ms)  toplam(ms)  hizlanma  verimlilik  cakisan kayit  imza");
  let taban = 0;
  for (const n of ISCI) {
    const r = await kos(n, kip);
    if (n === 1) taban = r.toplam;
    console.log(`${String(n).padStart(4)}  ${r.paralel.toFixed(0).padStart(11)}  ${r.birlestirme.toFixed(1).padStart(15)}  ` +
      `${r.toplam.toFixed(0).padStart(10)}  ${(taban / r.toplam).toFixed(2).padStart(8)}  ` +
      `${(taban / r.toplam / n).toFixed(2).padStart(10)}  ${String(r.cakisma).padStart(13)}  ${r.imza}`);
  }
}
cekirdek = 12, kalem = 12000000, bolum = 120000

bolumleme = indis
isci   paralel(ms)  birlestirme(ms)  toplam(ms)  hizlanma  verimlilik  cakisan kayit  imza
   1         1674              8.4        1683      1.00        1.00              0  799471432
   2          941             13.3         954      1.76        0.88         120000  799471432
   4          602             24.6         626      2.69        0.67         360000  799471432
   8          580             42.9         622      2.70        0.34         840000  799471432
  12          883             70.6         953      1.76        0.15        1320000  799471432
  16          882             68.3         950      1.77        0.11        1800000  799471432

bolumleme = anahtar
isci   paralel(ms)  birlestirme(ms)  toplam(ms)  hizlanma  verimlilik  cakisan kayit  imza
   1         1662              9.9        1672      1.00        1.00              0  799471432
   2          879              5.6         885      1.89        0.94              0  799471432
   4          549              5.5         554      3.02        0.75              0  799471432
   8          321              6.8         328      5.11        0.64              0  799471432
  12          284              5.5         289      5.79        0.48              0  799471432
  16          364              6.9         370      4.51        0.28              0  799471432

Bu sayılar ölçümdür ve bu makinede alınmıştır; çekirdek sayısı 12’dir ve başka bir makinede tablonun kırılma noktası başka yerdedir. İmza sütunu on iki koşumda da aynı değeri veriyor (799471432), yani bütün bölümlemeler aynı faturayı üretti; farklı sonuç veren koşumların süreleri karşılaştırılamaz. Tek işçili iki taban süresi 1683 ve 1672 milisaniyedir; aradaki fark ölçüm gürültüsüdür ve bu yüzden hızlanma her kipin kendi tabanına göre okunur.

Bir Sert Sınır

Anahtar bölümlemesinde hızlanma 1’den 12 işçiye kadar büyüyor: 1,89, 3,02, 5,11 ve 5,79. On altı işçide düşüyor: 4,51. Bu düşüş ölçüm hatası değil, dikey ölçeklemenin sınırının ta kendisi. Makinede 12 çekirdek var; on altı işçi on iki çekirdeği paylaşmak zorundadır ve paylaşımın kendisi bir maliyettir. Birimi büyütmenin bir noktada duracağı sezgisel bir iddia değil, tabloda okunan bir sayıdır.

Verimlilik sütunu aynı şeyi başka açıdan söylüyor. Paralel verimlilik (parallel efficiency) işçi başına elde edilen hızlanma payıdır, yani hızlanmanın işçi sayısına bölümü; Kalite Nitelikleri dersinin başarım verimliliği ailesiyle karıştırılmamalıdır, o bir nitelik ailesinin adıdır. Ölçümde iki işçide 0,94, dört işçide 0,75, sekiz işçide 0,64, on iki işçide 0,48. Her eklenen işçi öncekinden az getirir. Değerin 1’in altına düşmesi bir kusur değil, paralel işin doğasıdır; kararı ilgilendiren şey ne kadar hızlı düştüğüdür.

Yatay Ölçeklemenin Koşulu

İki bölümlemenin farkı dersin ikinci sonucudur. Aynı iş, aynı işçi sayısı, aynı makine — sekiz işçide hızlanma anahtar bölümlemesinde 5,11, indis bölümlemesinde 2,70. Neredeyse iki katı fark yalnızca işin nasıl bölündüğünden geliyor.

Farkın kaynağı çakışan kayıt sütununda. İndis bölümlemesinde on altı işçi 1 800 000 çakışan kayıt üretti: bir bölümün toplamı birden çok işçide parça parça durduğu için her parça birleştirme adımında toplanmak zorunda. Anahtar bölümlemesinde bu sayı sıfırdır. Birleştirme süresi de buna göre ayrışıyor: on altı işçide 68,3 milisaniyeye karşı 6,9 milisaniye.

Sunucu Tarafı Temelleri kursunda durumsuz süreç kuralı konmuştu: istekler arasında yaşaması gereken durum süreç dışına taşınır. Buradaki ölçüm o kuralın ölçekleme tarafındaki karşılığıdır. Yatay ölçekleme birim sayısını artırmakla olmaz; işin, birimler arasında paylaşılan durum gerektirmeyecek biçimde bölünebilmesiyle olur. Bölümleme anahtarının seçimi bu yüzden bir ayrıntı değil, ölçeklenebilirlik kararının kendisidir. Bölüm ve bölümleme terimleri İleri SQL kursunda tablo bölümleme bağlamında kurulmuştu; buradaki bağlam işin işçilere bölünmesidir, verinin diske değil.

Seri Pay, Üst Sınır ve Maliyet

Ölçülen hızlanmadan bir sayı daha çıkarılabilir. İşin bir kısmı bölünemez — işçilerin başlatılması, payların toplanması, sonucun yazılması. Bu seri pay ss ise, nn işçiyle en iyi hızlanma şudur:

S(n)=1s+1snS(n) = \frac{1}{s + \dfrac{1 - s}{n}}

Bağıntı tersten çözülünce ölçülen bir hızlanmadan seri pay çıkar, ve nn sonsuza giderken hızlanma 1/s1/s ile sınırlanır. Aşağıdaki hesap bunu iki bölümleme için yapar, sonra bir hedefi karşılamanın iki yoldaki maliyetini karşılaştırır. Dikey maliyet için bir varsayım gerekir: birimin gücü iki katına çıktığında birim maliyeti kaç katına çıkar. Üç değer birlikte veriliyor çünkü sonuç bu varsayıma duyarlıdır.

// hesap/maliyet.mjs — olculen hizlanmadan seri pay, ust sinir ve iki yolun maliyeti
const OLCUM = { anahtar: { isci: 8, hizlanma: 5.11 }, indis: { isci: 8, hizlanma: 2.70 } };
const MAKINE_SINIRI = 5.79;        // olculen: bu makinede tek birimin en yuksek hizlanmasi (12 cekirdek)

const seriPay = (n, S) => (1 / S - 1 / n) / (1 - 1 / n);
const amdahl = (s, n) => 1 / (s + (1 - s) / n);
const gerekenBirim = (s, K) => (1 - s) / (1 / K - s);

console.log("bolumleme   olculen(isci/hizlanma)   seri pay   ust sinir(1/s)");
for (const [ad, o] of Object.entries(OLCUM)) {
  const s = seriPay(o.isci, o.hizlanma);
  console.log(`${ad.padEnd(12)}${`${o.isci} / ${o.hizlanma.toFixed(2)}`.padStart(21)}${(s * 100).toFixed(1).padStart(11)}%` +
    `${(1 / s).toFixed(1).padStart(17)}`);
}

const s = seriPay(OLCUM.anahtar.isci, OLCUM.anahtar.hizlanma);
console.log("\nhedef  yatay birim   yatay maliyet   dikey x2.0   dikey x2.5   dikey x3.0   dikey ulasilir");
for (const K of [2, 4, 8, 16]) {
  const birim = gerekenBirim(s, K);
  const dikey = (c) => c ** Math.log2(K);
  console.log(`${`x${K}`.padStart(5)}${(birim > 0 ? birim.toFixed(1) : "ulasilmaz").padStart(13)}` +
    `${(birim > 0 ? birim.toFixed(1) : "-").padStart(16)}${dikey(2.0).toFixed(1).padStart(13)}` +
    `${dikey(2.5).toFixed(1).padStart(13)}${dikey(3.0).toFixed(1).padStart(13)}` +
    `${String(K <= MAKINE_SINIRI).padStart(15)}`);
}

console.log(`\n16 isciye kadar amdahl ongorusu (anahtar bolumlemesi, s = ${(s * 100).toFixed(1)}%):`);
console.log([1, 2, 4, 8, 12, 16].map((n) => `${n}:${amdahl(s, n).toFixed(2)}`).join("  "));
bolumleme   olculen(isci/hizlanma)   seri pay   ust sinir(1/s)
anahtar                  8 / 5.11        8.1%             12.4
indis                    8 / 2.70       28.0%              3.6

hedef  yatay birim   yatay maliyet   dikey x2.0   dikey x2.5   dikey x3.0   dikey ulasilir
   x2          2.2             2.2          2.0          2.5          3.0           true
   x4          5.4             5.4          4.0          6.3          9.0           true
   x8         20.8            20.8          8.0         15.6         27.0          false
  x16    ulasilmaz               -         16.0         39.1         81.0          false

16 isciye kadar amdahl ongorusu (anahtar bolumlemesi, s = 8.1%):
1:1.00  2:1.85  4:3.22  8:5.11  12:6.35  16:7.23

Seri pay satırları bölümleme kararının bedelini tek sayıya indiriyor: anahtar bölümlemesinde %8,1 ve üst sınır 12,4; indis bölümlemesinde %28,0 ve üst sınır 3,6. Aynı iş, aynı donanım — üst sınır üç kattan fazla ayrışıyor. İndis bölümlemesi seçilmiş bir sistemde on altı yerine bin birim eklense hızlanma 3,6’yı geçmez.

Maliyet tablosu iki eğrinin biçimini gösteriyor. Yatay maliyet birim sayısıyla doğrusal ödenir ama gereken birim sayısı doğrusal değildir: iki kat için 2,2 birim, dört kat için 5,4, sekiz kat için 20,8. On altı kat satırında sayı yerine “ulaşılmaz” yazıyor, çünkü hedef seri payın koyduğu 12,4 üst sınırının üstündedir — o hedefe kaç birim eklenirse eklensin varılamaz. Dikey maliyet ise varsayıma tamamen bağlı: çarpan 2,0 ise sekiz kat için 8,0 birim — yataydan ucuz; çarpan 3,0 ise 27,0 birim — yataydan pahalı.

Son sütun tartışmayı bitiriyor. Dikey yolun sekiz katına çıkması bu makinede mümkün değil, çünkü ölçülen en yüksek hızlanma 5,79’dur. Dikey ölçeklemenin asıl sorunu maliyet eğrisi değil, eğrinin bir yerde bitmesidir. Yatay yol pahalıdır ama sınırı seri paydan gelir, donanımdan değil — ve seri pay bir tasarım kararıdır, ölçüm tablosunun ilk satırı bunu gösteriyor.

Son satır modelin kendi sınırını da gösteriyor. Bağıntı on iki işçi için 6,35, on altı işçi için 7,23 hızlanma öngörüyor; ölçülen değerler 5,79 ve 4,51’dir. Model çekirdek sayısını bilmez ve hiç düşmez. Bir modelin öngörüsü ölçümden ayrıldığında düzeltilecek olan ölçüm değil, modelin varsayımıdır — burada eksik varsayım işçi başına bir çekirdeğin bulunmasıdır.

Özet

  • Dikey ölçekleme ölçekleme birimini büyütmek, yatay ölçekleme birim sayısını artırmaktır; ikisi farklı sınırlara çarpar.
  • Anahtar bölümlemesinde hızlanma 12 işçide 5,79’a çıktı, 16 işçide 4,51’e düştü: çekirdek sayısı dikey yolun ölçülen sert sınırıdır.
  • Paralel verimlilik her eklenen işçiyle düşüyor: 2 işçide 0,94, 8 işçide 0,64, 12 işçide 0,48.
  • İşin bölünme biçimi hızlanmayı neredeyse iki katı değiştirdi: sekiz işçide 5,11’e karşı 2,70. İndis bölümlemesi 16 işçide 1 800 000 çakışan kayıt ve 68,3 ms birleştirme üretti, anahtar bölümlemesi 0 ve 6,9 ms.
  • Ölçülen hızlanmadan çıkan seri pay anahtar bölümlemesinde %8,1 (üst sınır 12,4), indis bölümlemesinde %28,0 (üst sınır 3,6); bölümleme anahtarı bir ölçeklenebilirlik kararıdır.
  • Hedefi sekiz katına çıkarmak yatay yolda 20,8 birim ister, dikey yolda bu makinede ulaşılamaz; on altı kat yatay yolda da ulaşılmazdır, çünkü seri pay üst sınırı 12,4’te tutar.

Sonraki Adım

Buraya kadarki üç ders sistemin çalıştığı varsayımıyla ölçüm yaptı. Yük, kapasite, hızlanma ve maliyet — hepsi ayakta bir sistemin sayılarıdır. Ölçeklemenin gerekçesi de tek başına yük değildir: on iki milyon kaydı 289 milisaniyede tarayan bir düzenek zaten yeterken birim sayısını artırmanın başka bir nedeni olmalıdır. O neden birimlerin ayakta kalmamasıdır. Bir birim durduğunda sistemin ne kadarı durur, ne kadar süre durur ve bu süre kabul edilebilir midir? Üçüncü soru bir yüzdeyle yanıtlanır ve yüzde tek başına hiçbir şey söylemez: yüzde doksan dokuz ile yüzde doksan dokuz virgül dokuz arasındaki fark, dakika olarak yazılana kadar görünmez. Sonraki ders hizmet erişilebilirliğini bir kesinti bütçesine çevirir ve bileşenlerin birleştirilmesinin bu bütçeye ne yaptığını hesaplar.

İ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