Back to site
Since 2004, our University project has become the Internet's most widespread web hosting directory. Here we like to talk a lot about web servers, web development, networking and security services. It is, after all, our expertise. To make things better we've launched this science section with the free access to educational resources and important scientific material translated to different languages.

Ahritektura Servera Visokih Performansi

Uvod

Svrha ovog dokumenta je da podeli neke ideje koje sam razvio tokom godina o tome kako razviti određenu vrstu aplikacije za koju je termin “server” tek slaba odrednica. Preciznije, pisaću o širokom razredu programa koji su napravljeni da upravljaju velikim brojem posebnih poruka ili zahteva u sekundi. Mrežni serveri se najčešće uklapaju u ovu definiciju, ali svi programi koji se uklapaju nisu zapravo serveri u svakom smislu te reči. Radi jednostavnosti, ipak, i s obzirom na to da je “Program visoke performanse za upravljanje zahtevima” (High-Performance Request-Handling Programs) zaista loš naziv, samo ćemo reći “server” i završiti s tim.

Neću pisati o “blago paralelnim” aplikacijama, iako je multitasking u jedinstvenim programima sada uobičajena stvar. Pretraživač koji koristite za čitanje ovoga verovatno paralelno radi neke druge stvari, ali tako nizak nivo paralelizma zapravo ne uvodi mnogo interesantne izazove. Zanimljiv izazov se dešava kada sama infrastruktura za upravljanje zahtevima predstavlja ograničavajući faktor u ukupnom izvođenju, tako da poboljšanje infrastrukture zapravo poboljšava izvođenje. To nije čest slučaj kod pretraživača koji rade na gigaherc procesoru sa gigabajtom memorije koja izvršava šest simultanih preuzimanja preko DSL linije. Fokus ovde nije na aplikacijama koje se uvlače kroz slamčicu, već na onima koje se piju na veliko, koje su na samoj ivici mogućnosti hardvera, tamo gde način rada zaista pravi veliku razliku.

Neki ljudi će neizbežno imati problema sa mojim komentarima i predlozima, ili će misliti čak da znaju bolje. U redu. Ja ne pokušavam da ovde budem Božji glasnik; ovo su samo metodi koji meni dobro rade, ne samo u smislu njihove efikasnosti za izvođenje, već i u smislu njihove efikasnosti za teškoće otklanjanja grešaka ili kasnijeg proširenja koda. Vaša miljaža može varirati. Ako je za vas nešto drugo bolje - odlično, ali imajte na umu da skoro sve što ću ovde predložiti postoji kao alternativa nečemu drugom što sam nekada ranije probao i bio zapanjen i užasnut rezultatima. Vaša omiljena ideja će možda funkcionisati veoma značajno u nekoj od ovih priča, a nedužan čitalac će se možda na smrt dosađivati ako me ohrabrite da o tome pričam. Ne želite da ih povredite, zar ne?

Ostatak ovog članka će se fokusirati na ono što ja zovem Četiri konjanika lošeg izvođenja:

  1. Kopije podataka
  2. Izmena konteksta
  3. Alokacija memorije
  4. Sukob zaključavanja

Na kraju će takođe biti i odeljak sa nekim trikovima, ali ovo su najveći neprijatelji izvođenja. Ako možete upravljati većinom zahteva tako što nećete kopirati podatke, bez izmene konteksta, ne prolazeći kroz alokaciju memorije i ne sukobljavajući zaključavanja, imaćete server koji funkcioniše dobro čak i onda kada su neki manji delovi pogrešni.

Kopije podataka

Оvo može biti veoma kratak odeljak, iz jednog veoma jednostavnog razloga: većina ljudi je već naučila ovu lekciju. Svi znaju da je kopiranje podataka loše; to je očigledno, zar ne? Pa, zapravo, to verovatno samo izgleda očigledno jer ste to naučili veoma rano u vašoj računarskoj karijeri, a to se desilo samo zato što je neko to otkrio pre nekoliko decenija. Znam da je to za mene tako, ali da ne skrećem s teme. Danas se to obrađuje u biografiji svake škole i u svakom neformalnom priručniku. Čak su i marketing tipovi otkrili da je “nulta kopija” dobra poštapalica.

