İçeriğe geç
academia.sh

Ders 10 / 14

Nesne Depolama Servisi

Hacmin tasarımı belirlediği ilk vaka: parça boyunun kesinti olasılığına göre seçilmesi, fazladan gönderilen baytın süreç içi bir modelde ölçülüp kapalı biçimle doğrulanması, kopya sayısı ile silme kodlamasının saklama katı ve yıllık kayıp üzerinden karşılaştırılması ve küçük nesnede kodlamanın istek sayısıyla elenmesi.

İçindekiler

Önceki konu beş vakayı yazma tarafından savundu ve hepsinde ortak bir nokta vardı: veri küçüktü ve kayıt başına anlamlıydı. Tasarımı hacim değil, kaydın doğruluğu, sırası ve teslim garantisi belirledi.

Bu konu o varsayımı kaldırır. Burada tek bir kayıt megabaytlar tutar ve saklama kalemi ötekileri gölgeler. İlk vaka bir nesne deposu (object storage) servisidir: istemci bir anahtarla ikili bir gövde yazar, sonra aynı anahtarla okur. Ölçülecek iki karar var — parçalı yüklemenin (multipart upload) fazladan gönderdiği bayt ve verinin kaç kopya tutulacağı. Bu dersin dayanıklılığı (veri) verinin kaybolmamasıdır; Dayanıklılık ve Güvenilirlik kursunun dayanıklılığı hizmetin ayakta kalmasıydı — bir depo ayakta olup veri kaybedebilir.

Kısıtlar

İşlevsel gereksinim: anahtarla nesne yazma, okuma, silme, önek listeleme; büyük nesnelerde parça parça gönderme, sonda birleştirme, yarım kalanı sürdürme.

İşlevsel olmayan gereksinim, sayıyla: nesne başına fazladan gönderilen bayt — yeniden gönderim ve parça ek yükü — nesne boyunun yüzde 2’sini geçmez; yıllık kaybolan nesne 500’ün altında kalır; saklanan bayt ham verinin 1,5 katını aşmaz; küçük nesne okuması tek istekle karşılanır.

Kapsam daraltması: erişim denetimi ve imza doğrulama (Backend müfredatının Kimlik ve Yetki kursu), sürümleme, yaşam döngüsü, çapraz bölge kopya ve içerikte arama tasarlanmaz.

Varsayımlar

Kod Varsayım Değer Gerekçe
ND1 günlük yüklenen nesne 1.200.000 uçların günlük toplamı
ND2 küçük nesnelerin payı 0,92 küçük ekler sayıca hâkim
ND3 küçük nesne ortalama boyu 180 KB belge ve küçük görsel
ND4 büyük nesne ortalama boyu 90 MB kayıt ve yedek dosyası
ND5 nesne başına ömür boyu okuma 4 nesne birkaç kez okunur
ND6 tepe çarpanı 3 tepe saatin gün ortalamasına oranı
ND7 kesintiler arası gönderilen bayt 250 MB uçlar hareketli ağlarda
ND8 parça başına ek yük 8 KB başlık, imza, parça defteri kaydı
ND9 yıllık düğüm kayıp olasılığı 0,01 donanım ve işletim kaynaklı
ND10 saklama süresi 365 gün sözleşme yıllık dönem üzerinden
ND11 nesne üstverisi kaydı 400 bayt anahtar, boy, sağlama, yerleşim
ND12 en yavaş yükleme ucu 1 Mbit/s vale anahtarı ömrünün alt sınırı

Ölçek

// nesne-olcek.mjs — ND tablosundan cikan olcek hesabi; hepsi aritmetiktir
const ND = { nesne: 1_200_000, kucukPay: 0.92, kucukBayt: 180e3, buyukBayt: 90e6, okuma: 4,
  tepe: 3, kesintiBayt: 250e6, parcaEkYuk: 8192, saklamaGun: 365, ustveriBayt: 400,
  parcaBoyu: 2e6, yavasUcMbit: 1 };
