90 milionů vs. 500
Představte si, máte 90 milionů klientských stanic a všechny se ihned po plošné aktualizaci mají nahlásit centrálnímu serveru pro specifický update. Na konci máte stroj, který zvládá max. 500 spojení za sekundu. To je 30 tisíc za minutu. Necelé dva miliony za hodinu a tak dále.
Ať počítám, jak počítám, server to nemůže přežít. Už jen proto, že těch cca 70 milionů z 90 milionů requestů by nejspíš normálně přišlo během prvních dvou hodin a zbytek by se nějak trousil.
Zaplatit prachy, nebo napsat script?
Možnosti, jak toto vyřešit jsem si nalajnoval dvě.
- Zaplatit prachy a naškálovat servery, které poběží hodinu v roce na plný výkon a pak se budou 364 dní mrcasit na 10% výkon.
- Napsat nějaký omezovač requestů.
Kdybych zvolil variantu 1, nadpis článku by byl: „Jak zbytečně naškálovat servery a promrcasit prachy klientovi“.
Jak omezit 90 milionů zařízení, které spolu navzájem nekomunikují, neví o sobě a chtějí se doptávat centrálního serveru? (PS: toto je jistě úloha, kterou musí řešit třeba tvůrci botnetů).
Vyšší matematika
90 milionů / 500 (výkon serveru za sekundu) = 180 000 serversekund
180 000 / 60 = 3000 serverminut a to je 50 serverhodin, což jsou dva serverdny.
Velká část klientských stanic je obsluhována lidmi a ti v čase od 23 do 5 ráno spíše spí, než že by zatěžovali servery. K 50 spočteným hodinám tedy přidám 2 * 6 hodin nočního klidu, celkem tedy dva a půl dne, zaokrouhlíme na celé dny, takže 3.
Potřebuji lineárně natáhnou deploy proces na 3 dny.
Bojový plán
Na vstupu máme 6 parametrů.
- Název úlohy (pro případ že bychom jich v reálném čase měli více)
- Čas, kdy má začít provádění úlohy (ideálně čas v budoucnosti)
- Délku deploymentu (Jak dlouho nám potrvá celý proces – 3dny)
- Min procentuální škála na začátku, tedy např 20% ihned
- Max procentuální škála pro terminaci, pak skip na 100%.
- Úloha, kterou to má udělat, tedy callback
Jak se to bude chovat?
Dle vstupních parametrů si definujeme procentuální rozsah, do kterého se daná koncová stanice musí trefit během slow deploy fáze. Generátorem náhodných čísel se do rozsahu pokusíme trefit. Když ano, vykonáme callback, když ne, neděláme nic.
Každou sekundou se reálný čas a čas definovaný jako konec fáze slow deploy procesu zužuje, čímž zvyšuje procentuální šanci na zařazení do procesu.
Informace o tom, zda již byla klientská stanice zařazena do procesu, se ukládá do cookie.
Poté, co vyprší limit, vždy callback vykonáme.
Script
Script jsem se pokusil rozepsat tak, aby dávaly smysl mé myšlenkové pochody. V reálné aplikaci je jednodušší.
function slowDeploy (name,deployDate,rangeMS,min,max,callback){ // get current time in miliseconds (from external library) var cu = lib.time.current(); // skip slowDeploy if time is bigger than rangeMS var doIt = ((deployDate*1+rangeMS*1)>cu); if(doIt){ // define cookie prefix var cp = "sd_"; // define cookie name var cn = cp+name; // init empty variable to populate console log in the end and save computation time while drawing console log var report = ""; // range var ra = max - min; ra = (ra<=0?1:(ra>=100?100:ra)); report += ("Range: " + ra + " % | "); // diff var df = cu - deployDate; report += ("Diff: " + df/1000 + " s | "); // progress var pr = df/rangeMS*100; report += ("Progress: " + pr + " % | "); // normalize progress pr = (pr>=100?100:(pr<0?0:pr)); // piece var pi = 100/ra; report += ("Piece: " + pi + " % | "); // relative gain var rg = pr/pi; report += ("Relative gain: " + rg + " % | "); // absolute gain var ag = min+rg; report += ("Abslute gain: " + ag + " % | "); console.log(report); console.log("Current scale is " + df + " ms - " + df/1000 + " s - " + df/1000/60 + " m - " + df/1000/60/60 + " h - " + df/1000/60/60/24 + " d",1); // random percentage var rd = Math.random()*100; report = ("Random result is " + rd + " % | "); // decide if deploy or not var fn = (rd<ag?true:false); report += (fn? "Is in slowDeploy :-)":"Is not in slowDeploy :-("); // read cookie register time (from external library) var cv = lib.cookie.get(cn); console.log(report,1); } else { // slow Deployment is over limit fn = true; } // Slow deploy enabled already if(cv!=""){ // do delayed console.log("Register '" + name + "' through slow deploy",1); callback(); } // decide if set else { if(fn===true){ console.log("Register for first time '" + name + "' through slow deploy",1); lib.cookie.set(cn,deployDate,60); callback(); } else{ console.log("Currently excluded '" + name + "' due to slowDeploy",1); } } }
Alternativy
V průběhu času mě napadly ještě další způsoby, jak nasazování omezovat. Jedním z nich je vzít křivku průměrné denní zátěže serveru, k ní vytvořit křivku opačnou a ta by dále sloužila jako předpis pro omezování trafficu.
Hrubý omezovač
Prostě vím, že můj server zvládne max 30 000 requestů za minutu, takže od 22. hodin do 10. hodin dopoledne nebudu script nijak omezovat. Od 10. do 15. a od 19. do 22. jej ořežu na cca 80%.
Převrácený omezovač
Definuji si maximální zátěž a z ní spočítám relativní křivku vytížení serveru, která je opačná denní návštěvnosti. Hodnoty po hodinách zapíšu do pole a dle aktuálního času se dynamicky mění šance, že klientská stanice dostane zelenou ke kontaktování centrálního serveru. To reprezentuje žlutá křivka.