Diplomitöö: ASP.NET veebirakenduse optimeermine meediaorganisatsiooni näitel

From ICO wiki
Jump to navigationJump to search

Siin lehel kirjutan oma diplomitööd. Alustan 19.märtsil probleemi kirjeldusega.

1. Probleemi kirjeldus

1.1 Olemasolev lahendus

Meediaorganisatsiooni näitena kasutatakse Eesti Rahvusringhäälingut, mis tegutseb internetimeedia turul ja haldab enam kui kahtekümment portaali. Need on erineva sisuga, kuid kõikide näol on tegemist põhimõtteliselt uudiste portaalidega. Ainus erand on otse, mis võimaldab kasutajatel vaadata veebist internetiülekandeid. Ettevõtte eesmärgiks on laiendada oma tegevust ja kasutada kõikide portaalide haldamiseks ühtset .NET platvormi. Hetkel kasutavad seda tehnoloogiat pooled portaalid, milledest enamus kasutab .NET4, kuid leidub ka .NET 2 raamistikku kasutavaid rakendusi. Ülejäänud kasutavad PHPd. Hetkel tegeletakse aktiivselt kõikide portaalide ületoomisega .NET 4 raamistikule, mis on ka uute veebirakenduste aluseks.

ASP.NET võimaldab kasutada mitut programmeeriskeelt ühes rakenduses. Antud organisatsioonis kasutatakse nii C# kui ka Visual Basicut. .NET 4 raamistikul asuvate rakenduste näol on tegemist teise põlvkonna portaalidega, mis kasutavad enamus sarnast lahendust. Viimasel puudub aga korralik andmekiht, mille rolli täidab lihtsalt LinqToSql'iga genereeritud andmebaasitabelitest klassid. Kuna hetkel pole veel uut lahendust loodud, peab kasutama olemasolevat.

Andmebaasi platvormiks on SQL Server 2008 R2 Standart väljaanne. Veel mõned kuud tagasi oli kasutusel piiratud võimalustega Express versioon. Veebirakendused töötavad IIS 7.5 peal ja neil on kasutada üks server.

1.2 Probleem

Teise põlvkonna portaalide jaoks loodud lahendus valmis kiirustades, kuna seda oli vaja kiiresti ja programmeerijaid vähe. See tõttu puudus oodatava süsteemi kohta analüüs. Rakenduse loomiseks kasutati võtteid, mis kiirendasid küll selle valmimise kiirust, kuid paratamatult kannatas selle all jõudlus. Enne ametlikku kasutuselevõttu ei olnud võimalik teha süsteemile korraliku jõudlustesti, mis näidanuks tema toimimist suure koormuse korral. Sellel hetkel kasutas seda lahendust üks portaal ja .NET lahendusi oli vähem.

Arendusmeeskonda on lisandunud ka noori programeerijaid, kes ei ole piisavalt teadlikud optimeerimisvõimalustest. ASP.NET lahendusi on küll võimalik pealtnäha kiiresti ja kergelt valmistada, kuid jättes optimaalse ressursikasutuse jälgimise tagataustale, mõjub see rakenduse jõudlusele hävitavalt.

Poole aasta jooksul on lisandunud uusi .NET portaale, milledest enamus kasub eelpool mainitud lahendust. Viimasel ajal on esinenud suurte koormuste korral märgatavaid jõudlusprobleeme. Üheks põhjuseks on kindlasti asjaolu, et kõiki .NET portaalide haldamiseks on kasutada ainult üks server. Ehk kui üks portaal hakkab kasutama väga palju ressursse, siis mõjutab see ka teiste portaalide tööd. See on kindlasti ka üheks turvanõrkuseks - leides ühel leheküljel ressursinõudliku protseduuri, saab seda kasutada kõikide .NET portaalide töö häirimiseks. Halvimal juhul lõpeb see serveri töö seiskumisega. Täiendavad investeeringud riistvarasse on aga välistatud, et kasvatada serveripargi jõudlust. Kuna organisatsioon tegutseb internetimeedia turul ja laieneb sellel pidevalt, siis häired veebirakenduste töös mõjuvad mainele negatiivselt ning on oht kaotada külastajaid.