const GUN = 86_400;
const ortBayt = ND.kucukPay * ND.kucukBayt + (1 - ND.kucukPay) * ND.buyukBayt;
const gunlukBayt = ND.nesne * ortBayt, nesneSn = ND.nesne / GUN;
const parcaSn = nesneSn * ((1 - ND.kucukPay) * Math.ceil(ND.buyukBayt / ND.parcaBoyu) + ND.kucukPay);

const r = {
  "buyuk nesnelerin bayt payi": ((1 - ND.kucukPay) * ND.buyukBayt) / ortBayt,
  "gunluk yuklenen TB": gunlukBayt / 1e12,
  "ham saklanan PB": (gunlukBayt * ND.saklamaGun) / 1e15,
  "tepe yukleme nesne/s": nesneSn * ND.tepe,
  "tepe parca istegi/s": parcaSn * ND.tepe,
  "parcalamanin istek carpani": parcaSn / nesneSn,
  "tepe yukleme Mbit/s": (gunlukBayt / GUN) * ND.tepe * 8 / 1e6,
  "tepe okuma Mbit/s": (gunlukBayt / GUN) * ND.okuma * ND.tepe * 8 / 1e6,
  "ustveri saklanan GB": (ND.nesne * ND.saklamaGun * ND.ustveriBayt) / 1e9,
  "nesne/ustveri hacim orani": ortBayt / ND.ustveriBayt,
};
for (const [ad, d] of Object.entries(r))
  console.log(ad.padEnd(28) + (Number.isInteger(d) ? String(d) : d.toFixed(2)).padStart(10));

const en = (k) => Math.sqrt(2 * ND.parcaEkYuk * ND.kesintiBayt * k);   // en dusuk fazladan bayt
console.log(`\nen iyi parca boyu = karekok(2 * ek yuk * kesinti araligi) = ${(en(1) / 1e6).toFixed(2)} MB` +
  `; ND7 x2 -> ${(en(2) / 1e6).toFixed(2)} MB, ND7 x0.5 -> ${(en(0.5) / 1e6).toFixed(2)} MB`);
console.log(`en yavas ucta 2 MB parca ${(ND.parcaBoyu * 8) / (ND.yavasUcMbit * 1e6)} s -> ` +
  `vale anahtari omru 60 s (x${(60 / ((ND.parcaBoyu * 8) / (ND.yavasUcMbit * 1e6))).toFixed(2)} pay)`);
buyuk nesnelerin bayt payi        0.98
gunluk yuklenen TB                8.84
ham saklanan PB                   3.23
tepe yukleme nesne/s             41.67
tepe parca istegi/s             188.33
parcalamanin istek carpani        4.52
tepe yukleme Mbit/s            2455.20
tepe okuma Mbit/s              9820.80
ustveri saklanan GB             175.20
nesne/ustveri hacim orani     18414.00

en iyi parca boyu = karekok(2 * ek yuk * kesinti araligi) = 2.02 MB; ND7 x2 -> 2.86 MB, ND7 x0.5 -> 1.43 MB
en yavas ucta 2 MB parca 16 s -> vale anahtari omru 60 s (x3.75 pay)

Bu sayılar hesap sınıfındadır ve dördü tasarımı belirliyor. Nesnelerin yüzde 8’i baytların yüzde 98’ini taşıyor, bu yüzden iki boy sınıfı ayrı tasarlanır. Üstveri hacmi nesne hacminin 18.414’te biri: 3,23 PB’a karşılık 175,20 GB. Parçalama istek sayısını 4,52 katına çıkarıyor: 41,67 nesne yüklemesi tepe uçta 188,33 parça isteği. Tepe okuma çıkışı 9820,80 Mbit/s — baskın kalem istek değil, bant genişliği ve saklamadır.

Parça Boyu

