Praktický návod
Jak importovat miliony řádků bez vyčerpání paměti
Soubor čti jako stream, zpracuj jeden řádek a po omezené dávce ulož i uvolni stav.
Nejdřív stručně
Paměť musí zůstat téměř konstantní
Milion řádků nesmí vzniknout jako milion prvků v PHP poli. Stream drží v paměti jen malou část vstupu a batch omezuje počet čekajících databázových změn.
Import je zároveň datový proces: musí validovat formát, umět pokračovat po chybě a bezpečně poznat již zpracovaný záznam. Pouhé zvýšení memory_limit problém pouze odsune.
Připrav si
Co budeš potřebovat
Než začneš optimalizovat zápis, přesně definuj vstupní formát a chování chybných řádků.
- Ukázkové CSV včetně hlavičky, kódování, oddělovače, uvozovek, prázdných hodnot a víceřádkových polí.
- Stabilní externí identifikátor nebo jiný idempotentní klíč pro upsert a opakované spuštění.
- Pravidla validace a rozhodnutí, zda chybný řádek zastaví import, nebo skončí v samostatném reportu.
- Metriky zpracovaných, vložených, aktualizovaných a odmítnutých řádků, času dávky a využité paměti.
Kroky 1 až 3
Postav restartovatelnou pipeline
Čtení, převod a zápis odděl. Díky tomu můžeš měnit velikost dávky nebo databázovou strategii bez přepisu parseru.
1. Čti CSV po jednom záznamu
- Použij fopen a fgetcsv místo file(), file_get_contents() nebo načtení všech řádků do pole.
- Oddělovač, enclosure a escape nastav explicitně. Pro běžné RFC 4180 CSV použij prázdný escape a uvozovky zdvojené uvnitř pole.
- Hlavičku normalizuj jednou a u každého řádku ověř stejný počet sloupců. Zohledni BOM a deklarované kódování.
- Číslo logického záznamu neodvozuj slepě od fyzického řádku, pokud CSV dovoluje nové řádky uvnitř uvozovek.
$handle = fopen($path, 'rb');
while (($row = fgetcsv($handle, null, ',', '"', '')) !== false) {
yield $row;
} Oficiální PHP dokumentace k fgetcsv 2. Validuj a zapisuj po dávkách
- Každý řádek převeď do malého vstupního objektu, validuj a ihned přidej do omezené dávky. Chybové tělo neukládej bez limitu v paměti.
- Velikost dávky změř, například 500 až 5 000 řádků. Větší batch snižuje režii, ale prodlužuje transakci a zvyšuje paměť.
- Pro čistý import do PostgreSQL preferuj staging tabulku a COPY. Složitou doménovou logiku může zpracovat DBAL nebo ORM v menších dávkách.
- Každou dávku potvrď samostatně a ulož checkpoint. Jedna transakce přes milion řádků drží zámky, WAL i případný rollback příliš dlouho.
COPY product_import (external_id, name, price) FROM STDIN WITH (FORMAT csv); Oficiální PostgreSQL dokumentace k COPY 3. Uvolňuj stav a umožni pokračování
- Při Doctrine ORM po každém batchi zavolej flush a clear. Jinak Unit of Work dál drží reference na všechny entity.
- Vypni detailní SQL logování pro dlouhý import a sleduj jen agregované metriky a omezený vzorek chyb.
- Import identifikuj vlastním ID a ukládej poslední potvrzený checkpoint. Po pádu opakuj nedokončenou dávku idempotentně.
- Originální soubor uchovej pod checksumem a neměň ho během pokračování. Jiný obsah musí být nový import.
$entityManager->flush(); $entityManager->clear(); Oficiální Doctrine dokumentace k batch processingu Krok 4
Ověř konstantní paměť a obnovu
Import musí zvládnout celý soubor, chybný vstup i restart uprostřed bez duplicit.
-
Vygeneruj velký testovací soubor
Spusť import nad milionem reprezentativních řádků a po každé dávce loguj memory_get_usage(true). Křivka nesmí stále růst.
php -d memory_limit=256M bin/console app:import products.csv -
Vlož poškozené řádky
Ověř špatný počet sloupců, neplatné kódování, prázdný klíč a chybu uprostřed dávky. Report musí ukázat omezený a srozumitelný kontext.
-
Ukonči proces uprostřed
Po restartu pokračuj od checkpointu. Výsledné počty a unikátní klíče musí odpovídat jedinému úplnému importu.
Když to zlobí
Nejčastější chyby
Paměť roste po každé dávce
Zkontroluj Unit of Work, akumulovaný chybový report, SQL logger a vlastní pole statistik. Flush bez clear reference na entity neuvolní.
Import je pomalý i s malým využitím CPU
Měříš režii jednotlivých INSERTů nebo čekání na I/O. Použij připravené dávky, DBAL nebo COPY do staging tabulky a dolaď batch size.
Po restartu vznikají duplicity
Chybí idempotentní klíč nebo checkpoint ukazuje na nepotvrzenou práci. Opakuj celou poslední dávku přes upsert a checkpoint posuň až po commitu.
CSV se rozpadá na řádcích s uvozovkami
Nesplituj soubor přes explode ani regulární výraz. Použij CSV parser se správným delimiterem, enclosure a explicitním escape.
Hotovo
Import škáluje podle času, ne podle paměti.
Pipeline teď čte CSV jako stream, ukládá omezené dávky a umí bezpečně pokračovat. Stejný princip použij pro každý objemný feed nebo backfill.