Uprkos činjenične očiglednosti da su kopije loše, ipak izgleda da postoji još nijansi koje ljudima promiču. Najvažnija je da je kopiranje podataka često sakriveno i prerušeno. Da li zaista znate da li neki kod koji pozovete u drajveru ili biblioteci pravi kopije podataka? Verovatno je to češće nego što mislite. Pogodite na šta se “Programirani I/O” na PC-ju odnosi. Primer kopije koja je prerušena, pre nego prikrivena, jeste heš funkcija, koja ima pristup memoriji kopije i koja podrazumeva više računanja. Jednom kada se pokaže da je heširanje u stvari “dodatno kopiranje”, izgleda očigledno da to treba izbeći, ali ja poznajem barem jednu grupu sjajnih ljudi koji su to otkrili na teži način. Ako zaista želite da se rešite kopiranja podataka, bilo zbog toga što zaista ometaju izvođenje ili zato što želite da postavite “operaciju nultog kopiranja” na vaše slajdove haker konferencije, moraćete da ispratite dosta stvari koje zaista jesu kopije podataka, ali se ne reklamiraju kao takve.

Isprobani i pravi metod za izbegavanje kopiranja podataka jeste upotreba indirekcije i prenošenje bafer deskriptora (ili lanca bafer deskriptora) umesto pukih bafer pokazivača. Svaki deskriptor se tipično sastoji iz sledećeg:

  • Pokazivača i dužine za ceo bafer
  • Pokazivača i dužine, ili pomeranja i dužine, za deo bafera koji je zapravo ispunjen
  • Pokazivača napred i nazad ka drugim bafer deskriptorima na listi
  • Referentnog broja

Sada, umesto kopiranja dela podatka da bismo se uverili da ostaje u memoriji, kod može jednostavno uvećati referentni broj na odgovarajućem bafer deskriptoru. To može funkcionisati izuzetno dobro u nekim uslovima, uključujući i način na koji tipični mrežni stekovi protokola operišu, ali može biti i velika glavobolja. Uopšteno govoreći, lako je dodati bafere na početku ili na kraju lanca, dodati reference na cele bafere i dealocirati ceo lanac odjednom. Dodavanje u sredinu, dealociranje deo po deo ili stavljanje reference na deo bafera uveliko će otežati stvari. Pokušaj podele ili kombinovanja bafera će vas prosto izludeti.

Ja zapravo ne preporučujem upotrebu ovog pristupa za sve. Zašto ne? Zato što to postaje huge muka kada morate šetati kroz lanac deskriptora svaki put kada želite da pogledate polje zaglavlja. Postoje i gore stvari od kopiranja podataka. Ja mislim da je najbolje identifikovati velike objekte u programu, kao što je blokiranje podataka, uveriti se da se those alociraju zasebno, kao što je gore opisano, tako da se ne moraju kopirati i ne zamarati se mnogo drugim stvarima

To me dovodi do poslednje tačke vezano za kopiranje podataka: nemojte preterivati u njihovom izbegavanju. Video sam previše kodova koji izbegavaju kopiranje podataka čineći nešto još gore, kao što je prisiljavanje na izmenu konteksta ili lomljenje velikih I/O zahteva. Kopiranje podataka je skupo, i kada tražite mesta za izbegavanje redudantnih operacija, to je nešto što prvo treba da gledate, ali postoji i tačka opadajućih povraćaja. Češljanje kroz kod i njegovo pravljenje duplo složenijim, samo da biste se rešili poslednjih nekoliko kopija podataka je obično gubljenje vremena koje bi se moglo drugačije utrošiti.

Izmena konteksta

Iako svi misle da je kopiranje podataka loše, često me iznenadi koliko ljudi potpuno zanemaruje efekat izmene konteksta u izvođenju. Prema mom iskustvu, izmena konteksta je zapravo totalnije “topljenje” na visokom učitavanju, nego što je to kopiranje podataka; sistem počinje da troši više vremena prelazeći sa jedne niti na drugu, nego što zapravo troši u bilo kojoj niti čineći nešto korisno. Zanimljivo je to što je, na jednom nivou, potpuno očigledno šta izaziva preteranu izmenu konteksta. Uzrok broj 1 izmene konteksta je posedovanje aktivnijih niti nego što je procesor. Kako se razmer aktivnih niti i procesora povećava, povećava se i broj izmene konteksta - linearno ako imate sreće, ali često i eksponencijalno. Ova veoma jednostavna činjenica objašnjava zašto višenitni programi koji imaju jednu nit po konekciji skaliraju veoma loše. Jedina realna alternativa za skalabilne sisteme jeste ograničavanje aktivnih niti tako da ih (obično) bude manje ili jednako kao i procesora. Jedna popularna varijanta ovog pristupa je korišćenje samo jedne niti, uvek; iako takav pristup izbegava trošenje konteksta, i takođe izbegava i potrebu za zaključavanjem, ona je i nesposobna da postigne više od ukupne vrednosne memorije jednog procesora, čime ostaje ispod zadovoljavajućeg praga, osim ako program nije ionako ne-CPU-ograničen (obično mrežno I/O-ograničenje).