Lähiajal on lisandumas antud serverile veel kaks täiesti uut portaali. Lisaks peavad hakkama mõned esimese põlvkonna portaalid kasutama hakkama ka "uut mootorit". Kuna juba praegu esineb jõudlusprobleeme, tuleb enne seda parandada olemasolevate portaalide jõudlust. Selle tõttu on vaja olemasolevaid rakendusi optimeerida. Lisaks ei pruugi riistvara soetamine alati olla ka lahendus nagu seda näitavad ka meie digiretsept või valimistulemustesüsteem.

Tegemist on tänapäeval väga aktuaalse probleemiga, mis ei puuduta ainult näitena toodud organisatsiooni. Kasutades optimeerimist ei tööta veebirakendused mitte ainult kiiremini, vaid võib ära hoida ka täiendavaid lisakulutusi riistvara soetamiseks, millega loodetakse nii probleeme lahendada.

Metoodika valik ja võimalikud lahendused

Metoodika

Lahenduse leidmiseks kasutatakse olemasolevate materjalide analüüsi. Lõputöö käigus tutvutakse parimate praktikatega ja valitakse välja neist sobivaimad. Valiku tegemisel hinnatakse sobivust olemasoleva lahendusega. Kui valik on teostatud mõõdetakse praeguse lahenduse töökiirust. Peale seda võetakse kasutusele leitud optimeerimisvõimalused, mida rakendatakse kõigepealt testserveris, millele järgnevad uuesti mõõtmiseid. Võrreldakse saadud tulemusi ja tehakse järeldus. Eelistatud on Microsofti enda poolt soovitatud lahendused.

ASP.NET rakenduse arhitektuur

Optimeerimises üldiselt

Andmebaasi optimeerimine

Öeldakse, et koodi saab alles siis optimeerida, kui midagi töötavat on juba valmis tehtud. Andmebaaside puhul selline väide aga ei kehti, kuna selle hea jõudluse tagamise üheks tähtsamaks nurgakiviks on korralik disain. Kui andmebaas on halvasti loodud, siis on ka selle optimeerimine keeruline ja sellest pole nii palju kasu. Öeldakse, et päev tööd andmebaasi loomise algusfaasus võrdub hiljem sama tulemuse saavutamiseks kolme kuuga. Andmebaasi jõudluse tagamine ei koosne ainult protseduuride korrigeerimisest ja mõnede seadete muutmitest. Enne selle kallale asumist peab analüüsima üldse seda, mis hetkel toimub - kuidas päringuid täidetakse, kui palju see võtab jõudlust, kas indekseeritud on õiged väljad. Ei saa loota, et suvaline indekseerimine parandab andmebaasijõudlust, vastupidi, selle hoolimatu kasutamine mõjub jõudlusele hoopis negatiivselt. Siiski on mõned põhitõed, mis parandavad andmebaasi jõudlust alates selle loomisest.

Andmebaasi loomine

Nagu eelpool mainitud, algab kõik andmebaasiloomisest. Selle protsessi käigus tehtud otsused mõjutavad väga palju andmebaasi edaspidist jõudlust ja ressursikasutust. Alguses valesti tehtud otsused võivad mõjuda halvasti andmebaasi, kui ka seda kasutava rakenduse jõudlusele. Muidugi on kõige tähtsam andmebaasi loogiline ülesehitus, kuid tähelepanu tuleb pöörata ka kindlasti veergude definitsioonidile, mis tuleb määrata korrektselt ja loogiliselt. Ehk kuupäevad peaksid olema kuupäeva tüüpi, mitte nVarchar või char. Teine punkt mida veerutüüpide loomisel tasub alati läbi mõelda on, kui täpseid andmeid meilt nõutakse? Lihtne näide selle kohta on kuupäevad, mida kasutatakse alati väga palju, näiteks registreerimaks uue sissekande loomise kuupäeva. Tihti kasutatakse andmebaasis kuupäeva tüübiks automaatselt DateTime, mis võtab ruumi 8 baiti ja täpsuseks kolm millisekundit. Kuid kui tihti on selline täpsus süsteemides vajalik? Jah, tõesti on kohti kus see on väga kriitiline, kuid kindlasti leidub rohkem situatsioone, kus piisab ka minutitäpsusest.

