Tinklapio našumo optimizavimas
Parašė ozzWANTED 2009 liepos 3 00:07:27
Šis straipsnis tiems, kurie turi didelius projektus paremtus Php-Fusion sistema. Tai yra, kai įrašų skaičius lentelėse pradeda viršyti 100k.
Taigi, bedarydamas updeitus šiam tinklapiui, vienas jų yra pabandyti jį kažkiek optimizuoti. Kadangi šitas didelis dramblys pasidarė labai lėtas kaip pradėjo valgyti lenteles su 500k įrašų.

Taigi, pirmiausia, apžvelgsiu jau prieš metus, porą ar dar seniau padarytų sistemos atnaujinimų optimizacijos atžvilgiu.

[center][big][b]PAŽENGUSIEMS VARTOTOJAMS[/b] (advanced-users)[/big][/center]

[big][b]1.Taigi, pirmasis žingsnis būtų - "[color=red]spauskime srautą[/color]"[/b] (maincore.php):[/big]
[code]ob_start();[/code]
eilutės keitimas į
[code] ob_start(ob_gzhandler);[/code]

Šis žingsnis mums leidžia persiųsti tinklapio srautą vartotojui suspaustu pavidalu. Tai gana ženkliai įtakoja krovimo laiką.
Beje, įdomumo dėlei yra ir kitų srauto spaudimo būdų, ne tik "[b]ob_gzhandler[/b]", tačiau šis yra, matyt, populiariausias.