Kesinti bir akışı kestiğinde o ana kadar gönderilmiş baytlar boşa gitmiştir. Parça küçüldükçe boşa giden bayt azalır, ama ek yük (ND8) her parçada ödenir. İki eğrinin toplamı ölçülür.

// parca-boyu.mjs — surec ici model: 90 MB'lik nesnenin parcali yuklenmesi. Ag yok, dosya yok;
// kesinti, gonderilen bayt uzerinde ustel araliklarla uretilir. Tohum gorunur, uretec kendi yazilir.
const NESNE = 90e6, KESINTI = 250e6, EK = 8192, KOSU = 5000, TOHUM = 20260730, L = 1 / KESINTI;
let durum = TOHUM;                                  // dogrusal eslesik uretec
const rast = () => { durum = (durum * 1103515245 + 12345) % 2147483648; return durum / 2147483648; };
const aralik = () => -Math.log(1 - rast()) / L;     // kesintiye kadar gonderilen bayt

function yukle(boy) {
  let gonderilen = 0;
  for (let kalan = NESNE; kalan > 0; kalan -= boy) {
    const p = Math.min(boy, kalan);
    for (;;) {
      gonderilen += EK;                             // her deneme bir istek turu
      const g = aralik();
      if (g >= p) { gonderilen += p; break; }
      gonderilen += g;                              // kesildi: bu parca bastan gonderilir
    }
  }
  return gonderilen;
}

function beklenen(boy) {                            // ayni bolumleme uzerinde kapali bicim
  let t = 0;
  for (let kalan = NESNE; kalan > 0; kalan -= boy) {
    const p = Math.min(boy, kalan);
    t += (Math.exp(L * p) - 1) / L + EK * Math.exp(L * p);
  }
  return t;
}

console.log(`model: ${KOSU} yukleme, nesne ${NESNE / 1e6} MB, kesinti araligi ${KESINTI / 1e6} MB, ` +
  `parca ek yuku ${EK} bayt, tohum ${TOHUM}\n\n` + "parca".padStart(9) + "parca sayisi".padStart(14) +
  "gonderilen MB".padStart(15) + "olculen oran".padStart(14) + "beklenen oran".padStart(15));
for (const b of [256e3, 1e6, 2e6, 4e6, 16e6, NESNE]) {
  let t = 0;
  for (let i = 0; i < KOSU; i += 1) t += yukle(b);
  const ort = t / KOSU;
  console.log((b === NESNE ? "tek akis" : `${(b / 1e6).toFixed(2)} MB`).padStart(9) +
    String(Math.ceil(NESNE / b)).padStart(14) + (ort / 1e6).toFixed(2).padStart(15) +
    (ort / NESNE).toFixed(4).padStart(14) + (beklenen(b) / NESNE).toFixed(4).padStart(15));
}
model: 5000 yukleme, nesne 90 MB, kesinti araligi 250 MB, parca ek yuku 8192 bayt, tohum 20260730

    parca  parca sayisi  gonderilen MB  olculen oran  beklenen oran
  0.26 MB           352          92.94        1.0326         1.0326
  1.00 MB            90          90.91        1.0101         1.0102
  2.00 MB            45          90.68        1.0075         1.0081
  4.00 MB            23          90.90        1.0100         1.0101
 16.00 MB             6          93.11        1.0346         1.0319
 tek akis             1         108.14        1.2015         1.2038

Ölçülen oran bu koşumda geçerli bir ölçüm, beklenen oran koşumdan bağımsız bir hesaptır; ikisi dört basamağın üçünde örtüşüyor. Yüzde 2 kısıtı eğrinin iki ucunu kesiyor: 256 KB’da fazladan bayt yüzde 3,26 ve sebebi yeniden gönderim değil, 352 parçanın ek yüküdür; 16 MB’da yüzde 3,46 ve sebep yeniden gönderimdir. Kısıtı yalnız 1 MB ile 4 MB arası karşılıyor, en iyi nokta 2 MB’da ve kapalı çözümle aynı yerde. Tek akışta 90 MB için ortalama 108,14 MB gidiyor; bu oran nesne büyüdükçe kötüleşir çünkü beklenen bayt nesne boyuyla üstel büyür.