Näiteks uudisteportaalides on avaldamisajad tihti märgitud ainult kuupäeva ja minutitega. Selle jaoks on ideaalne SmallDateTime, mis on minuti täpsusega ja lisaks võtab poole vähem ruumi(4baiti). Lisaks on andmebaasis olemas nüüd täpselt vajalikus formaadis kuupäev ning me ei pea hakkama seda veebirakenduses andmete väljaküsimusel seda omakorda ümber hakkama vormindama, mis säästab veel rohkem ressurssi.

Kindlasti tuleb säilitada aga ka paindlikus ehk tinyint võtab küll 4 korda vähem ruumi, kui Int, aga see eest saab tabelisse kokku tekitada 256 unikaalset kannet kui tegemist on primaarse võtme väljaga.

Selliseid näiteid leidub kõigi andmetüüpide kohta. Ehk enne tabelite loomist tuleks kindlasti tutvuda konkreetse andmebaasimootori dokumentatsiooniga ja teha selgeks, milliseid võimalusi pakutakse ja mida meil täpselt vaja on. Eelpool mainitiud SmallDateTime on saadaval alates SQL Server 2008 versioonist.

GUID

Huvitav on mainida ka GUIDi kasutamist. Jõudluse poolelt tundub olevate GUID'i kasutamine hullumeelne kui võrrelda seda näiteks INT tüübiga. (Ikkagi 16 baiti vs 4 baiti). Samas GUID'iga kaasneb ka kaks plussi. GUID on alati unikaalne, kui INT on unikaalne ainult ühes tabelis. See tõttu on GUID'i kasutamisega välistatud, et kokku viiakse omavahel tabelite veerud, mis tegelikult ei ole omavahel seotud. Teine pluss on see, et GUID ei saa kunagi otsa.

Üks enim levinud argument, miks mitte GUIDi kasutada primaarse võtmena ja eelistada sellele INTi on asjaolu, et see nullib ära klasteeritud indekseerimisega saavutatava jõudlusevõidu. INT kasvab automaatselt ja seega iga uus sissekanne automaatse primaarse võtme kasvamise puhul on viimane, GUID genereeritakse automaatselt ja see tõttu ei pruugi see viimane olla. Iga uus sissekanne nõuab indeksite uuendamist ja see tõttu põhjustab selle kasutamine lehtede poolitusprobleeme ja indeksite fragmenteerumist. Alates SQL Server 2005 versioonist saab selle vastu kasutada funktsiooni NewsequentialID(), mis tagab, et iga järgmine GUID on eelmisest suurem. Seega ei tule uus sissekanne mitte lehe keskele, vaid läheb alati viimaseks. Samas tuleb tõdeda, et kuna GUID kasutab rohkem mälu, siis tekib juurde indekseerimisel rohkem lehti ja indeksite uuendamine on kulukam kui INT tüübi puhul.

Päringute optimeerimine

Info saamiseks andmebaasist tehakse päringuid, kus vastavalt seatud tingimustele väljastatakse sobivad tulemused. Nende loomisel ja kirjutamisel tuleb jälgida mõningaid lihtsaid võtteid, et vältida üleliigset ressursi kulutamist.

Infot küsides peab alati kirjeldama, mida me tahame tagasi saada. Ehk näiteks SELECT * päring võib olla päris korralik jõudlusetapja, olukorras kus meil on tabelis 10 välja, kuid tahame saada kätte neist ainult kahte. Sellisel juhul on tegemist ebavajaliku ressursiraiskamisega. Lisaks on sellisel juhul ka tabelite indekseerimisest väga vähe kasu, kuna kõigi veergude kättesaaamiseks tuleb teha lisapäringuid.

Päringuid kirjutades peaks küsima alati seda, mida meil vaja on. Võimalusel tuleks vältida eitusi.

Andmebaasi töö sõltub väga palju selle ülesehitustest ja konkreetsetest päringutest. Selle optimeerimiseks peab analüüsima juba loodud ja loomisel olevaid päringuid, et näha kuidas nad käituvad. Selle jaoks on soovitatv kasutada SQL Serveri Query Editor'i kus on võimalik näha päringu täitmist kasutades Execution plan'i. Nii on võimalik kindlaks teha, näiteks kas loodud indekseid kasutatakse, kuidas täpselt toimub päringu täitmine ning kui palju see aega võtab. Selle alusels saab vajadusel muuta indekseerimist või päringuid.

Protseduurid

