İçeriğe geç
academia.sh

Ders 02 / 15

Hız Sınırlama Pencereleri

Aynı beş dakikalık yükün üç ayrı hız sınırı penceresinde koşturulması ve pencere biçiminin üç sayıyı nasıl kaydırdığı: sabit pencerenin sınır etkisi, kayan pencerenin meşru tepe yükünden kestiği istek, jeton kovasının tepe payını kurtarırken saldırı tepe yükünü de geçirmesi, meşru tepeyi kurtaran limitin kaç kötü isteği serbest bıraktığı ve kesilen hacmin hangi bölümünün daha erken bir aşamada kapatılabileceği.

İçindekiler

Önceki ders bir kural kümesini tek tek isteklere baktırdı ve kaçan bin iki yüz isteğin tam olarak bu yüzden kaçtığını gösterdi: hiçbiri tek başına yanlış değildi. Yanlışlık isteğin içinde değil, aynı istemciden gelen istek sayısında ve zamanlamasındaydı. Hız sınırlama bu eksene bakan savunmadır; ölçüsü ise durdurduğu kötü istek değil, meşru tepe yükünden kestiğidir.

Kısma algoritmaları daha önce ölçüldü ve burada tekrarlanmıyor. Bu dersin sorusu tektir: aynı nominal sınır üç ayrı pencere biçiminde uygulandığında üç sayı nereye kayıyor. Pencere biçimi bir uygulama ayrıntısı gibi görünür; ölçüldüğünde meşru işin kesilip kesilmediğini belirleyen ana karar çıkar.

Aynı Yük, Üç Pencere Biçimi

Bölgesel ölçüm ağının üretim ortamında beş dakikalık bir dilim alınıyor. Aşağıdaki her şey node ile kurulan bir modeldir; gerçek trafik üretilmiyor, hiçbir istek gövdesi yazılmıyor.

  • US6. Yedi istemci sınıfı, varış serileri ve saniyedeki istek sayıları kurgusal ölçüm ağına aittir. Meşru sınıflardan ikisinin tepe yükü işin kendisidir: vardiya başlangıcında saha uygulamasının açtığı oturumlar ve fatura döneminde çalışan toplu sorgu.
  • US7. Hız sınırı istemci başına tek bir anahtara bağlıdır ve bu derste anahtar sabittir. Anahtarın hesap mı adres mi olduğu ayrı bir eksendir; sonraki ders onu ölçüyor.
  • US8. Varış hızı saniye içinde düzgün dağılıyor. Gerçekte saniye içi kümelenme kayan pencerenin kestiği miktarı bir miktar artırır, yönünü değiştirmez.
  • US9. Üç biçim aynı nominal sınırı taşıyor: dakikada altı yüz istek. Jeton kovası saniyede on jeton ekliyor ve dokuz yüz jetonda doluyor; ortalama hız aynı, tepe payı farklı.
  • US10. “Daha erken kapatma aşaması” sütunu her kötü istemci için elle işaretlendi.
// yuk.mjs — bes dakikalik varis serisi ve uc pencere bicimi; model, gercek trafik degil
// [istemci, gercek sinif, [[t0, t1, saniyedeki istek], ...], daha erken kapatma asamasi]
export const SURE = 300;
export const ISTEMCI = [
  ['abone portali, surekli', 'mesru', [[0, 300, 4]], '-'],
  ['saha uygulamasi, vardiya baslangici', 'mesru', [[0, 110, 2], [110, 150, 30], [150, 300, 2]], '-'],
  ['fatura donemi toplu sorgu', 'mesru', [[0, 45, 1], [45, 65, 40], [65, 300, 1]], '-'],
  ['ic izleme yoklamasi', 'mesru', [[0, 300, 1]], '-'],
  ['is akisi disi toplu indirme', 'kotu', [[0, 300, 9]], 'tasarim'],
  ['sayfa gezinme kaziyicisi', 'kotu', [[0, 300, 26]], 'yok'],
  ['oturum acma denemesi dizisi', 'kotu', [[40, 100, 18], [160, 220, 18]], 'kod'],
];

export const seri = (prof) => {
  const a = new Array(SURE).fill(0);
  for (const [t0, t1, r] of prof) for (let t = t0; t < t1; t++) a[t] = r;
  return a;
};

// sabit pencere: sayac pencere sinirinda sifirlanir
export function sabit(a, limit, pen) {
  let gecen = 0, sayac = 0;
  for (let t = 0; t < a.length; t++) {
    if (t % pen === 0) sayac = 0;
    const izin = Math.min(a[t], Math.max(0, limit - sayac));
    sayac += izin; gecen += izin;
  }
  return gecen;
}

