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.

25 minut · PHP a CSV

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

  1. Použij fopen a fgetcsv místo file(), file_get_contents() nebo načtení všech řádků do pole.
  2. Oddělovač, enclosure a escape nastav explicitně. Pro běžné RFC 4180 CSV použij prázdný escape a uvozovky zdvojené uvnitř pole.
  3. Hlavičku normalizuj jednou a u každého řádku ověř stejný počet sloupců. Zohledni BOM a deklarované kódování.
  4. Čí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

  1. 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.
  2. 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ěť.
  3. 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.
  4. 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í

  1. 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.
  2. Vypni detailní SQL logování pro dlouhý import a sleduj jen agregované metriky a omezený vzorek chyb.
  3. Import identifikuj vlastním ID a ukládej poslední potvrzený checkpoint. Po pádu opakuj nedokončenou dávku idempotentně.
  4. 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.

  1. 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
  2. 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.

  3. 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.

Zavolejte mi

Zavolám vám následující pracovní den mezi 9:00 a 17:00.

Můžete mi také zavolat rovnou.

+420 605 181 728

Nechte mi telefonní číslo a pošlete žádost o zpětné zavolání.

Odesláním souhlasíte se zpracováním údajů pro vyřízení žádosti.