PHP a Nette v nekonečném cyklu na serveru
- pawouk
- Člen | 172
Ahoj, chci se jen optat zda máte někdo zkušenost s PHP v nekonečném cyklu na serveru, dělám ted jeden projekt kde potřebuji na serveru software který bude neustále číst externí ethernetové zařízení tak váhám zda je PHP a Nette vhodná volba. Máte s něčím podobným někdo zkušenosti? Díky
- saimons
- Člen | 293
Moc zkusenosti s tim nemam, jen takove prakticke veci co jsem zjistil, kdyz jsem neco testoval s hodne dlouhymi scripty. Ikdyz jsem na serveru nastavil cas vykonavani scriptu na dlouho dobu, tak mi aplikace padala po nake dobe kvuli nedostatku pameti. Nevim jestli se to da nak poresit, ale ikdyz jsem to zkousel zmenit, tak to stejne po case padlo jejim vycerpanim.
- mildabre
- Člen | 62
S Nette bych na to nešel, je to framework orientovaný na běžné aplikace a ty máš hodně specifickou věc. Ono ani PHP na tohle není úplně zamýšleno že? Zkus třebas jeden dlouhý proces udělat jako sérii krátkých a spouštěj cronem – myslíš že by to takhle šlo ?
Editoval mildabre (13. 12. 2012 1:57)
- LeonardoCA
- Člen | 296
Jde to i v php a s nette. Jen se musí dodržovat pár zásad. Testováno na skriptu, který parsuje tisíce XML souborů o velikosti až jednotek MB které průběžně získává přes curl z externí služby a volá tisíce sql dotazů. Kromě paměti, kterou potřebuje na data si php i během několika hodin nevezme navíc jediný byte. Ale je to celkem sranda odladit, když člověk nemá zkušenosti. (skript vlastně běžel skoro dva dny a více než 200 000 poměrně náročných cyklů, než spadl, když jsem mu pod rukama nedopatřením změnil struktůru databáze)
Hlavní tip: paradoxně – vůbec se nestarat se o uvolňování paměti a nechat to na php. Ale zároveň nebránit uvolňování paměti špatnými konstrukcemi a nezabírat paměť zbytečnými proměnnými. Jinak je to téma na celý článek.
Nette samotné je napsáno dobře a paměť nežere.
Editoval LeonardoCA (13. 12. 2012 3:05)
- Filip Procházka
- Moderator | 4668
@LeonardoCA: jak řešíš připojení do databáze? Vytvoříš persistent? Nebojíš se, že to spadne? Nebo když to spadne, tak jen chytíš exception a připojíš znovu?
Jdu zrovna psát robota na Nette, protože už skoro nedokážu psát v čistém PHP :) A té databáze se maličko bojím…
- Nox
- Člen | 378
Souhlas, spíš bych řekl, že to neustálé spouštění je právě naopak v PHP problém, i pokud zapojíme různé akcelerátory, stejně ta inicializace bude pomalejší než v ostatních jazycích, vše se zahodí a zase startuje znovu (což je i výhoda, ale tady zrovna moc ne).
Taky mám s dlouhoběžícím Nette dobré zkušenosti, zatím teda ne v řádu dní, ale hodin.
Samozřejmě je hlavní vše zpracovávat průběžně a nepoužívat
konstrukce typu „načtu vše a pak to projdu“. A bacha jestli neprobíhá
nějaké logování nebo cachování v paměti bez toho, aby si kontrolovalo
velikost.
Jestli je problém s manuálním uvolňováním nevim, radši vše
(v takových skriptech) ruším hned jak vím, že to není potřeba, než až
PHP zjistí, že už to není potřeba.
- Filip Procházka
- Moderator | 4668
V aplikacích, které běží dlouhodobě (třeba .NET, …) mají DI Containery takovou speciální feature – starají se o životnost služeb, které vytváří. Takové službě se dá říct, že má například existovat jenom pro jeden HTTP požadavek a pak se má zahodit (a pro další se vytvoří znovu).
Tuhle jsem si říkal, že by nemuselo být špatné něco takového zkusit napsat nad Nette\Application – takový malý http server :) Domnívám se, že by to mohlo drasticky zvýšit výkon PHP aplikací, ale také drasticky zvýšit jejich komplexitu :-/
Ale zpět k tématu… co ta databáze? :)
- Milo
- Nette Core | 1283
@HosipLan: S databází je to specifické případ od případu. Záleží, jak často do ní potřebuješ sáhnout a jestli forkuješ. Při forkování je to trochu peklo. PHP samotné neumí pořádně předávat databázové připojení potomkům, takže co potomek, to připojení k DB.
Perzistentní připojení nepotřebuješ, jelikož Ti ten proces běží stále. Dobré je si připojení k databázi schovat do samostatné metody a db pingovat, cca:
private function getConnection()
{
if ($this->connection) {
pg_ping(...) until success
} else {
pg_connect(...);
}
return $this->connection;
}
Při forkování záleží, jak moc často děti přistupují do db a kolik je dětí. Když málo dětí (do 20) vyplatí se, aby každé mělo neustále otevřené svoje připojení. Když je jich víc, tak záleží jak často přistupují. Když málo často, otevřít připojení, dotaz, zavřít. Když hodně často, tak jsem to vyřešil vlastním driverem do dibi. Rodič otevře DB spojení a UNIX socket, potomci si každý otevře svoje připojení na UNIX socket a přes to tuneluji dotazy. To se dá naroubovat i na sdílenou paměť a pod…
- bazo
- Člen | 620
napisal som daemony ktore bezia mesiace v kuse, nacitavaju spravy z rabbit mq a spracuvaju ich. pouzivaju sluzby z nette. skript daemona si nacita bootstrap php a ma vsetko k dispoizicii. o loopovanie sa stara rabbit mq kniznica, ale normalny while by fungoval tiez.
ako db sa pouziva mongo db cez doctrine odm. spojenie s db nepadlo ani raz, specialne som nenastavoval nic, ale predpokladam, ze pri spracovani kazdeho messagu sa vytvorilo nove spojenie.
forkovanie podprocesov som v daemonoch nepouzil, iba v cronoch, ale celkom spokojny som bol s power process kniznicou. inak na githube su mraky kniznic na daemonizovanie a forkovanie procesov.
na monitoring a manazment tych daemonov som pouzival supervisord, super vecicka, spusti hociaky php skript ako daemon.
co sa tyka pamate tak vsetky premenne po pouziti setnem na null a este zavolam gc collector. setovanie na null dost setri pamat, pri gc collect som nejaky extra rozdiel nespozoroval
- LeonardoCA
- Člen | 296
pro připojení na MySQL nepoužívám zásadně nikdy perzistent connection, nepamatuji se už proč, ale asi z toho důvodu, že kdyby procesy zůstavaly z nějakého důvodu viset, ať neblokují otevřené connection.
S connection problém nemám, problémy vznikají až při běhu více takových skriptů paralelně, kdy se občas vyskytne deadlock, což se dá taky vyladit, ale raději se tomu vyhýbám celkovou architekturou procesů.
Celkový výpadek databáze zatím ošetřený nemám, prostě se to snažím udělat tak ať datábázi skriptem nezahltím, a když server spadne, tak spadne stejně všechno. Ale nějaké ošetření bych tam ještě měl přidat.
- LeonardoCA
- Člen | 296
Co se týče gc collect, tak jsem zkoušel asi den, dva různé experimenty a nikdy se to nechovalo podle očekávání. PHP si paměť stejně uvolňuje až když se mu chce (zjednodušeně). Našel jsem i zajimavé zdroje, jak PHP mapuje proměnné a reference interně, ale to je tak na týden, 14 dní studia, aby to člověk pochopil, vyzkoušel a dospěl k nějakému použitelnému závěru.
Dával jsem gc_collect na různé místa, ale nikdy se mi nepodařilo uvolnit paměť, když jsem chtěl já.
- řekl bych že pomáhá mít dobře rozčleněný kód do bloků, protože PHP samo automaticky uvolňuje paměť právě ve chvíli kdy ukončí práci v nějakém bloku.
- proměnné většinou ani nenastavuji na null, protože je používám opakovaně a tak se přepisuje už jen paměť, kterou mají rezervovanou, a je to možná i efektivnějšší a hlavně jsem se snažil nepoužívat skoro žádné dočasné proměnné
- místo cyklů foreach, atp, se vyplatí používat funkce array_map, array_filter a podobné, případně iterátory
Editoval LeonardoCA (13. 12. 2012 15:54)
- Nox
- Člen | 378
Co jsem našel v komentářích u J. Knesla (Hosiplan, M. Ponkác. D.
Milde):
Array_*: 4× pomalejší než foreach, 2.5× víc zabrané paměti jak
foreach…
(a ten rozdíl v čitelnosti mi taky nepřijde velký a řádky se ušetří
dva, cena za to je dost bolestná)
Reusování proměnných zní jako dobrý nápad. Z dočasných proměnných bych vyjmul zapamatování si výpočtů, tam se to hodí uchovat pro zvýšení rychlosti.
- LeonardoCA
- Člen | 296
@Nox můžeš hodit odkaz na to kde se řešilo array vs foreach? Moc mi to nesedí, možná ty funkce používám jiným způsobem. Až bude čas, zkusím porovnat…
- LeonardoCA
- Člen | 296
Co mě trklo, tak v komentářích odkazují na nějaký test s číselnými poli a triviální operací a to má do praxe dost daleko. Myslím si že při komplexnějších operacích nad víceúrovňovými associativními poli se projeví úplně jiné faktory a výsledek bude vypadat jinak. Koncem roku zkusím udělat nějaké testy na reálných datech a hodím to sem.
Jedna věc je, že php díky interní datové struktůře při práci s poli zabírá nehorázně více paměti než je skutečná velikost dat a je jasný, že foreach iterátor nezabere nic navíc. Druhá věc je, že při použití array funkcí si php samo efektivně paměť pohlídá a ve foreach si to musíš víc hlídat sám. Při komplexnějších úlohách pochybuju, že bude foreach vždy efektivnější, minimálně se rozdíly smažou.
- Nox
- Člen | 378
Tak to netušim, jestli array_* obsahují nějaké vychytávky. Jinak pokud potřebuješ paměťově efektivní pole, nevim jestli znáš SplFixedArray (jenže to pak zase skoro určitě nepůjde dát do array_*). Ale vždy, když jsem ho chtěl použít, jsem potom radši vymyslel, jak udělat zpracování průběžnější a tím to ztratilo smysl.
Editoval Nox (16. 12. 2012 19:20)
- Filip Procházka
- Moderator | 4668
Tohle jsou jen dohady, fakta jsou zde. Udělej ten benchmark a pak uvidíme:)
- pawouk
- Člen | 172
Zdravím, tak už mi jede muj první démon na PHP + nette. S Nette není vůbec problém (obával jsem se jestli nebude žrát moc paměti ale ne). No já defakto používám jen Debugger a Database. Problém ale nastal v MySQL což se dalo očekávat. Nevím jak ověřit zda je MySQL ready. Tedy Vytvořím connection a používám jej, někdy často jindy málo, včera se mi stala věc, že se connection nepoužilo cca 9 hodin a poté to vyhodilo výjimku MySQL server has gone away. Potřeboval bych si udělat metodu getConnection() které mi ověří zda je connecion vpohodě a pokud ne vytvoří nové a původní zahodí. Ale jak to ověřit? Existuje nějaká způsob ověření connection zda je ready? Samozřejmě když se zeptam if($connection) tak mi to vrati TRUE, ale to nic neřeší. Díky za rady
Editoval pawouk (20. 12. 2012 12:10)
- LeonardoCA
- Člen | 296
Nezkoušel jsem to, ale asi bych udělal nějaký testovací „select“ dotaz, zachytil na něm tu výjimku a v připadě „gone away“ vytvořil novou connection.
(Kdyby měl být skript brutálně robustní, tak by měl být schopen vychytat i stavy, kdy db server neodpoví třeba kvůli přetížení, když běží zálohy a pak by to chtělo zachytávat výjimky nad celým skriptem a po nějakém čase zopakovat předchozí neúspěšný cyklus. To samé se mi stává u externí služby, že má občas pár minut výpadek. Ale to záleží na tom co se zpracovává, je li to třeba řešit)
Editoval LeonardoCA (20. 12. 2012 17:44)
- pawouk
- Člen | 172
No přesně to mě také napadlo a také to tak řeším, ale nepřijde mi to ideální.
private function getDatabase() {
if (!$this->_database)
$this->_database = $this->createDatabaseConnection();
// try get simple query
try {
$row = $this->_database->table('user')->fetch();
} catch (\PDOException $e) {
\Nette\Diagnostics\Debugger::log($e, \Nette\Diagnostics\Debugger::ERROR);
$this->_database = $this->createDatabaseConnection();
}
return $this->_database;
}
Funguje to dobře, ale trochu mi to přijde plýtvání dotazů…
Editoval pawouk (21. 12. 2012 20:48)