Andmebaasi jõudluse tagamisel on hea praktika protseduuride kasutamine. Hästi kirjutatud protseduurid on väga kiired. Lisaks on neid lihtne testida ja hallata võrreldes ad hoc päringutega. Samuti on need turvalisemad, kuna kasutavad parameetreid, mis antakse neile kaasa. Ka rakenduseloojatel on lihtsam kutsuda välja protseduur, mille sisendiks antakse parameetrid, kui kirjutada ad hoc päringut, mis avab võimaluse SQL "injenction" rünnakuteks ja seega on ka üheks turvariskiks. Viimaseid on ka raske hallata ja asuvad reaalsetest andmetest kaugel.

Kasutades protseduure on lihtsam jälgida päringute täitmist ja ajakulu, võrreldes tavalise SQL päringuga, mis tuleb väljaspoolt andmebaasi. Kuna kaasa antakse ainult parameetrid, siis on ka edastatav andmemaht väiksem. Samuti asub see ka andmetele lähem.

Kirjutades protseduure on hea praktika lülitada COUNT välja kasutades käsku SET NOCOUNT ON. Viimasega võib sõltuvalt päringu keerukusest võita kuni 17% selle täitmise kiirusest. Vaikimisi on NOCOUNT välja lülitatud ja see tõttu saadab saadab SQL Server kaasa ka alati ridade arvu, mis päringu tulemusena leiti.

Lisaks tuleks vältida kursoreid. Kuigi nad on väga paindlikud ja võimaluste rohked, tuleb nende kasutamisel maksta ränka hinda jõudluses. Kui protseduurides kasutatakse kursoreid, siis nende optimeerimiseks tuleks alustada kursorite eemaldamisest.

Indekseerimine

Indeksid mõjutavad väga palju SQL Serveri jõudlust. Neid tuleb kasutada, et tagada märgatavalt parem jõudlus. Indekseerimine on nagu raamatu sisukord. Kui me soovime leida vajalikku infot, vaatame me sisukorda ning leiame õige koha. Samamoodi käime pidevalt andmebaasist infot otsimas, kus mingi sissekanne asub. Tabel ilma indeksita on nagu raamat sisukorrata - millegi leidmiseks peame lappama läbi terve raamatu, et leida õige koht, mida otsime ja see on kulukas. Kui andmebaasis on tabelid õigesti indekseeritud toimub otsimine nagu raamatu sisukorrast. Rühmitatud indekseerimist tuleb kasutada tabeli primaatvõtmete väljadel, kuna tihti otsime infot just selle võtme järgi. Indekseerimiseks ei sobi väljad, mis on väga mahukad. Näiteks nVarchar(200), kuna see on väga mahukas ja indeksite uuendamine on kulukas protsess.

Indekseerides tabeli veerge kõhutunde järgi, võib see mõjuda jõudlusele hoopis negatiivselt, kuna õigete ridade leidmiseks peab ikkagi tegema lisapäringuid. Selle vätlimiseks tuleks jälgida päringu täitmise plaani, mis näitab, kuidas päringuid läbi viiakse ja kas indeksite tabelit üldse kasutatakse. Nii võib leida kohti, kus tabel on küll indekseeritud, kuid selleks on kasutatud valesid välju ja see tõttu ei saa kasutada kunagi otsimist indeksi järgi ja päringu täitmine võtab kaua aega ning palju ressurssi.

Indeksite loomisel peab arvestama, et indekseerimisega peab maksma natuke hinda ka jõudluses. See tõttu tuleks kaasta indeksitesse minimaalselt välju. Mida rohkem on välju, seda kulukam on indeksite uuesti arvutamine, mis leiab aset iga DDL käsu puhul. Indeksite tabel koosneb lehtedest ja lisades uue sissekande tuleb lehed uuesti luua. Eriti halvasti mõjub see siis, kui uus sissekanne tuleb indeksi lehtede keskele. See eeldab lehtede uuesti loomist ja nende poolitamist. See tõttu tuleb hoolikalt kaaluda, kus kasutada rühmitatud indekseid.

Alates SQL Server 2008 versioonist on võimalik kasutada filtreeritud indekseerimist, mis tähendab, et rühmitamata indeksite loomiseks kasutatakse andmetest ainult mingit osa, mis vastab seatud tingimustele.

Sõnaotsing