[big][b]2.Antrasis etapas - "[color=red]sudėkime indeksus[/color]"[/b][/big]
Kad suprastumėte, kada ir kur tai daryti, nėra sunku. Tiesiog per tinklapio failus pasileiskite kokį TC ar Notepad++ kuris jums išmestų daugiausiai(pagal svarbą, nuo svarbiausio):
1.[b]"WHERE"[/b] (laukeliai pagal kurios ieškome - [u]SVARBIAUSIAS[/u] ir esminis 2 etapo faktorius)
2.[b]"group_by"[/b] (kai grupuojame įrašus naudodami kelias lenteles)
3.[b]"ON"[/b] (kai norime susieti lenteles)
4.[b]"AS"[/b] + [b]"count()", "sum()"[/b] ir t.t. (užklausos mysql dalys, kai sumuojame, skaičiuojame ar pan. laukelio įrašus dalyje "SELECT")
5.[b]"SELECT" COL[/b] (šis žingsnis aktualus tik įvykdžius 3 etapą ir pakeistus užklausas iš "[b]SELECT *[/b]" į "[b]SELECT COL(s)[/b]"
dalyse naudojamus laukelius.
Taigi, prisijungę prie PhpMyAdmin ar kokio kito duom. bazių valdymo pulto, tiesiog sudėkime "[b]INDEX[/b]" tipo raktus([b]KEY's[/b]) daugiausiai naudojamiems laukeliams.
Kaip pavyzdį tarkim, galime paimti "online_ip" iš lentelės "{prefix}_online", bei "user_ip" iš lentelės "{prefix}_users". Jeigu esame įsidiegę SIP ar kokias kitas su IP surištas saugumo sistemas, tikrai dažnai naudosime "WHERE" užklausą šiems laukeliams. Tad sudėti indeksus šiems lentelių laukeliams yra pravartu.
Toliau iš aktualių būtų galima paminėti:
"thread_id" lentelėje "posts" (kadangi dažnai grupuojame postus pagal temą)
"user_name" lentelėje "users" (dažnai ieškome vartotojo vardo)
ir daugelį kitų.
Esmė paprasta - sudėkite kuo daugiau į paminėtus punktus patenkančių indeksų. Žinoma persistengti neverta, nes vieno indekso teikiama nauda su kiekvienu nauju lentelės indeksu mažėja.
Kritiniais variantais toks indekso padėjimas tinkamoje vietoje gali sumažinti tinklapio krovos laiką nuo 15-16 sekundžių, iki vos 1-2 sekundžių.

[big][b]3.Trečio etapo esmė - "[color=red]NESIRINKIME nenaudojamų laukelių[/color]"[/b][/big]:
Šio punkto esmė manau labai puikiai suprantama - kalbu apie užklausos, į duom. bazė, dalį "[b]SELECT [color=red][i](laukelių sąrašas)[/i][/color][/b]". Jeigu Jūsų tinklapis pilnas SELECT * tipo užklausų - vadinasi esate tikrai neoptimizavęs tinklapio ir bereikalingai švaistote serverio resursus.
Pagal idėją, "[b]SELECT laukelis1,laukelis2 [/b]" ir t.t. turėtų būti tik tie lentelės laukeliai, kuriuos iškviesite žemiau seksiančioje funkcijos implementacijoje, ar bet kokiame kitame informacijos iš duom. bazės ištransliavime į tinklapio HTML kodą vartotojui.

Tinkamai pasinaudojus šiuo punktu, tinklapio krovos laiką galite sumažinti iki 10 kartų. Na bet jeigu kažkiek ribojimo jau buvo, indeksai irgi dalinai sudėti buvo, tai galite tikėtis 2-4 kartus greitesnio krovimo.

[big][b]4.Ketvirtas etapas - "[color=red]Nesaugokime ISTORIJOS - archyvuokime ją[/color]"[/b][/big]
Ši funkcija labai aktuali tiems kas stebi lankytojų srautą - iš kokių tinklapių lankytojai ateina, kiek peržiūrų padaro, ar dar kada lankėsi pastaruoju metu ir t.t.
Pradžioje galbūt nieko ir nejausime, tačiau tinklapiui esant populiaresniam po pusmečio ar metų, mūsų log'ų lentelės pradės daryti griozdiškos, iš nejučiomis nepajusite kaip tinklapio krovos laikas pradės ilgėti. To esmė, ilgos paieškos didelės apimties lentelėse. Taip čia duoda naudos būtinų laukelių SELECT dalyje apibrėžimas, indeksų sudėjimas ir t.t., bet 'net ir geriausias vairuotojas neįvarys mašinos į už mašinos plotį siauresnį garažą'. Todėl, geriausia tokiu atveju išeiti - failo pabaigoje pasirašyti papildomą užklausą, kuri, tarkim Jūsų lentelei viršijus 30k (30.000) įrašų dydį, automatiškai perkeltų juos į archyvine lentelę, pvz. "{prefix}_users_visited_pages[b]_ARCHIVE[/b]", tokiu atveju archyviniai duomenys visuomet bus išsaugoti, tačiau tikrai neįtakos tinklapio darbo, be to darant duom. bazės kopijas, nereiks siųstis kaskart(užteks tik vieną - pradinį kartą) milžiniško dydžio bylų - UŽARCHYVUOTŲ lentelių turinys tampa statiniu ir nekeičiamu. Tačiau papildomo failo pagalba, galite suteikti administratoriui kartas nuo karto pasižiūrėti į tą lentelę - užklausos bus ilgos, bet jis tai žinos.

[big][b]5.Penktas etapas - "[color=red]Optimizuokime lenteles[/color]"[/b]:[/big]
Šio etapo prasmė paprasta - visados nepamirškime pasinaudoti "phpmyadmin" suteikiama funkcija "optimize" duom. bazės lentelėms. Dažnai atsitinka, kad dėl ryšio trukių, ar serverio trikdžių ar šiaip kokių problemų įsivelia klaidų ir atsiranda duomenų perteklius duom. bazės lentelėje, kartais perteklius gali būtų ir keliasdešimt megabaitų, jeigu lentelės apimtis didelė ir ji buvo ilgą laiką neotimizuota. Optimizuodami lentelę, išsprendžiame šias 'mini' problemas ir kartu laimime tinklapio krovos laiko. :)

[big][b]6.Šeštas etapas - "[color=red]Naudokime LIMIT X funcija[/color]"[/b]:[/big]
Būtinai rekomenduoju naudoti LIMIT funkcija kur tik įmanoma. T.y. [u]verta aukoti tiksumą[/u] vardan užklausos laiko.
Tipinis pavyzdys - užklausos sutapimų kiekis atliekant paiešką tarp postų. Visų pirma jeigu turime tarkim 500k įrašų postų lentelę, paieškos rezultatams rekomenduojama gražinti ne daugiau kaip 100 įrašų per 5-iuose - 10-tyje puslapių.
Tai yra įgyvendinta Php-Fusion v7 versijoje, tačiau čia pamiršama smulkmena - kaip viena eilutė yra pateikiama informaciją apie bendrą sutapimų skaičių - sakoma "[i]rasta 95521 sutapimai vietoje 100 maksimaliai galimų[/i]". [b]TAI KLAIDINGAS SPRENDIMAS[/u]. [u]Verta paaukoti tikslumą[/u] ir gražinti atsakymą pvz. "[i]rasta virš 500 sutapimų vietoje 100 maksimalių leistinų[/i]". Vartotojui informacijos pilnai užtenks, tačiau mes galėsime išlošti sistemos resursų bemaž dešimteriopai įdėję į mūsų rezultatų skaičiavimo užklausos pabaigą kodą "[b]LIMIT 501[/b]".

[big][b]7.Septintas etapas - "[color=red]Protingai pasirinkime ORDER BY reikšmes[/color]"[/b]:[/big]
Šio etapo esmė - nedėkite ORDER BY rūšiavimo sql užklausose, pagal didelių lentelių ne pirminius(PRIMARY KEY/ UNIQUE INDEX) atributus. T.y. jeigu pvz. jungiame užklausą iš kelių lentelių, kur vienos lentelės dydis yra 10 tūkst. atributų, o kitos lentelės dydis - 500 tūkst. atributų, ir abi lentelės turi datos laukelį, pagal kurį Jūs norite rūšiuoti savo rezultatus, tai esant galimybei [u]verta[/u] aukoti dalį tikslumo išlošiant užklausos laiką dažnai net ir dešimteriopai ar šimteriopai.
Php-Fusion sistemos konkretus pavyzdys būtų paieškos sistema:
[code]ORDER BY p.post_datestamp[/code]
Verta keisti į:
[code]ORDER BY t.thread_detestamp[/code]
Nors ir prarandame tikslumo, esant 10k temų ir 500k pranešimų, mes išlošiame užklausos laiko šimteriopai.

[big][b]8.Aštuntas etapas - "[color=red]Rinkimės mažesnių lentelių laukelius[/color]"[/b]:[/big]
Logika paprasta - tarkime ieškome jungdami tris lenteles [i][b]".DB_POSTS." p[/b][/i], [i][b]".DB_THREADS." t[/b][/i] ir ir [i][b]".DB_FORUMS." f[/b][/i]. Pirmosios dydis yra 500 tūkst. eilučių, antrosios - vos 10 tūkst, trečiosios - vos 20 įrašų.
Todėl [i][b]SELECT[/b][/i] dalyje pakeitę frazę:
"SELECT [color=red][b]p[/b][/color].forum_id, [color=red][b]p[/b][/color].thread_id, p.post_id, ..."
Fraze:
"SELECT [color=red][b]f[/b][/color].forum_id, [color=red][b]t[/b][/color].thread_id, p.post_id, ..."
Galime išlošti iki 5-10 proc. sistemos resursų. Priklausomai nuo eilučių lentelėse kiekybinio santykio.

[hr]
[center][big][b]PATYRUSIEMS VARTOTOJAMS[/b][/big] (expert-users)[/center]

[big][b]9.Devintas etapas - "[color=red]Sekime užklausų "Load time[/color]"[/b].[/big]
Šį dalyką įgyvendinti gana paprasta - pasiimkite bet kurią iš [b]microtime()[/b] funkciją reazijuojančių mikro-laiko laikmačių skriptų(funkcijų) ir tiesiog saugokite:
--> Bendrą tinklapio krovos laiką ([b]"START"[/b] pradžioje po pirmųjų eilučių maincore.php faile [b][[u]PRIEŠ[/u] visas mysql užklausas ir bet kokias skaičiavimo funkcijas][/b], [b]"END"[/b] pabaiga theme.php footer dalies pabaigoje [b][[u]PO[/u] visų mysql užklausų ir bet kokių skaičiavimo funkcijų][/b])
--> Kiekvieno modulio krovimo laiką (startinis laikas fiksuojamas iškart po "opentable()", pabaigos laikas - eilutė prieš closetable() funkciją)
--> Kiekvienos užklausos krovos laikas (fiksuojama prieš ir iškart PO užklausos)

Taip išsivedę į ekraną visus krovos laikus, galėsite nesunkiai matyti kuriose būtent vietose susidaro "tinklapio butelio kakliukai" arba "silpnieji taškai". Nesunkiai analizuodami gautą informaciją galime nuspręsti ką daryti su lėtomis užklausomis(pirmiausia, žinoma jas pabandę kiek įmanoma optimizuoti):
a)Retinti jų iškvietimą (rodysime ją kas kelintą kartą)
b)Apskritai atsisakyti šių užklausų ar funkcijų
c)Ieškoti tobulesnės sistemos ar modifikacijos, kuri atliktų tą patį darbą su kur kas geriau paruošta duomenų struktūra ir saugojimu
d)Saugoti funkcijos rezultatą, ir kviesti ją rečiau.
e)Keisti atsakymu tipą (jeigu kalbame apie dbcount(), tai keičiame į "DESC LIMIT 1");
f)Kešuoti funkcijos duomenis.

