ugrás a tartalomhoz

2 kérdés 1 topic (PHP+MySQL - DateTime, Smarty's lassulás)

Kael · 2009. Dec. 14. (H), 19.55
Kezdő programozó rengeteg, remélhetőleg értelmes kérdése...

Sziasztok!
Először postolok itt, kezdő programozóként ezt a weblapot választottam a szakmai segítség kérésre.

A kérdéseim:
Kb egy éve volt egy oldalam, amit még nem én fejlesztettem. Egy ismerős csinálta Smarty-s keretrendszert használva az oldal előállításához.
Amikor kicsit beindult az oldal és volt napi 3.000 látogató, hirtelen egy erős lassulást vettem észre. Az oldal egymaga volt egy elég erős szerver gépen. Mégis volt, hogy 20-50 mp-et kellett várni egy oldal betöltésére. Nyilván ez komoly visszaesést okozott az oldal látogatottságában.
Megkérdeztem a felelős rendszergazdát a cégnél és azt felelte, hogy túl sok SELECT lekérdezés van és valószínűleg ez okozza a durva lassulást.
Az oldalon kb 1.000 regisztrált felhasználó volt és kb 5.000 tétel az oldal témájával kapcsolatban a táblákban, kb 400 post a fórumban.
Tehát a kérdés, hogy (ennyire kevés infobol sajna, és ennyire utólag) tényleg lehet-e a Smarty rendszer gyengesége, a programozó rossz kódja a hiba vagy valami teljesen más, és akkor hibáztam-e?

Ekkoriban volt ott egy elég profi programozó srác akitől részben elkezdtem tanulni és ő mindig azt forszirozta, hogy a lehető legkevesebb SQL lekéréssel oldjam meg a programokat, mert állítása szerint ez a szűk keresztmetszet a nagy adatbázisok esetében. Erről mi a véleményetek?

Ezt az elvet követve egy most fejlesztés alatt lévő programnál lenne az utolsó kérdésem. Egy cégnek készítek egy ügyfél kezelő rendszert, amit a telefonos üzletkötők használhatnak, hogy az általuk végzett telefonokat lehessen nyomon követni, mert van hogy napi 40 hívást lenyomnak és nem lehet papíron vezetni, hogy kivel mit beszéltek és mikor kell visszacsörögni.
Van egy funkció ami az emlékeztető hívásokat kezeli. Itt a táblában egy ido_vege oszlopban tárolom a dátumot, amikor vissza kell hívni a céget kb ilyen formában: 2009-12-15 20:20:00.
Van egy emlek.php ami ellenőrzi a session-be beléptetett user-t és az alapján megkeresi a hozzá tartozó emlékeztetőket, majd szét kéne választani azokat amik még nem aktuálisak és amik már lejártak, tehát hívni kell.
Megpróbáltam azt, bekérem az összes emléket és egy foreach-el elkezdek végiglépdelni rajta, majd megkérdezem a time()-ot és az emlékeztetőből készült instance ido_vege változóját strtotime()-al bekonvertálom timestamp-é és összehasonlítom majd egy if-el beteszem egy arraybe attól függ, hogy lejárt-e vagy sem. Ezzel viszont az volt a baj, hogy csak az if true állapotában lévő $lejart-ba tette be az objecteket, ráadásul amikor elkezdtem while($lejart as $emlek)-el kiiratni csak az utolsó ID-jű elem volt benne.
A kérdés: miért csak az utolsó elemet tette bele, ha array_push()-t használtam?
Szerencsésebb lenne két külön funkciót írni ami külön vizsgálja, hogy lejárt-e már az idő és ha igen akkor kiírja a másik meg akkor ha nem?
Szerencsésebb lenne a unix_timestamp-et tárolni az emlékeztetőben, hogy könnyebb legyen az összehasonlítás?
Az SQL-nél használjam a WHERE ido_vege =< "time()" módszert vagy PHP-vel hasonlítsam össze inkább?

Ennyi a kérdésem elsőre. Kérlek válaszoljatok türelemmel, és ha kell bevágom magát a kódot is, ha nagyon érthetetlenül írtam le ide.

Köszönettel, egy kezdő:
Kael
 
1

Néhány válasz

gphilip · 2009. Dec. 15. (K), 12.45
Szia!

Gyorsan egy lényegi reakció a kérdéseidre (jó kérdések egyébként, jó, hogy ilyeneket felteszel):

1. A Smarty egy template kezelő, ami natÍv PHP kódba fordÍt. Ez azt jelenti, hogy amikor változatsz a template-en, akkor a Smarty automatikusan lefordÍtja azt egy gyors PHP kóddá, amin nagyon kicsi valószÍnűséggel lehet bottleneck.

2. Az SQL kérések annál inkább, a legtöbb nagy látogatottságú oldalon ez jelenti a szűk keresztmetszetet. Erre megoldást jelenthet a cachelés mindenféle szinten, úgy mint böngésző, query cache (natÍv), query cache (memcached vagy más), controller cache, stb.

Nagy valószÍnűséggel azonban a te esetedben (napi 3000 látogató, nem tudom hány SQL query/oldal) nem ez jelentette a lassulás okát, sokkal inkább vlamiféle hibás gyakorlat.

Ilyen lehet például a(z átgondolt) indexelés hiánya az adatbázis tábláidban, vagy a rosszul kialakÍtott SELECT-ek (pl nem használsz LIMIT-et, hanem PHP-ban vágod le a nem kellő rekordokat, minden oszlopot kiválasztasz, stb).

3. Az emlÍtett példáűval kapcsolatban:
A helyes gyakorlat azt diktálja, hogy adatbázis oldalon szűrd le a kelő rekordokat, és semmiképpen sem PHP-ben, mert ebben az esetben az összes (régebbi) bejegyzés is visszatérÍtésre kerül a PHP-be, memóriát és gépidőt fogyaszt.

Így tehát a válasz ez:
"SELECT oszlop_kell1, oszlop_kell2 FROM emlek WHERE ido_vege =< NOW()"

A NOW egy SQL függvény, tehát nem szükséges PHP-ból lekérdezni az időt.

Nem tudom, miért nem ment a push-od, ezzel kapcsolatban egy megjegyzés:
Ha egy tömbbe szeretnél elemet helyezni, akkor sokkal hatékonyabb, ha Így teszed:
$tomb = array();
$tomb[] = $elem;

Még egy jó tanács: kapcsold be a PHP beállÍtásaiban a notice-ok kijelzését is, és Így sok elÍrést vagy hibát észrevehetsz, amti egyébként nem, mert alapértelmezetten a warningok és a fatal errorok jelennek csak meg. Persze az éles szerveren mindet ki kell kapcsolnod.

error_reporting(E_ALL);
error_reporting(0);
2

+1

vbence · 2009. Dec. 15. (K), 13.48
A legtöbb dolgot Gphilip leírta ami az oldal sebességét adja: ami leválogatás lehetséges mind SQL-ben csincsálni (néha megéri akár azon az áron is, hogy 2 query fusson egy helyett).

Ha saját szerver, használj slow query logot, ami rögzíti az 1 másodpercnél hosszabb queryke (ezt be kell állítani).

Használ profilingot. Készíthetsz egy shutdown functiont ami az oldal generálásának végén listázza a lefutott querylet és azok idejét (meg hogy mire ment el az idő).

Indexelj! Ami mezőre szűrsz vagy rendezel, az indexelés gyorsítani fogja az adatbázisműveleteidet. Én erre gyanakszom, mint fő okra.

Az indexelésnél ne csak egyes mezőkben gondolkozz, hanem összetett indexekben is (sőt, főleg azokban). Példa: van egy queryd, amiben egy user jövőbeli aktív visszahívásait listázod:
SELECT * FROM visszahivas WHERE time > NOW() AND aktiv=1 AND user=446 ORDER BY time;
Ha ezt a queryt szeretnéd gyorsítani, egy dolog, hogy létrehozol indexeket az aktuális mezőkre:
CREATE INDEX single1 ON visszahivas(time);
CREATE INDEX single2 ON visszahivas(aktiv);
CREATE INDEX single3 ON visszahivas(user);
A létező indexek közül azonban csak egy lesz használva (ez a tábla tartalmától függően az aktiv vagy a user mező indexe lesz). Sokkal hatékonyabb index pl:
CREATE INDEX multi1 ON visszahivas(aktiv, user, time);
Így veszi az alktív rekordokat, ezen belül az aktuális userhez kötődőket, majd ezenbelül kap egy idő szerint rendezett recordsetet, amiből egy összefüggő rész képezi az eredményhalmazt.

A sorrend nagyon fontos. Ha a time lenne elől, akkor gyakorlatilag mien későbbi indexelt mező fölösleges, tekintve, hogy a time (valószínűleg) egyedi értékeket hordoz. A (time, aktiv, user) index nem lesz hasznosabb a (time) indexél.

Az indexben próbáld meg a legtöbb rekordot csoportosító mezőket előrébb helyezni (pl az "aktív" mezőnek csak 2 állása lehetséges). De nagyban múlik az adataid összetételén is, célszerű kipróbálni a (user, aktiv, time) sorrendet is.
3

summa summarum

Kael · 2009. Dec. 15. (K), 15.27
Köszönöm a remek válaszokat.
Elég sok info van benne amit fel kell dolgozzak. De a lényeget megértettem.
Kellemes napot és ha még kérdésem van, felteszem, egyébként meg böngészek.

Kael