SQL Server pakub sõnade otsimiseks lahendust, mille nimeks on FullTextSearch. Seda saab kasutada alates Standard versioonist ning töötab indekseerimise põhimõttel. See lahendus pole küll mõeldud otseselt otsingumootoriks, kuid see on väga hea jõudlusega ning täidab igati oma põhiülesannet, hoides samal ajal ressursikasutuse madalana. Kindlasti tuleks seda eelistada algelisele otsingule, kus kasutatae LIKE operaatorit, mis on aeglane.

Andmebaasi konfigureerimine

Parimate praktikate kohaselt soovitatakse jätta palju variante vaikimisi, kuna nii töötab server kõige optimaalselt ning nende muutmisel võib olla effekt jõudlusele hoopis negatiivne

Startup Manager Kui soovida SQL Serverist pigistada välja viimane, siis võib SQL Server Configuration Manager'i all saab lisada Startup Parameters alla parameetri -x, mis võimaldab maksimaalselt jõudlust. Selllel on aga üks väga suur miinus. Nimelt lülitatakse kõik monitoorimisvõimalused selleks välja ja jõudlusprobleemide jälgimine ja otsimine muutub peaaegu võimatuks.

Ühenduste automaatne sulgemine(Auto Close) Ühenduste automaatset sulgemist saab kontrollida graafilisel Management Studiost ja käsuga: ALTER DATABASE <ANDBEMAASINIMI> SET auto_close ON/OFF. Kui Auto Close on lubatud, siis peale kõigi aktiivste ühenduste sulgumist andmebaasiga vabastab SQL server kõik selle andmebaasi ressursid, mis teoreetiliselt peaks andma rohkem ressursse mõnele teisele andmebaasile. Pealtnäha võib see mõjuda jõudlusele hästi, kuid tegelikult on see kindel viis kuidas hävitada SQL Serveri jõudlus, kuna paljud rakendused sulgevad ja avavad ühendusi korduvalt. Uute ühenduste puhul tuleb aga andmebaas uuesti laadida, kompileerida protseduurid ja arvutada päringu läbiviimise plaanid. See tõttu peaks see ühenduste automaatne sulgemine olema keelatud, mis on ka kõigis SQL Serveri versioonides vaikimis, välja arvatud SQL Express.

Ühenduste automaatne sulgemine peaks olema lubatud ainult siis, kui tegemist on andmebaasiga, mis hoiab endas arhiivi ja kasutatakse väga vähe.

Automaatne kahanemine (Auto Shrink) Samuti peaks olema keelatud käsuga ALTER DATABASE <Andmebaasinimi> SET AUTOSHRINK OFF; Kui andmebaasil on üle 25% vaba ruumi, siis see põhjustab andmete ja logifailide kahandamise operatsiooni. Failide kahandamine on väga kulukas protsess.

ASP.NET tehnoloogiast tulenevad optimeerimisvõimalused

Web.config

web.config failis asub informatsioon veebirakenduse seadistuse kohta nagu näiteks andmebaasiga ühenduse loomiseks vajalikud parameetrid või kuidas toimub rakenduse kompileerimine. ASP.NET kasutab selles failis olevat infot, et luua rakenduse ülesehitus. Kui testkonnas testida rakendust, peaks olema kindlasti web.configis olema parameeter "compilation debug" väärtus true. Vigade esinemise korral on sel juhul neid lihtne leida. Veebirakenduse tõstmisel live serverisse tõstmisel peab jälgima, et antud parameeter oleks false. Vastasel juhul kompileeritakse pidevalt veebirakendust iga lehe laadimise ajal uuesti ja see omakorda kasvatab protsessori ja mälu kasutust. Kõige kindlam viis selle vältimiseks on muuta serveris asuvat machine.config faili ja lisada parameeter deployment retail="true". Nii on kindel, et ükski veebiaplikatsioon ei kasuta antud serveris debug mode'i. Ainukeseks miinuseks selle puhul on, et vea ilmnemisel ei kuvata vea täpset asukohta(Rea numbrit ja veakirjeldust).

Lisaks tasub web.config faili lisada ka outputcache'imise erinevad profiilid, millega saab määrata kui kaua lehte serveri mälus hoitakse. Kui lehele on tulemas suur koormus või midagi muud, saab ühest kohast muuta kõikide cahche'ide kehtivuse pikkust, mis kasutavad konkreetset profiili.