Būtent paskutinį etapą, aš ir rekomenduočiau - jo principas paprastas. Tarkim norime žinoti kiek vartotojas parašė komentarų: jeigu duomenų lentelė nėra didelė, tai mums nėra svarbu kaip tie duomenys bus gaunami. Tačiau lentelei estint gana didelei, tai jau tampa aktualu, nes yra faktinis skaičiavimo nuostolis. Todėl tokiu atveju, geriausias sprendimas - "skaitliukas", kuris parašius kiekvieną naują komentarą saugotų jį vartotojo įrašę vartotojų lentelėje. Tokiu atveju vietoje ilgo skaičiavimo, atsakymą gauname vienu paprastu užklausimu.

Taip pat galime keisti atsakymo tipą - tarkime norime žinoti kiek mūsų tinklapyje yra žinučių. Jų yra keli šimtai tūkstančių, ir ten +-keli tūkstančiai nieko nereiškia - atsakymus galime apvalinti. Tokiu atveju, vietoje ilgai trunkančio skaičiavimo su [b]dbcount()[/b] galime tiesiog pakeisti mūsų užklausą į užklausą su pabaiga [b]"DESC LIMIT 1"[/b]. Tokiu atveju išrinksime naujausią duomenų bazės įrašą toje lentelėje vos vienu užklausimu ir taupysime krovos laiką.