Prva stvar koju program sa “štedljivim nitima” treba da uradi jeste da otkrije kako da učini da jedna nit upravlja višestrukim konekcijama odjednom. To obično podrazumeva čeoni program koji koristi funkciju izaberi/povuci, asinhroni I/O, signale ili završne portove sa strukturom vođenom događajima iza toga. Vođeni su mnogi “religiozni ratovi”, i još se vode, oko toga koji je od više čeonih API-a najbolja. Dan Kegelov C10K rad je dobar izvor za to. Lično, mislim da su svi flavori izaberi/povuci i signali ružni hakovi, i stoga dajem prednost ili AIO ili završnim portovima, ali to zapravo i nije toliko važno. Svi oni - osim možda izaberi() (select()) - rade dosta dobro i ne čine mnogo da pokažu šta se dešava kada se pređe najudaljeniji sloj vašeg čeonog programa.

Najjednostavniji konceptualni model višenitnog servera vođenog događajem ima red u svom centru; zahteve čita jedna ili više niti “slušaoca” i stavlja ih u red iz kojeg ih jedna ili više niti “radnika” uklanja i obrađuje. Konceptualno, to je dobar model, ali ljudi prečesto realizuju svoj kod na ovaj način. Zašto to ne valja? Jer je uzrok broj 2 za izmenu konteksta prenos rada sa jedne niti na drugu. Neki ljudi čak upotpunjuju grešku zahtevajući da se odgovor na zahtev pošalje preko originalne niti - garantujući ne jednu, već dve izmene konteksta po zahtevu. Veoma je važno koristiti “simetrički” pristup u kojem data nit može preći sa slušaoca u radnika i onda opet u slušaoca a da ne dođe do izmene konteksta. Da li ovo podrazumeva delimične konekcije između niti ili to da se sve niti smenjuju kao slušaoci i radnici u celom skupu konekcija, izgleda da je manje bitno.

Obično nije moguće znati koliko niti će biti aktivno u jednoj instanci u budućnosti. Ipak, zahtevi se mogu pojaviti na svakoj konekciji u svakom trenutku, ili “pozadinske” niti posvećene različitim zadacima održavanja mogu izabrati taj trenutak da se probude. Ako ne znate koliko niti je aktivno, kako onda možete ograničiti broj aktivnih niti? Prema mom iskustvu, jedan od najefikasnijih pristupa je i jedan od najjednostavnijih: upotrebite staromodni semafor za računanje koji svaka nit mora imati svaki put kada izvršava neki “pravi rad”. Ako je granica niti već dostignuta, tada svaka nit u načinu slušanja može uzeti na sebe dodatnu izmenu konteksta kad se probudi i onda se blokira na semaforu, ali jednom kada se sve niti slušaoci blokiraju na taj način, neće nastaviti da se bore za izvore sve dok se jedna od postojećih niti “ne povuče” tako da sistemski efekat postane zanemarljiv. Što je još važnije, ovaj metod upravlja nitima održavanja - koje spavaju većinu vremena i stoga se ne računaju u aktivne niti - najelegantnija od svih alternativa.

Kada se obrada zahteva razbije na dve faze (slušanje i rad) sa višestrukim nitima koje opslužuju faze, prirodno je razbiti tu obradu dalje na više od dve faze. U najjednostavnijem obliku, obrada zahteva tada postaje pitanje uspešnog pozivanja faza u jednom pravcu, a zatim u drugom (radi odgovora). Ipak, stvari se mogu iskomplikovati; faza može predstavljati “viljušku” između dve obradne putanje koje uključuju različite faze ili se može generisati sam odgovor (npr. keširana vrednost) bez pozivanja narednih faza. Stoga, svaka faza mora biti u stanju da odredi “šta će se desiti sledeće” sa zahtevom. Postoje tri mogućnosti koje predstavljaju vrednosti iz funkcije obaveštavanja u jednoj fazi:

  • Zahtev se mora preneti na drugu fazu (ID ili pokazivač u povratnoj vrednosti)
  • Zahtev je dovršen (specijalna povratna vrednost “zahtev ispunjen”)
  • Zahtev je blokiran (specijalna povratna vrednost “zahtev blokiran”). To je ekvivalentno prethodnom slučaju, osim što zahtev nije oslobođen i nastaviće se kasnije iz sledeće niti.