Viewstate

Viewstate'i kasutatakse, et hoida väärtusi, mis postitatakse tagasi lehele endale. Nii on võimalik antud väärtuseid kasutada ka peale kliendi-serveri vahelist suhtlust, kui selleks kasutatakse postbacki, mille kutsub välja näiteks nupulevajutus. Kõiki neid väärtusi hoitakse lehel peitud väljas, mis kannab nime _VIEWSTATE. Hoides selles aga suurel hulgal andmeid, kasvab see tõttu ka lehe suurus ning selle laadimise aeg. Arvestama peab ka sellega, et lehe renderdamisel tuleb viewstate'i serialiseerida ja deseriaaliseerida. Lisaks võib see vähendada Garbage Collection'i efektiivsust. Sellepärast tuleb selle kasutamisega olla ettevaatlik ja eemaldada kohtadest, kus seda vaja ei lähe, kuna see mõjub jõudlusele halvasti. Näiteks genereerivad kõige rohkem viewstate'i ASP.net enda tööriistad Listview ja Gridview. Lehel sisalduva viewstate'i hulga analüüsimiseks on hea tööriist ASP.NET ViewState Helper, mis töötab Firefoxi ja Internet Exploreriga. Selle abil on võimalik näha, kui suure osa moodustab kogu lehe suurusest viewstate. Vaikimisi on see ASP.NET'is igal pool sisselülitatud. Soovitatav on see välja lülitada kohtades, kus ei toimu serveri poolseid kontrolle ja ei kasutata postbacke. Seda saab välja lülitada erinevatel tasemetel: serveris(machinge.config), veebirakenduses(web.config), lehel <%@ Page EnableViewState="false" %> kui ka ASP.NET tööriistades endas(EnableViewstate=false).

ASP.NET enda tööriistad

Enimlevinud tööriistad, mida tihti kasutatakse on GridView, Listview ja Repeater. Esimesed kaks neist genereerivad suurel hulgal viewstate'i ja selle tõttu on soovitatav see keelata, kui seda ei lähe vaja. Lisaks tuleb pöörata tähelepanu kuidas nende abil liste genereerida. Et väärtused nende sisse paigutada on väga levinud viis on kasutada varianti <%#Eval("fieldname")%>, mis defineerib välja nime, mida kasutatakse. Selle plussiks on see, et välja tüübi muutumisel ei pea koodi muutma, kuna see tehakse kindlaks automaatselt. Selle eest tuleb hinda maksta jõudluses, kuna tüübi kindlaks tegemiseks kasutatakse peegeldust("reflection"). Nii tuleb 10 rea väärtustamiseks teha Eval operatsiooni 20 korda, kuna esialgu pole teada, millist tüüpi andmetega tegu on. Parem on viidata kohe klassile, kust otsitav väli pärit on: <%#((MyClass)Container.DataItem).field1%> või DataTable kasutamisels <%#((DataRowView)Container.DataItem).field1%>. Ainuüksi 10 rea lisamisel võib kiirusevahe olla enam kui kümne kordne, viimase variandi kasuks. Viimase variandi puhul peab arvestama, et muutes tüüpi, tuleb teha seda ka koodi sees.

ASP.net genereerib automaatselt ID väljadele ise nimed peale kompileerumist. Näiteks kui meil on kontroll <asp:Image ID="imgMainItem" runat="server"/>, siis sõltuvalt selle asukohast võib peale kompileerimist ID'ks osutuda hoopis id="ctl00_leftColumn_ctl00_Vaikekategooria3_imgMainItem", mis nagu näha on üpriski mahukas. Kui seda ID'd aga ei kasutata tegelikult, siis võim jätta selle määramata. Alternatiiv on peale kontrolli defineerimsit koodi sees anda id ks null ehk imgMainItem.ID=null. Kui lehel on palju ASP.NET enda kontrolle, siis on lehe suuruse vähenemine märgatav.

Postback'i kontrollimine Kui lehel on lubatud viewstate ja toimub postback, tuleb seda kontrollida kasutades IsPostBack() meetodit, mis teeb kindlaks, kas tegemit on postbackiga. Kui on, siis pole mõtet enam uuesti andmeid pärida, vaid kasutada viewstate'is olevaid.