[big][b]10.Dešimtas patarimas - "[color=red]Kešuokime viską ką galime[/color]"[/b][/big]
Ilgos ir didelės užklausos - tikras galvos skausmas dideliems portalams, todėl paprastas to sprendimas yra tiesiog kuo didesnis užklausų kešavimas:
[b]1.[/b] Kešuokime vartotojų paieškos rezultatus (tai aktyviai įgyvendinama daugelyje kitų TVS - pvz. "Invision Power Board" ar "phpBB")
[b]2.[/b] Kešuokime aktyvius vartotojus - kam kaskart daryti paieškas lentelėje su 80 proc. 'mirusių vartotojų', darykime jas tarp 'gyvųjų'. Na o miręs į tarp gyvųjų bus automatiškai įtrauktas kai dar kartą prisijungs.

[b]3.[/b] Kešuokime postų skaičių per parą, dieną, ar bendrą:
[b]Algoritmas:[/b] Turbūt šiam punktui daliai žmonių gali kilti algoritmo klausimas, na argoritmo sudėtingumas čia yra tiesionis O(n), tad resursų visiškai nebenaudoja, ir skaičiuojame pasinaudodami "[b]rūšiavimas eile[/b]" rūšiavimo metodu.
Tam turime būti susikūrę papildomą duom. bazės lentelę, pvz.:
"[b]{prefix}_last_day_posts_cache[/b] (toliau [b][i]DB_PCACHE[/i][/b])". su tokiu [b]VIENINTELIU[/b] laukeliu:
[i][b]Name:[/b] post_time
[b]Type:[/b] [u]timestamp[/u]
[b]Default:[/b] [V] CURRENT_TIMESTAMP
[b]Key:[/b] primary[/i]