// kayan pencere: son `pen` saniyede kabul edilen istek sayilir
export function kayan(a, limit, pen) {
  let gecen = 0; const kabul = new Array(a.length).fill(0);
  for (let t = 0; t < a.length; t++) {
    let son = 0;
    for (let u = Math.max(0, t - pen + 1); u < t; u++) son += kabul[u];
    kabul[t] = Math.min(a[t], Math.max(0, limit - son));
    gecen += kabul[t];
  }
  return gecen;
}

// jeton kovasi: saniyede `hiz` jeton eklenir, kova `kap` jetonda dolar
export function kova(a, hiz, kap) {
  let gecen = 0, jeton = kap;
  for (let t = 0; t < a.length; t++) {
    jeton = Math.min(kap, jeton + hiz);
    const izin = Math.min(a[t], jeton);
    jeton -= izin; gecen += izin;
  }
  return gecen;
}

export const BICIM = [
  ['sabit pencere 60 s', (a) => sabit(a, 600, 60)],
  ['kayan pencere 60 s', (a) => kayan(a, 600, 60)],
  ['jeton kovasi 10/s', (a) => kova(a, 10, 900)],
];

export function olc(bicim) {
  let yakalanan = 0, kacan = 0, yanlis = 0, mesru = 0;
  for (const [, sinif, prof] of ISTEMCI) {
    const a = seri(prof);
    const toplam = a.reduce((x, y) => x + y, 0);
    const kesilen = toplam - bicim(a);
    if (sinif === 'kotu') { yakalanan += kesilen; kacan += toplam - kesilen; }
    else { yanlis += kesilen; mesru += toplam; }
  }
  return { yakalanan, kacan, yanlis, mesru };
}

Üç biçim arasındaki tek fark, sayacın geçmişi nasıl tuttuğudur. Sabit pencere sayacı dakika başında sıfırlar; önceki dakikanın hiçbir isteğini hatırlamaz. Kayan pencere son altmış saniyede kabul ettiği her isteği hatırlar. Jeton kovası hiç pencere tutmaz: kullanılmayan payı biriktirir ve tek seferde harcamaya izin verir.

Meşru Tepe Yükünden Kesilen İstek

// kosum.mjs — ayni bes dakikalik yuk, degisen tek sey pencere bicimi
import { ISTEMCI, BICIM, seri, olc } from './yuk.mjs';

const s = (x, n) => String(x).padStart(n);
const T = (f) => ISTEMCI.filter(f).reduce((t, k) => t + seri(k[2]).reduce((x, y) => x + y, 0), 0);
console.log(`${ISTEMCI.length} istemci, 300 saniye; toplam ${T(() => true)} istek` +
  ` (${T((k) => k[1] === 'kotu')} kotu / ${T((k) => k[1] === 'mesru')} mesru)\n`);

console.log(`${'istemci'.padEnd(36)}${s('sinif', 6)}${s('istek', 7)}` +
  BICIM.map(([ad]) => s(ad.split(' ')[0], 9)).join('') + s('erken asama', 13));
for (const [ad, sinif, prof, asama] of ISTEMCI) {
  const a = seri(prof), toplam = a.reduce((x, y) => x + y, 0);
  console.log(`${ad.padEnd(36)}${s(sinif, 6)}${s(toplam, 7)}` +
    BICIM.map(([, f]) => s(toplam - f(a), 9)).join('') + s(asama, 13));
}

console.log(`\n(uc sutun: o bicimin kestigi istek sayisi)\n`);
console.log(`${'pencere bicimi'.padEnd(20)}${s('yakalanan', 11)}${s('kacan', 8)}` +
  `${s('yanlis kesilen', 16)}${s('mesru kesme %', 15)}`);
for (const [ad, f] of BICIM) {
  const r = olc(f);
  console.log(`${ad.padEnd(20)}${s(r.yakalanan, 11)}${s(r.kacan, 8)}${s(r.yanlis, 16)}` +
    `${s((100 * r.yanlis / r.mesru).toFixed(1), 15)}`);
}
7 istemci, 300 saniye; toplam 16960 istek (12660 kotu / 4300 mesru)

istemci                              sinif  istek    sabit    kayan    jeton  erken asama
abone portali, surekli               mesru   1200        0        0        0            -
saha uygulamasi, vardiya baslangici  mesru   1720      360      640        0            -
fatura donemi toplu sorgu            mesru   1080       45      240        0            -
ic izleme yoklamasi                  mesru    300        0        0        0            -
is akisi disi toplu indirme           kotu   2700        0        0        0      tasarim
sayfa gezinme kaziyicisi              kotu   7800     4800     4800     3910          yok
oturum acma denemesi dizisi           kotu   2160      240      960        0          kod