Imajte u vidu da, u ovom modelu, smeštanje zahteva u red se radi u okviru faze, ne između faza. Time se izbegava uobičajena glupost konstantnog stavljanja zahteva u red u naslednoj fazi, zatim trenutnog pozivanja te nasledne faze i ponovnog vađenja iz reda; ja to nazivam prečestom aktivnošću smeštanja u red - i zaključavanja - ni zbog čega.

Ako vam ova ideja razdvajanja složenih zadataka na višestruke manje komunikacijske delove zvuči poznato, to je zbog toga što je ona zapravo veoma stara. Moj pristup ima svoje korene u Communicating Sequential Processes (komunikacija uzastopnih obrada) konceptu koji je razjasnio C.A.R. Hoare 1978, zasnovano na razmeni ideja Pera Brincha Hansena i Matthewa Conwaya još davne 1963 - pre nego što sam se i rodio! Ipak, kada je Hoare skovao termin CSP, mislio je na “obradu” u apstraktnom matematičkom smislu i CSP obrada ne treba da se povezuje sa entitetima operativnog sistema istog naziva. Prema mom mišljenju, uobičajeni pristup za realizaciju CSP preko korutina sličnih niti u pojedinačnoj OS nit,i pruža mnogo glavobolje korisniku zbog konkurentnosti, bez imalo skalabilnosti.

Savremeni primer izvršne ideje koja se razvila u jednom zdravijem pravcu je SEDA. Zparavo, je tako dobar primer “pravilnog rada serverske arhitekture” da je vredno komentarisati neke od njegovih specifičnih karakteristika (naročito one po kojima se razlikuje od onoga što sam prethodno napisao).

  1. SEDA “grupisanje” teži da naglasi obradu višestrukih zahteva u fazi odjednom, dok moj pristup teži da naglasi obradu pojedinačnog zahteva u višestrukoj fazi odjednom.
  2. Jedan od SEDA značajnih mana, po mom mišljenju, jeste što alocira zasebne grupe niti u svakoj fazi sa samo “pozadinskom” realokacijom niti između faza u odgovoru na učitavanje. Kao rezultat, gore pomenuti uzroci 1 i 2 izmene konteksta su i dalje veoma prisutni.
  3. U smislu projekta akademskog istraživanja, realizacija SEDA-e u Javi ima smisla. U stvarnom svetu, ipak, smatram da se ovaj izbor može okarakterisati kao nesrećan.

Alokacija memorije

Alokacija i oslobađanje memorije je jedna od najčešćih operacija u većini aplikacija. Shodno tome, razvijeno je mnogo pametnih trikova koji će alokatore memorije opšte svrhe učiniti još efikasnijim. Ipak, nema te pameti koja može nadoknaditi činjenicu da ih baš uopštenost takvih alokatora neizbežno čini manje efikasnim od alternativa u mnogim slučajevima. Stoga imam tri predloga kako potpuno izbeći sistemski alokator memorije.

Predlog br. 1 je jednostavna prealokacija. Svi znamo da je statička alokacija loša ako nameće veštačke granice na funkcionalnost programa, ali postoje i mnoge druge forme pre-alokacije koje mogu biti prilično korisne. Obično se razlog svodi na činjenicu da je jedan prolazak kroz sistemski alokator memorije bolji nego više njih, čak i kada se određena memorija “izgubi” u obradi. Dakle, ako je moguće tvrditi da ne treba biti u upotrebi više od N stavki odjednom, pre-alokacija na početku programa bi mogla biti dobar izbor. Čak i kada to nije slučaj, pre-alociranje svega što upravljaču zahteva može biti potrebno odmah na početku može biti bolje od alociranja svakog posebnog dela prema potrebi; da ne pominjemo mogućnost povezanog alociranja višestrukih stavki u jednom prolasku kroz sistemski alokator, ovo često uveliko pojednostavljuje kod oporavka od greške. Ako je memorija mala, onda pre-alokacija ne može biti opcija, ali u svim, osim u najekstremnijim okolnostima, obično se pokaže kao dobra stvar.