Duyarlılık: ND7 iki katına çıkarsa en iyi parça boyu 2,86 MB’a, yarıya inerse 1,43 MB’a kayar; ND7 dört kat değiştiğinde parça boyu iki kat. Karar ND7’ye zayıf bağlı, 2 MB geniş bir aralıkta savunulabilir.

Tasarım

Yükleme yolu vale anahtarı kalıbına dayanır (Dayanıklılık ve Güvenilirlik kursunun Dağıtık Doğruluk konusu). Servis baytları taşımaz; istemciye tek bir parçaya yazma yetkisi veren süreli bir anahtar döner. Anahtarın kapsamı bir yükleme kimliği ile bir parça numarası, ömrü 60 saniyedir — en yavaş uçta 2 MB’lık parça 16 saniye sürdüğü için 3,75 kat pay kalır. Bu seçim 2455,20 Mbit/s yükleme trafiğini servis düğümlerinin dışında tutar. Parça yazma etkisizdir (aynı konu, Etkisiz İşlemler); tekillik anahtarı aynı ikilidir, dolayısıyla istemci hangi parçanın gittiğinden emin olmadan gönderebilir.

Üstveri deposu anahtar–değer deposudur (Veri Katmanı Ölçekleme kursunun Veri Dağıtımı konusu, Depo Türleri) ve nesne anahtarının karmasına göre parçalanır (aynı konu, Parçalama). Aralık değil karma seçilir: ortak önek altında toplanan anahtarlar aralık bölümlemesinde tek parçayı ısıtır. Yarım kalan yüklemelerin parçalarını zamanlayıcı–aracı–denetçi kalıbı toplar (Uygulama Katmanı ve Servis Etkileşimi kursunun Kuyruklar ve İş Akışları konusu); penceresi 7 gün.

Bilerek kullanılmayan kalıp: önbellek. Veri Katmanı Ölçekleme kursunun Önbellek Mimarisi konusundaki yanında okuma buraya konmaz: bir nesne ömrü boyunca yalnız 4 kez okunuyor (ND5) ve günlük 8,84 TB’ın kesrini tutacak katmanın maliyeti dört okumaya değmez. İkincisi oturum yapışkanlığıdır (Trafik Katmanı kursunun Yük Dengeleme konusu): parça yazmaları durumsuzdur.

Elenen Alternatif: Her Nesne Üç Kopya

En basit tasarım her nesneyi üç kopya tutmaktır. Alternatif silme kodlamasıdır (erasure coding): nesne k veri parçasına bölünür, m eşlik parçası hesaplanır, k+m parçadan herhangi k tanesi nesneyi geri verir.

// dayaniklilik.mjs — kopya sayisi ile silme kodlamasinin saklama, kayip ve istek maliyeti.
// Kayiplar bagimsiz varsayilir (ND9); bu bir model kabuludur, olcum degil.
let P = 0.01;                                             // ND9
const N = 1_200_000 * 365, KUCUK = 180e3, EK = 8192;      // ND1xND10, ND3, ND8
const TEPE = (1_200_000 * 4 / 86_400) * 3 * 0.92;         // nesne-olcek.mjs: kucuk nesne okuma/s
const bin = (n, i) => { let t = 1; for (let j = 0; j < i; j += 1) t = (t * (n - j)) / (j + 1); return t; };
const kayip = (k, m) => {
  if (m === 0) return P ** k;                             // k kopyanin hepsi kaybolur
  let t = 0;                                              // k+m parcadan m+1 ve fazlasi kaybolur
  for (let i = m + 1; i <= k + m; i += 1) t += bin(k + m, i) * P ** i * (1 - P) ** (k + m - i);
  return t;
};