(uc sutun: o bicimin kestigi istek sayisi)

pencere bicimi        yakalanan   kacan  yanlis kesilen  mesru kesme %
sabit pencere 60 s         5040    7620             405            9.4
kayan pencere 60 s         5760    6900             880           20.5
jeton kovasi 10/s          3910    8750               0            0.0

Üç satır aynı nominal sınırı taşıyor ve üçünde de üç sayı farklı. Kayan pencere en çok kötü isteği durduruyor: beş bin yedi yüz altmış. Aynı satırda meşru yükün yüzde 20,5’i kesilmiş; kesilenler de rastgele istekler değil, vardiya başlangıcında saha uygulamasının açtığı altı yüz kırk oturum ile fatura döneminin iki yüz kırk sorgusudur. Sabit pencere daha az kesiyor, çünkü sayacı dakika sınırında sıfırlıyor; vardiya tepesi iki pencereye bölündüğü için üç yüz altmış istekle kurtuluyor. Aynı sıfırlama kötü tarafta da çalışıyor: oturum açma denemesi dizisinden yalnız iki yüz kırk istek kesiliyor, çünkü altmış saniyelik iki tepe de sınırın iki yanına dağılıyor. Sınır etkisi kimin lehine çalışacağını seçmez.

Jeton kovası satırı en dikkat çekici olanıdır. Meşru istekten hiçbir şey kesilmiyor: dokuz yüz jetonluk birikim vardiya tepesini de fatura tepesini de bütünüyle karşılıyor. Aynı birikim oturum açma denemesi dizisini de bütünüyle karşılıyor — iki bin yüz altmış isteğin tamamı geçiyor. Kova bir tepenin meşru mu olduğunu sormaz; yalnızca birikmiş pay olup olmadığına bakar. Tepe payı tanımak isteyen bir savunma, tepe biçimindeki saldırıyı da tanımak zorunda kalır.

İki bin yedi yüz isteklik iş akışı dışı toplu indirme ise üç biçimin hiçbirinde kesilmiyor. Saniyede dokuz istek, dakikada altı yüzlük sınırın altındadır; istemci sınırın hemen altında sabit hızla çalıştığı sürece hiçbir pencere biçimi onu görmez. Önceki dersin kural kümesinden kaçan kalem, hız sınırından da kaçıyor.

Limiti Yükseltmenin Bedeli

Kayan pencerenin kestiği sekiz yüz seksen meşru istek üretimde bir aksama demektir: vardiya başlangıcında saha ekibinin bir bölümü uygulamaya giremez. Verilen ilk yanıt limiti yükseltmektir.

// limit.mjs — mesru tepe yukunu kurtarmak icin limiti yukseltmenin bedeli
import { ISTEMCI, seri, kayan, kova } from './yuk.mjs';

const s = (x, n) => String(x).padStart(n);
const KOTU = ISTEMCI.filter((k) => k[1] === 'kotu');
const MESRU = ISTEMCI.filter((k) => k[1] === 'mesru');
const dizi = ISTEMCI.find((k) => k[0].startsWith('oturum'));

const kes = (liste, f) => liste.reduce((t, k) => {
  const a = seri(k[2]); return t + a.reduce((x, y) => x + y, 0) - f(a);
}, 0);

console.log(`kayan pencere, 60 s; degisen tek sey limit`);
console.log(`${'limit'.padEnd(7)}${s('yanlis kesilen', 16)}${s('yakalanan', 11)}${s('kacan', 8)}` +
  `${s('gecen oturum denemesi', 23)}`);
for (const L of [600, 800, 1000, 1240, 1500]) {
  const f = (a) => kayan(a, L, 60);
  console.log(`${String(L).padEnd(7)}${s(kes(MESRU, f), 16)}${s(kes(KOTU, f), 11)}` +
    `${s(12660 - kes(KOTU, f), 8)}${s(f(seri(dizi[2])), 23)}`);
}

console.log(`\njeton kovasi, 10/s; degisen tek sey kova kapasitesi`);
console.log(`${'kapasite'.padEnd(10)}${s('yanlis kesilen', 16)}${s('yakalanan', 11)}${s('kacan', 8)}` +
  `${s('gecen oturum denemesi', 23)}`);