Predlog br. 2 je korišćenje liste odvojenog zaključavanja za objekte koji se često alociraju i oslobađaju. Osnovna ideja je stavljanje nedavno oslobođenih objekata na listu umesto zaista ih osloboditi, nadajući se da, ako ponovo budu bili potrebni uskoro, samo će morati da se uzmu sa liste umesto da se alociraju sa sistemske memorije. Dodatna korist je što se prelaz sa i na liste odvojenog zaključavanja često može realizovati da bi se preskočili složeni počeci/završeci objekata.

Generalno je nepoželjno neograničeno povećavati listu odvojenog zaključavanja, ne oslobađajući nikad ništa čak i onda kada vam je program u stanju mirovanja. Stoga, obično je neophodno imati neku vrstu periodičnih “čišćenja” radi oslobađanja neaktivnih objekata, ali isto bi bilo nepoželjno ako bi uvedeno čišćenje pokvarilo složenost zaključavanja ili sukobljavanje. Dobar kompromis je sistem u kojem se lista zaključavanja zapravo sastoji iz zasebno zaključanih “starih” i “novih” lista. Alokacija se radije vrši iz nove liste, a iz sistema samo kao iz poslednjeg izvora; objekti se uvek oslobađaju na novu listu. Nit čišćenja radi prema sledećem:

  1. Zaključavanje obe liste
  2. Čuvanje zaglavlja za staru listu
  3. Pravljenje prethodne (nove) liste starom dodeljivanjem zaglavlja liste
  4. Otključavanje.
  5. Oslobađanje svega sa sačuvane stare liste

Objekti u ovakvoj vrsti sistema se stvarno oslobađaju samo onda kada nisu potrebni u najmanje jednom ukupnom intervalu čišćenja, ali uvek u manje od dva. Što je najvažnije, čišćenje radi većinu posla bez blokiranja koje će se takmičiti sa regularnim nitima. Teoretski, isti pristup se može generalizovati u više od dve faze, ali to još moram učiniti korisnim.

Jedna nevolja kod upotrebe liste zaključavanja je što pokazivači liste mogu uvećati veličinu objekta. Prema mom iskustvu, većina objekata za koje sam koristio listu zasebnog zaključavanja već su ionako sadržali pokazivače liste, pa je to nekako sporno pitanje. Čak iako su pokazivači potrebni samo za listu zaključavanja, uštede u smislu izbegavanja prolaza kroz sistemski alokator memorije (i inicijalizacija objekta) bi obezbedile dodatnu memoriju.

Predlog br. 3 ima veze sa zaključavanjem o kojem još nismo govorili, ali ću ga ipak pomenuti. Sukob zaključavanja je obično najveći trošak u alociranju memorije, čak i onda kada su liste zaključavanja u upotrebi. Jedno rešenje je zadržati višestruke privatne liste zaključavanja, tako da apsolutno ne postoji mogućnost sukoba ni za jednu listu. Na primer, možete imati zasebnu listu zaključavanja za svaku nit. Jedna lista po procesoru je možda još bolje, uzimajući u obzir zagrevanje keša, ali to funkcioniše samo ako se niti ne mogu pretprazniti. Privatne liste zaključavanja se čak mogu kombinovati sa podeljenom listom, ako je neophodno, da bi se kreirao sistem sa ekstremno niskom dodatnom memorijom za alociranje.

Sukob zaključavanja

Izuzetno je teško nparaviti efikasne šeme zaključavanja zbog onoga što ja nazivam Scilom i Haribdama, prema čudovištima iz Odiseje. Scila predstavlja zaključavanje koje je previše jednostavno i/ili nisku rezoluciju, aktivnosti serijalizacije koje mogu ili bi trebalo da se nastave paralelno i time žrtvuju izvođenje i skalabilnost; Haribde predstavljaju prekomerno složeno zaključavanje ili zaključavanje fine rezolucije, sa prostorom za zaključavanje i vremenom za operacije zaključavanja, opet podrivajući izvođenje. Pored Scile nalaze se mnoštva koja predstavljaju uslove uzajamne blokade i uslove bespotrebnog rada (livelock); pored Haribdi nalaze se mnoštva koja predstavljaju uslove takvog utrkivanja. Između njih nalazi se uzani kanal koji predstavlja zaključavanja koje je i efikasno i ispravno...a da li je? S obzirom da zaključavanje teži da bude duboko povezano sa programskom logikom, često je nemoguće napraviti dobru šemu zaključavanja a da se iz osnove ne izmeni način rada programa. To je razlog zbog kojeg ljudi ne vole zaključavanja i pokušavaju da racionalizuju njihovu upotrebu neskalabilnim jednonitnim pristupima.