[color=red][b][u]Pseudokodas:[/u][/b][/color]
[code][color=green]/* Įrašymas */[/color]
if(Vartotojas parašo pranešimą temoje) {
[b]then[/b]
if(egzistuoja lentelėje ".DB_PCACHE." įrašų senesnių nei 24 valandos) {
[b]then[/b] ištriname visus senesnius nei 24 valandos įrašus iš mūsų ".DB_PACHE." lentelės;
}
[b]then[/b]
Įterpiame tuščią įrašą į mūsų lentelę ".DB_PCACHE.";
[color=green]/* Mysql atveju tai būtų tiesiog komanda [b]"INSERT INTO ".DB_PCACHE." () VALUES ()"[/b] */[/color]
}
[color=green]/* Skaičiavimas */[/color]
rezultatas := [u][b]SUMA[/b][/u] visų įrašų tenkinančių sąlygą ("DB_PCACHE" LENTELĖJE): post_time > time();[/code]


4.Kešuokime narių postų, komentarų, shoutų ar dar kokios kitos veiklos vykdymą.
[b]5.[/b] Kešuokime nuotraukas, kaip tai daro TinyMCE (imagelist.js) bei vandenženklintas nuotraukas(watermarked), kaskart taupysime serverio resursus.
[b]6.[/b] Negeneruokime to paties skripto rezultato keletą kartą - geriau saugokime jį. Tarkim super sudėtingas vartotojo apsaugos kodą - kur kas protingiau jį yra tiesiog saugoti duom bazėje, o skriptu bus pasiekiamas tarkim tik koduotas pvz. sha1() jo variantas - tai užtikrins ir saugumą ir tinklapio našumą.

[big][b]11.Vienuoliktas etapas(tik VPS/DS turėtojams) - "[color=red]Loginkime lėtas užklausas[/color]"[/b][/big]
Kartais nutinka taip, kad nariai skundžiasi, jog kartas nuo karto tinklapis labai smarkiai laggina. Tokiu atveju tiems, kurie turi VPS'us ar DS'us, rekomenduoju įsidiegti kokį nors padoresnį "Slow-queries log" modulį į savo serverį. Tarkim "CentOS" oper. sistemai yra puikus "log-slow-queries" modulis, kuris į spec. failą saugo visas lėtas užklausas, lėtesnes nei X sekundžių. Tačiau šis modulis efektyvus yra kai norime rasti 'išsišokusį lėtūną', o ne bendrai padidinti tinklapio krovimo laiką - tokiu atveju moduli logintų tūkstančius užklausų ir pats lėtintų serverį.

-----------------------
Tikiuosi šis straipsnis Jums padėjo.

[color=maroon][b]Atnaujinta: 2010-02-03[/b][/color]

[color=green][b]ozzWANTED Copyright © 2009 ; PhpFusion-Lt.com Copyright © 2009[/b][/color]