for (const K of [200, 400, 600, 900, 1200]) {
  const f = (a) => kova(a, 10, K);
  console.log(`${String(K).padEnd(10)}${s(kes(MESRU, f), 16)}${s(kes(KOTU, f), 11)}` +
    `${s(12660 - kes(KOTU, f), 8)}${s(f(seri(dizi[2])), 23)}`);
}
kayan pencere, 60 s; degisen tek sey limit
limit    yanlis kesilen  yakalanan   kacan  gecen oturum denemesi
600                 880       5760    6900                   1200
800                 480       4360    8300                   1600
1000                240       2960    9700                   2000
1240                  0       1600   11060                   2160
1500                  0        300   12360                   2160

jeton kovasi, 10/s; degisen tek sey kova kapasitesi
kapasite    yanlis kesilen  yakalanan   kacan  gecen oturum denemesi
200                   1020       5190    7470                   1580
400                    620       4590    8070                   1980
600                    220       4210    8450                   2160
900                      0       3910    8750                   2160
1200                     0       3610    9050                   2160

İki tablo aynı şeyi iki ayrı ayarla söylüyor. Kayan pencerede yanlış kesilen istek ancak limit bin iki yüz kırka çıktığında sıfıra iner; vardiya tepesinin altmış saniyelik en yoğun dilimi tam olarak o kadardır. Aynı satırda yakalanan istek beş bin yedi yüz altmıştan bin altı yüze düşmüş, kaçan istek altı bin dokuz yüzden on bir bin altmışa çıkmıştır. Son sütun kararın asıl bedelini yazıyor: oturum açma denemesi dizisinin tamamı geçmeye başlıyor.

Jeton kovası tablosu aynı eğriyi kapasite ekseninde tekrarlıyor. Meşru tepenin sıfır kesmeyle geçtiği ilk kapasite dokuz yüzdür; oturum açma dizisinin tamamının geçtiği ilk kapasite ise altı yüz. Yani kova, meşru tepeyi kurtaracak kadar gevşetilmeden önce saldırı tepesini kurtarmış oluyor. Sıralama tersine çevrilemez, çünkü her iki tepe de aynı şeydir: kısa süreli yüksek hız.

Buradan çıkan bir ayar önerisi değil, bir sınırdır: tepe yükü işin kendisi olan bir sistemde tek anahtara bağlı tek eşikli hız sınırı, meşru tepeyi geçirmek için saldırı tepesini de geçirmek zorundadır. Ayrım hızda değil, isteğin neye yapıldığındadır; hız sayacı bunu görmez.

Kesilenin Nereye Ait Olduğu

// asama.mjs — kesilen kotu istegin kaci daha ucuz bir asamada kapatilabilirdi
import { ISTEMCI, BICIM, seri, kayan } from './yuk.mjs';

const s = (x, n) => String(x).padStart(n);
const KOTU = ISTEMCI.filter((k) => k[1] === 'kotu');
const MESRU = ISTEMCI.filter((k) => k[1] === 'mesru');
const ASAMA = ['tasarim', 'kod', 'yok'];
const kesilen = (k, f) => { const a = seri(k[2]); return a.reduce((x, y) => x + y, 0) - f(a); };

console.log(`${'pencere bicimi'.padEnd(20)}${ASAMA.map((a) => s(a, 10)).join('')}${s('yalniz burada %', 17)}`);
for (const [ad, f] of BICIM) {
  const pay = ASAMA.map((a) => KOTU.filter((k) => k[3] === a).reduce((x, k) => x + kesilen(k, f), 0));
  const t = pay.reduce((x, y) => x + y, 0);
  console.log(`${ad.padEnd(20)}${pay.map((p) => s(p, 10)).join('')}${s((100 * pay[2] / t).toFixed(1), 17)}`);
}

console.log(`\nkayan pencere; is akisi disi toplu indirmeyi (9/s) yakalamak icin gereken limit`);
console.log(`${'limit'.padEnd(7)}${s('toplu indirmeden kesilen', 26)}${s('mesru kesilen', 15)}${s('mesru kesme %', 15)}`);
const indirme = KOTU.find((k) => k[3] === 'tasarim');
for (const L of [600, 540, 480, 360]) {
  const f = (a) => kayan(a, L, 60);
  const m = MESRU.reduce((x, k) => x + kesilen(k, f), 0);
  console.log(`${String(L).padEnd(7)}${s(kesilen(indirme, f), 26)}${s(m, 15)}${s((100 * m / 4300).toFixed(1), 15)}`);
}
pencere bicimi         tasarim       kod       yok  yalniz burada %
sabit pencere 60 s           0       240      4800             95.2
kayan pencere 60 s           0       960      4800             83.3
jeton kovasi 10/s            0         0      3910            100.0