Skoro svaka šema zaključavanja počinje kao “jedno veliko zaključavanje oko svega” i uzaludna je nada da izvođenje neće biti očajno. Kada ta nada ispari, a skoro uvek ispari, veliko zaključavanje se razbija na manja i molitva se ponavlja i onda se ceo proces ponavlja, verovatno sve dok izvođenje ne bude adekvatno. Često, ipak, svaka iteracija povećava složenost i dodatnu memoriju alociranja za 20-50%, u odnosu na 5-10% smanjenja u sukobu zaključavanja. Uz malo sreće, neto rezultat je još uvek skormno uvećanje u izvođenju, ali ni stvarna umanjenja nisu nepoznata. Programeru ostaje da se češka po glavi (upotrebio sam muški rod jer sam i sam tog roda; prebolite to). “Napravio sam zaključavanje sa finom rezolucijom, kao što su sve knjige rekle da treba”, misli, “pa zašto se onda izvođenje pogoršalo?”

Prema mom mišljenju, stvari se pogoršavaju jer je pomenuti pristup fundamentalno pogrešan. Zamislite “prostor rešenja” kao planinski lanac, sa vrhovima koji predstavljaju dobra rešenja i nižim delovima koji predstavljaju loša. Problem je što je početna tačka “jednog velikog zaključavanja” skoro uvek odvojena od viših vrhova manjim dolinama, sedlima, nižim vrhovima i ćorsokacima. To je klasičan problem penjanja; pokušaj da se sa tako niskih tačaka dođe do viših vrhova samo preduzimanjem manjih koraka i nikada ne silaziti niz brdo skoro nikada ne funkcioniše. Ono što je potrebno je fundamentalno drugačiji pristup vrhovima. Prva stvar

koju treba da uradite je da formirate mentalnu mapu zaključavanja vašeg programa. Ta mapa ima dve ose:

  • Vertikalna osa predstavlja kod. Ako koristite faznu arhitekturu bez ne-razgranatih faza, verovatno već imate dijagram koji pokazuje te podele, kao one koje svi koriste za mrežne stekove protokola OSI modela
  • Horizontalna osa predstavlja podatke. U svakoj fazi, svaki zahtev se treba dodeliti skupu podataka sa sopstvenim izvorima odvojenim od drugih skupova.

Sada imate mrežu, u kojoj svaka ćelija predstavlja određeni skup podataka u određenoj fazi obrade. Ono što je najvažnije jeste sledeće pravilo: dva zahteva ne bi trebalo da budu u sukobu osim ako nisu u istom skupu podataka i u istoj fazi obrade. Ako možete to postići, već ste dobili pola bitke.

Kada jednom definišete mrežu, svaki tip zaključavanja koje vaš program vrši može se isplanirati, a vaš sledeći cilj je da osigurate da se rezultirajuće tačkice jednako distribuiraju duž obe ose. Nažalost, ovaj deo ima veoma specifičnu primenu. Morate misliti kao rezač dijamanata, koristeći znanje onoga što program radi da pronađe prirodne “linije cepanja” između faza i skupova podataka. Ponekad su one očigledne još na početku. Ponekad ih je teško naći, ali se čine očiglednijim u retrospektivi. Deljenje koda na faze je komlikovana stvar u programskom dizajniranju, pa nema mnogo čega što vam tu mogu ponuditi, ali evo nekoliko predloga kako da definišete skupove podataka:

  • Ako imate neku vrstu broja bloka ili heš ili ID transakciju povezanu sa zahtevom, retko možete učinit bolje osim da podelite tu vrednost po broju skupova podataka
  • Ponekad je bolje dodeliti zahteve skupovima podataka dinamički, na osnovu toga koji skup podataka ima najviše dostupnih izvora, a ne na osnovu nekih unutarnjih svojstava zahteva. Mislite na to kao na višestruku integer jedinicu u modernom CPU-u; ti momci znaju ponešto o pravljenju diskretnog toka zateva kroz sistem
  • Često je od pomoći uveriti se da se zadataka skupa podataka razlikuje za svaku fazu, tako da se za zahteve koji će se sukobiti u jednoj fazi, garantuje da se neće sukobiti u drugoj fazi.