console.log(`ND9 = ${P}, saklanan nesne ${N.toLocaleString("tr-TR")}\n\n` + "kurulum".padEnd(14) +
  "saklama kati".padStart(13) + "kaybolan nesne/yil".padStart(19) + "okuma istegi".padStart(14) +
  "180 KB'de ek yuk".padStart(18));
for (const [ad, k, m] of [["3 kopya", 3, 0], ["silme (6,3)", 6, 3], ["silme (10,4)", 10, 4],
  ["silme (12,4)", 12, 4]])
  console.log(ad.padEnd(14) + (m === 0 ? k : (k + m) / k).toFixed(2).padStart(13) +
    (kayip(k, m) * N).toFixed(2).padStart(19) + String(m === 0 ? 1 : k).padStart(14) +
    `%${((((m === 0 ? k : k + m) * EK) / KUCUK) * 100).toFixed(1)}`.padStart(18));

const HAM = 3.23, BUYUK_PAY = 0.9775;                     // nesne-olcek.mjs
const karma = BUYUK_PAY * 1.4 + (1 - BUYUK_PAY) * 3;      // buyuk silme (10,4), kucuk 3 kopya
// GB-ay: dogrusal buyumede yil ortalamasi yil sonunun yarisi, 12 ayla carpilir -> x6
console.log(`\nyil sonu saklama: hepsi silme (10,4) ${(HAM * 1.4).toFixed(2)} PB, karma ` +
  `(kat ${karma.toFixed(3)}) ${(HAM * karma).toFixed(2)} PB, hepsi 3 kopya ${(HAM * 3).toFixed(2)} PB`);
console.log(`karmanin fazlasi ${(HAM * (karma - 1.4) * 6e6).toFixed(0)} GB-ay; kucuk nesnede tepe ` +
  `parca istegi/s 3 kopya ${TEPE.toFixed(2)}, silme (10,4) ${(TEPE * 10).toFixed(2)}\n`);
for (const p of [0.005, 0.01, 0.02, 0.04]) {
  P = p;
  console.log(`ND9=${p} -> 3 kopya ${kayip(3, 0).toExponential(2)}, silme (10,4) ` +
    `${kayip(10, 4).toExponential(2)}, oran ${(kayip(3, 0) / kayip(10, 4)).toFixed(2)}`);
}
ND9 = 0.01, saklanan nesne 438.000.000

kurulum        saklama kati kaybolan nesne/yil  okuma istegi  180 KB'de ek yuk
3 kopya                3.00             438.00             1             %13.7
silme (6,3)            1.50             530.17             6             %41.0
silme (10,4)           1.40              81.33            10             %63.7
silme (12,4)           1.33             174.51            12             %72.8

yil sonu saklama: hepsi silme (10,4) 4.52 PB, karma (kat 1.436) 4.64 PB, hepsi 3 kopya 9.69 PB
karmanin fazlasi 697680 GB-ay; kucuk nesnede tepe parca istegi/s 3 kopya 153.33, silme (10,4) 1533.33

ND9=0.005 -> 3 kopya 1.25e-7, silme (10,4) 6.03e-9, oran 20.74
ND9=0.01 -> 3 kopya 1.00e-6, silme (10,4) 1.86e-7, oran 5.39
ND9=0.02 -> 3 kopya 8.00e-6, silme (10,4) 5.51e-6, oran 1.45
ND9=0.04 -> 3 kopya 6.40e-5, silme (10,4) 1.51e-4, oran 0.42

Üç kopya iki sayıda birden eleniyor. Saklama kısıtı 1,50 katken üç kopyanın katı 3,00; silme (10,4) 1,40 ile altında kalıyor ve dayanıklılıkta da kazanıyor: yılda 438 nesne yerine 81,33. Silme (6,3) yıllık kaybı 530,17 ile “500’ün altında” kısıtını çiğniyor; silme (12,4) saklamada en ucuzu ama kaybı (10,4)’ün iki katından fazla.