kayan pencere; is akisi disi toplu indirmeyi (9/s) yakalamak icin gereken limit
limit    toplu indirmeden kesilen  mesru kesilen  mesru kesme %
600                             0            880           20.5
540                             0           1000           23.3
480                           300           1120           26.0
360                           900           1360           31.6

Üst tablo katmanın gerçek payını veriyor. Sayfa gezinme kazıyıcısı hiçbir erken aşamada kapatılamaz: istekler tek tek meşrudur, kimliği doğrulanmıştır, yetkisi vardır ve yalnız birim zamandaki sayıları anlamlıdır. Üç biçimde de kesilen dört bin sekiz yüz — kovada üç bin dokuz yüz on — istek bu sınıftandır ve bu katmanın varlık nedeni tam olarak budur. Oturum açma denemesi dizisi ise farklı bir yere aittir: kod aşamasında ikinci etken zorunlu kılındığında dizinin değeri kalmaz, çünkü doğru parola bulunsa bile oturum açılmaz. Hız sınırının o dizide kestiği iki yüz kırk ya da dokuz yüz altmış istek, kapatılabilir bir kalemi her gün yeniden yakalamaktır.

Alt tablo tasarım aşamasına ait kalemi gösteriyor. İş akışı dışı toplu indirmeyi hız sınırıyla görmek için limitin dört yüz seksene indirilmesi gerekiyor; o ayarda bu istemciden kesilen istek üç yüz, meşru yükten kesilen ise bin yüz yirmidir — meşru işin dörtte biri. Limit üç yüz altmışa inince kesilen kötü istek dokuz yüze çıkıyor, meşru kesme yüzde 31,6’ya. Bu kalem hız sınırıyla ancak sistemi kullanılamaz hâle getirerek kapatılabilir; doğru yeri tasarımdır. Toplu indirme bir kuyruk işine alındığında uçta sınırın altında sabit hızla çalışma imkânı kalmaz.

Özet

  • Aynı nominal sınır (dakikada 600 istek) üç pencere biçiminde üç ayrı sonuç verdi: sabit pencere 5.040 / 7.620 / 405, kayan pencere 5.760 / 6.900 / 880, jeton kovası 3.910 / 8.750 / 0 (yakalanan / kaçan / yanlış kesilen).
  • Kayan pencere meşru yükün yüzde 20,5’ini kesti ve kesilenler vardiya başlangıcı ile fatura dönemi tepesiydi. Sabit pencerenin daha az kesmesi bir üstünlük değil, sınır etkisinin yan ürünüdür: aynı sıfırlama oturum açma dizisini de 960 yerine 240 istekle kurtardı.
  • Jeton kovası meşru tepeden hiçbir şey kesmedi ama oturum açma denemesi dizisinin 2.160 isteğinin tamamını geçirdi. Kova kapasitesi meşru tepeyi kurtaracak kadar açıldığında (900) saldırı tepesi zaten 600 kapasitede kurtulmuştu.
  • Kayan pencerede yanlış kesilen isteği sıfırlayan limit 1.240’tır; o ayarda yakalanan 5.760’tan 1.600’e düşer, kaçan 11.060’a çıkar. Tepe yükü işin kendisi olan bir sistemde tek eşikli hız sınırı meşru tepeyi geçirmek için saldırı tepesini de geçirir.
  • Kesilen hacmin yalnız burada kapatılabilen bölümü kazıyıcıdır (4.800 / 3.910). Oturum açma dizisi kod aşamasında ikinci etkenle, iş akışı dışı toplu indirme tasarımda kuyruk işiyle kapanır; ikincisini hız sınırıyla görmek limiti 480’e indirmeyi, yani meşru işin dörtte birini kesmeyi gerektirir.

Sonraki Adım

Bu dersin sayacı tek bir anahtara bağlıydı ve o anahtarın ne olduğu hiç sorulmadı. Oturum açma denemesi dizisinin kesilen ya da kaçan bölümü de tam olarak bu yüzden belirsiz kaldı: dizinin saniyedeki hızı biliniyordu, ama denemelerin kaç hesaba ve kaç adrese dağıldığı bilinmiyordu. Aynı toplam hız, tek hesaba yüklendiğinde başka, on bin hesaba tek deneme olarak dağıtıldığında bambaşka bir saldırı biçimidir ve ikincisi bu dersin hiçbir ayarında görünmez. Sonraki ders eşiği hız ekseninden çıkarıp hesap ve adres eksenine taşıyor, hangi eşiğin hangi saldırı biçimini durdurduğunu ayırıyor ve aynı üç sayıyı orada soruyor — özellikle kilitlenen meşru hesabı.

İ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