Ako ste podelili vaš “prostor zaključavanja” i vertikalno i horizontalno, i uverili se da se aktivnost zaključavanja protegla jednako preko rezultirajućih ćelija, možete biti sigurni da je vaše zaključavanje u dosta dobrom stanju. Postoji još jedan korak. Da li se sećate pristupa “malih koraka” kojima sam se rugao pre nekoliko pasusa? To i dalje ima svoju priču, jer ste sada u dobroj početnoj poziciji umesto u lošoj. U metaforičkom smislu verovatno ste već na dobroj visini na litici na jednom od najviših vrhova na planinskom vencu, ali verovatno niste na najvišem. Sada je vreme da prikupite statistiku sukoba i videti šta treba da unapredite, razdvajajući faze i skupove podataka na različite načine i zatim prikupljajući još više statistike sve dok ne budete zadovoljni. Ako sve to uradite, sigurno ćete imati lep pogled sa planinskog vrha.

Оstale stvari

Kao što sam obećao, obradio sam četiri najveća problema izvođenja u serverskom programiranju. Još uvek postoji nekoliko važnih pitanja koja će svaki posebni server morati da obradi. Većinom, ona se svode na poznavanje sopstvene platforme/okruženja:

  • Kako vaš skladišni podsistem funkcioniše sa većim, a kako sa manjim zahtevima? Uzastopno ili nasumično? Kako funkcionišu funkcije slanje unapred (read ahead) i piši naknadno (write behind)?
  • Koliko je efikasan mrežni protokol koji trenutno koristite? Da li postoje parametri ili zastavice koje možete podesiti tako da rade bolje? Da li postoje olakšice kao što su TCP_CORK, MSG_PUSH ili Nagle-toggling trik koje možete koristiti da biste izbegli sitne poruke?
  • Da li vaš sistem podržava rasturanje/prikupljanje I/O (npr. readv/writev)? To može unaprediti izvođenje i takođe rešiti vas muke korišćenjem bafer lanaca
  • Kakva je veličina vaše stranice? Kakva je veličina vaše keš linije? Da li vredi poravnati stvari na tim granicama? Koliko su skupi sistemski pozivi ili izmene konteksta, u odnosu na druge stvari?
  • Da li vaše zaključavanje pisanja/čitanja predmet gladovanja? Čijeg? Da li vaši događaji imaju probleme “gromovitog stada”? Da li vaša funkcija spavanje/buđenje ima neprijatno (ali često) ponašanje da se, kada X probudi Y, izmena konteksta u Y dešava trenutno, čak iako X i dalje ima stvari koje treba da uradi?

Siguran sam da se mogu setiti još dosta pitanja sada. Siguran sam da i vi možete. U nekoj posebnoj situaciji možda ne bi bilo vredno činiti bilo ta povodom bilo kog od ovih pitanja, ali obično vredi barem razmišljanja o njima. Ako ne znate odgovore - većinu nećete naći u sistemskoj dokumentaciji - pronađite. ih sami. Napišite probni program ili mikro-merila i pronađite odgovore empirijski; pisanje takvog koda je korisna veština i sama po sebi. Ako pišete kod koji će raditi na višestrukim platformama, većina od ovih pitanja je povezana sa tačkama u kojima ćete verovatno morati da apstrahujete funkcionalnost u biblioteke platforme, tako da možete uvideti korist za izvođenje na toj jednoj platformi koja podržava to posebno svojstvo.

Teorija “poznavanja odgovora” primenjuje se i na vaš sopstveni kod. Otkrijte koje su važne visokostepene operacije u vašem kodu i odredite im vreme pod različtim uslovima. To nije baš isto kao tradicionalno profilisanje; ovde se radi o merenju dizajnerskih elemenata, ne stvarne realizacije. Niskostepena optimizacija je praktično poslednje rešenje nekoga ko je pokvario ceo dizajn.

Published (Last edited): 10-04-2013 , source: http://pl.atyp.us/content/tech/servers.html