Alternatifin kazandığı yer küçük nesnededir. Silme (10,4) 180 KB’lık nesneyi 18 KB’lık on dört parçaya bölüyor: ek yük nesnenin yüzde 63,7’sine çıkıyor, okuma on istek gerektiriyor — tepe uçta 153,33 yerine 1533,33 parça isteği, yani “tek istekle karşılanır” kısıtının çiğnenmesi. Seçim bu yüzden karmadır: küçük nesne üç kopya, büyük nesne silme (10,4), kat 1,436.

Hangi kısıt değişirse alternatif kazanır: ND9 binde beşe inerse kodlamanın üstünlüğü 20,74 kata çıkar; yüzde 2’de 1,45’e, yüzde 4’te 0,42’ye düşer — düğüm kaybı sıklaştığında üç kopya öne geçer, çünkü on dört parçanın üçünü değil kopyaların tamamının kaybını gerektirir. Bağımsız kayıp bir model kabulüdür; kayıplar ilişkiliyse iki sütun da iyimserdir.

Arıza Davranışı ve Feda Edilen

Üstveri deposunun bir parçası düştüğünde o karma aralığında yeni yükleme başlatılamaz ve birleştirme yapılamaz; ama sürmekte olan parça yazmaları vale anahtarıyla doğrudan depoya gittiği için devam eder. Bu, zarif bozulmadır (Dayanıklılık ve Güvenilirlik kursunun Arıza Yalıtımı konusu): yükleme durmaz, yalnız tamamlanamaz ve istemci 7 günlük pencere içinde tamamlayabilir. Bir depolama düğümü kaybında silme (10,4) nesnesi 14 parçadan 13’üyle okunur; onarım k = 10 parça okuduğu için trafiği nesne boyu kadardır. Parça isteği tepe hızı 188,33/s olduğundan yükleme yoluna kısıtlama (aynı konu) bu hızın üstünde bir eşikle konur.

Feda edilen: küçük nesnelerde saklama katı 3’te bırakıldı. Her şey kodlansaydı yıl sonu 4,52 PB olurdu, karma seçim 4,64 PB: yılda 697.680 GB-ay fazladan saklama — küçük nesne okumasını 1533,33 yerine 153,33 parça isteğinde tutmanın bedeli.

Özet

  • Nesnelerin yüzde 8’i baytların yüzde 98’ini taşıyor; iki boy sınıfı ayrı tasarlanır ve parçalı yükleme istek sayısını 4,52 katına çıkarır: tepe uçta 188,33 parça isteği.
  • Parça boyu bir çukurun dibinden seçilir: fazladan bayt oranı 256 KB’da 1,0326, 2 MB’da 1,0075, 16 MB’da 1,0346, tek akışta 1,2015 ve en iyi nokta karekök(2 × ek yük × kesinti aralığı) formülüyle aynı yerde.
  • Üç kopya iki sayıda birden elenir: saklama katı 3,00’e karşı 1,40, yıllık kayıp 438’e karşı 81,33 nesne; ama küçük nesnede kodlama okumayı 153,33’ten 1533,33 isteğe çıkardığı için orada üç kopya kazanır.
  • Seçim ND9’a bağlıdır: kayıp olasılığı yüzde 4’e çıkarsa oran 0,42 olur, üç kopya öne geçer.

Sonraki Adım

Bu vakada nesne anlamsız bir bayt dizisiydi: yazıldığı gibi saklandı, okunduğu gibi verildi, içeriğine bakılmadı. Sonraki vaka bu varsayımı kaldırır: yüklenen dosyanın birden çok temsili üretilir ve hangisinin verileceği isteği yapanın o andaki koşullarına göre belirlenir. Soru şuna dönüşür: aynı içerik kaç biçimde tutulur, bunları üretmenin işlemci maliyeti ile sakladıkları bayt nasıl karşılaştırılır ve fazladan bir biçim ne zaman israftır.

İ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