Selection – Při iteraci dochází paměť
- ondrej256
- Člen | 187
Zdravím,
narazil jsem na problém s pamětí v nette. Používám nette/database v2.4.11. PHP memory_limit mám 1024MB
Pokud si s databáze vytáhnu knihy, kterých je 112 881 a iteruju nad nima + nad každou knihou uvnitř ještě iteruji nad tagy, tak mě dojde paměť a vyskočí chyba:
Fatal Error: Allowed Memory Size of x Bytes Exhausted
$bookSelection = $this->bookRepository->findAll(); // 112 881 záznamů
foreach ($bookSelection as $book) {
$items = [];
$tags = $book->related('tag');
$filteredTags = [];
foreach ($tags as $tag) { // kniha větsinou obsahuje 2-4 tagy
if (...) { // nejaka podminka, ktera neni dulezita
$filteredTags[] = $tags;
}
}
$this->processAuthors($authors); // funkce vyzaduje array, nesmi to byt selection
}
Ani jedna tabulka (authors ani tag) neobsahuje velký počet sloupců nebo
nějaké drasticky velké data.
Je možné, že si nette database vytváří nějaké interní
struktury/objekty/cokoliv co má vysoké nároky na paměť?
Nebo je to tím že je to prostě velké množství dat, které samo o sobě zabere celou paměť?
Předem díky za odpověď
Editoval ondrej256 (17. 3. 2021 12:18)
- Pavel Kravčík
- Člen | 1196
Dotazy ve loopech je většinou cesta do pekel. Pokud už děláš nějaký složitější dotaz, ideálně ho udělej v jednom SQL. Pokud Ti jde čistě o seznam tagů ke knize – podívej se na GROUP BY.
- David Grudl
- Nette Core | 8239
Pavel Kravčík napsal(a):
Dotazy ve loopech je většinou cesta do pekel. Pokud už děláš nějaký složitější dotaz, ideálně ho udělej v jednom SQL. Pokud Ti jde čistě o seznam tagů ke knize – podívej se na GROUP BY.
To není pravda, právě tohle NDBE řeší. Ale proč dochází paměť netuším.
- Pavel Kravčík
- Člen | 1196
Předpokládám, že vně toho kódu budou nějaké nové dotazy, co nejsou vidět v příkladu a ty to shazují.
OT: btw nejdeš komentovat z nějakého důvodu, jen nahlásit.
- ondrej256
- Člen | 187
Sice jsem ten kód zjednodušil, ale žádné další skryté dotazy, které by mohly dělat problémy tam neprobíhají. Nicméně jsem udělal zajímavý objev.
Pro upřesnění kód spouštím přes konzoli (kdyby/console)
1. Pokus
- Kód úspěšně proběhne
$bookSelection = $this->bookRepository->findAll(); // 112 881 záznamů
foreach ($bookSelection as $book) {
$items = [];
$tags = $this->tagRepository->findByBookId($book['id']);
// $tags = $book->related('tag'); // RELATED SE NYNI NEPOUZIVA, taham primo pres tagRepository, tzn. bez JOINŮ
$filteredTags = [];
foreach ($tags as $tag) { // kniha větsinou obsahuje 2-4 tagy
if (...) { // nejaka podminka, ktera neni dulezita
$filteredTags[] = $tags;
}
}
$this->processAuthors($authors); // funkce vyzaduje array, nesmi to byt selection
}
2. Když smažu cache
$bookSelection = $this->bookRepository->findAll(); // 112 881 záznamů - spadne hned na prvním řádku na paměti
foreach ($bookSelection as $book) {
$items = [];
$tags = $book->related('tag');
$filteredTags = [];
// PRVNI ITERACE PROBEHNE, ALE DALSI ITERACE UZ NEPROBEHNE, DOJDE PAMET
foreach ($tags as $tag) {
// TADY UZ NEDOJDE ANI K PRVNI ITERACI
...
}
...
}
3. Když smažu cache a spustím
$bookSelection = $this->bookRepository->findAll()->limit(1); // OMEZIM POUZE NA JEDEN ZAZNAM
foreach ($bookSelection as $book) {
$items = [];
$tags = $book->related('tag'); // FUNGUJE I S POUZITIM RELATED
$filteredTags = [];
foreach ($tags as $tag) { // kniha větsinou obsahuje 2-4 tagy
if (...) { // nejaka podminka, ktera neni dulezita
$filteredTags[] = $tags;
}
}
$this->processAuthors($authors); // funkce vyzaduje array, nesmi to byt selection
}
tak nyní skript v pořádku s jedním záznamem doběhne. A pokud v tuto chvíli vyhodím ->limit(1) a nechám skript běžet v původním stavu, tak najednou doběhne v pořádku. Mám z toho dojem, že je to nějaký problém s cachováním. Jakoby to po smazání cache dotahovalo hrozně moc informací, které zaberou spoustu paměti, ale při druhém pokusu už jsou ty informace v cachi a vše proběhne bez problémů.
- Polki
- Člen | 553
Nette si namapuje, jaké dotazy nad čím děláš. Pokud děláš dotazy v cyklu, tak při prvním průchodu si nette mapuje, co taháš, ale aby to fungovalo musí dotazy udělat zvlášť. To znamená více dotazů, větší traffic a taky větší zátěž.
Jakmile ale jednou doběhnou dotazy v pohodě (například když dáš limit(1)), tak Nette zjistí, jakým způsobem to taháš a vytvoří si nejefektivnější možné dotazy a ty uloží pro ten který případ do cache.
V dalším volání už to bude strukturu dotazu tahat z cache, což
zapříčiní to, že se provedou dotazy jen 2 Jeden na tabulku
book
a druhý na tabulku tag
, čímž se ten traffic
neuvěřitelně zmenší. Proto ti to před vytvořením cache padá a po
vytvoření cache ne, ale po smazání cache zase jo.