Efektywny import danych w Laravel – odczyt strumieniowy, chunkowanie i queue

Efektywny import danych w Laravel – odczyt strumieniowy, chunkowanie i queue

1. Wprowadzenie

Efektywny import danych w Laravel – dlaczego to temat, który warto znać?

Współczesne aplikacje Laravel bardzo często muszą przetwarzać duże zbiory danych pochodzące z plików CSV, Excel, JSON lub XML. Przykłady są liczne:

  • import tysięcy produktów do sklepu internetowego,
  • synchronizacja użytkowników z systemu ERP,
  • przetwarzanie raportów z platform finansowych,
  • aktualizacja danych mieszkań, lokali i nieruchomości od deweloperów.

Na pierwszy rzut oka może się wydawać, że wystarczy „przelecieć” plik pętlą foreach i wstawić dane do bazy. Niestety – to podejście działa tylko dla małych plików. Gdy pojawia się plik ważący 200 MB, a w nim 1 000 000 rekordów — Laravel może się „zadławić”:

  • Przekroczenie limitu pamięci PHP (Allowed memory size...)
  • Przekroczenie czasu wykonania (Maximum execution time...)
  • Zawieszony worker lub timeout podczas przetwarzania
  • Użytkownik, który nie wie, czy import się zakończył

To nie tylko problem techniczny — to również problem UX i kosztów serwera.


Co znajdziesz w tym artykule?

W tym przewodniku pokażę Ci wydajne i odporne na błędy podejścia do importu dużych plików w Laravel:

  • jak przetwarzać pliki liniowo (streaming),
  • jak je dzielić na mniejsze paczki (chunkowanie),
  • jak zastosować kolejki i batch processing (queue, jobs, batch).

Wszystko z praktycznymi przykładami kodu, które można od razu wdrożyć w swoim projekcie.


Kiedy ten artykuł Ci się przyda?

  • Masz plik CSV od klienta i chcesz zaimportować dane do bazy
  • Przetwarzasz duże XML/JSON z systemu zewnętrznego
  • Budujesz aplikację SaaS i chcesz umożliwić użytkownikom import danych
  • Nie chcesz zawieszać serwera przy każdym większym pliku

W kolejnym punkcie pokażę, czego NIE robić – czyli jakich podejść unikać, jeśli zależy Ci na stabilności i wydajności aplikacji.

2. Pułapki – czego nie robić

Zanim przejdziemy do dobrych praktyk i rozwiązań, warto omówić kilka często popełnianych błędów, które pojawiają się podczas pracy z dużymi plikami w Laravelu. Te pułapki mogą prowadzić do poważnych problemów ze stabilnością aplikacji i znacznym zużyciem zasobów serwera.


file() i Storage::get()

Te funkcje ładują cały plik do pamięci RAM, co jest całkowicie nieopłacalne (i niebezpieczne) przy większych plikach:

$lines = file(storage_path('app/import.csv'));

Albo:

$content = Storage::get('import.csv');
$lines = explode("\n", $content);

🔴 Problem: Nawet plik 200 MB potrafi zająć kilka gigabajtów pamięci, zwłaszcza po rozbiciu go na linie i dalszym przetwarzaniu.


❌ Zbyt długa pętla foreach bez chunkowania

Często spotykany błąd:

foreach ($lines as $line) {
    // parsowanie, walidacja, insert
}

🔴 Problem: brak limitowania, brak monitoringu, a także brak możliwości wznowienia przetwarzania w razie błędu. To prosta droga do przeciążenia serwera.


❌ Bezpośredni import do bazy bez transakcji i kolejek

Wielu początkujących programistów próbuje natychmiastowo wstawiać dane do bazy:

DB::table('products')->insert($parsedRow);

🔴 Problem: Przy importach powyżej kilku tysięcy rekordów zaczynają się:

  • timeouty,
  • błędy blokad,
  • konflikty transakcyjne,
  • spadki wydajności całej aplikacji.

❌ Brak komunikacji z użytkownikiem

Nie tylko backend cierpi. Często użytkownik:

  • nie wie, czy import się rozpoczął,
  • nie otrzymuje powiadomienia po zakończeniu,
  • nie ma dostępu do raportu błędów lub logów.

To zły UX i potencjalne źródło frustracji.


✅ Co zamiast tego?

W kolejnych częściach artykułu pokażemy:

  • jak wykorzystać strumieniowe przetwarzanie danych,
  • jak używać chunkowania i kolejek,
  • jak skalować i zabezpieczać cały proces importu.

Przejdźmy teraz do konkretów – zacznijmy od strumieniowego odczytu plików!

3. Techniki przetwarzania dużych plików w Laravel – streaming, chunking, queue

W tej części skupimy się na trzech głównych podejściach do wydajnego przetwarzania dużych plików w Laravel:

  • strumieniowanie danych (streaming),
  • dzielenie danych na paczki (chunking),
  • przetwarzanie asynchroniczne za pomocą kolejek (queue, jobs, batch).

Każde z tych rozwiązań ma swoje miejsce i zalety, a często najlepszym podejściem jest łączenie ich ze sobą.


3.1. Streaming – odczyt danych linia po linii

Streaming pozwala przetwarzać plik bez ładowania go całkowicie do pamięci RAM. Idealne dla dużych plików tekstowych, CSV, TSV.

📌 Przykład: streaming pliku CSV z dysku lokalnego

use Illuminate\Support\Facades\Storage;

$handle = Storage::readStream('import.csv');

while (($line = fgets($handle)) !== false) {
    $columns = str_getcsv($line);
    // przetwarzaj $columns
}

fclose($handle);

To podejście jest lekkie, stabilne i wyjątkowo odporne na duże rozmiary pliku.

✅ Zastosowania:

  • import plików powyżej 100 MB,
  • przetwarzanie rekordów 1:1,
  • ograniczony dostęp do zasobów serwera.

3.2. Chunking – dzielenie danych na paczki

Chunking polega na przetwarzaniu danych w ustalonych porcjach. Laravel oferuje LazyCollection, które idealnie nadają się do tego zadania.

📌 Przykład: chunkowanie streamu

use Illuminate\Support\LazyCollection;

LazyCollection::make(function () {
    $handle = fopen(storage_path('app/import.csv'), 'r');
    while (($line = fgets($handle)) !== false) {
        yield str_getcsv($line);
    }
    fclose($handle);
})
->chunk(1000)
->each(function ($chunk) {
    foreach ($chunk as $row) {
        // przetwarzanie rekordu
    }
});

✅ Zastosowania:

  • walidacja i przygotowanie danych,
  • grupowe zapisy do bazy danych,
  • redukcja liczby zapytań INSERT.

3.3. Kolejki – asynchroniczne przetwarzanie danych

Zamiast przetwarzać wszystko od razu, możemy delegować każdy rekord lub każdą paczkę danych do osobnego zadania (Job) i wykonać je w tle.

📌 Przykład: dispatchowanie do kolejki

foreach ($rows as $row) {
    ImportCsvRow::dispatch($row);
}

ImportCsvRow.php

class ImportCsvRow implements ShouldQueue
{
    public function __construct(public array $row) {}

    public function handle()
    {
        // Walidacja i zapis do DB
    }
}

✅ Zastosowania:

  • import danych bez obciążania requestu,
  • retry przy błędach,
  • możliwość śledzenia postępu i logowania.

W kolejnej części pokażemy realne scenariusze i przykłady, np. jak zaimportować 1 milion rekordów CSV z wykorzystaniem tych podejść razem.

4. Praktyczne przypadki importu danych w Laravel

W tej części przedstawimy trzy realistyczne scenariusze, które często występują w aplikacjach opartych o Laravel. Każdy przypadek pokazuje, jak dobrać odpowiednie techniki opisane wcześniej: streaming, chunking i kolejki.


🧾 Przypadek 1: Import pliku CSV z 1 milionem produktów

Sytuacja: klient przesyła plik CSV z danymi produktów (ID, nazwa, opis, cena, stan magazynowy, kategoria).

Rozwiązanie:

  • Użycie Storage::readStream() + fgets() do odczytu linii,
  • Każdy rekord przekazywany do kolejki jako osobne zadanie (Job),
  • Walidacja danych i zapis do bazy w tle,
  • Logowanie błędnych rekordów do osobnego pliku lub tabeli.

Bonus: dodanie postępu przetwarzania (np. przez Redis lub DB counter).


📄 Przypadek 2: Przetwarzanie dużego pliku XML z systemu deweloperskiego

Sytuacja: plik XML z danymi mieszkań/lokali o rozmiarze 300 MB zawiera zagnieżdżoną strukturę z property, building, unit itd.

Rozwiązanie:

  • Użycie natywnej klasy PHP XMLReader (działa jak streaming),
  • Iteracja przez while ($reader->read()) tylko po wybranych nodach,
  • Parsowanie danych i wrzucanie ich do kolejki batchowanej (grupy rekordów),
  • Przekształcenie danych do formatu CPT (np. wtyczka WordPressa przez REST API).

Zaleta: minimalne zużycie pamięci, niezależnie od rozmiaru pliku.


📊 Przypadek 3: Import pliku Excel (.xlsx) od użytkownika SaaS

Sytuacja: użytkownik wrzuca przez frontend plik .xlsx z danymi klientów (imię, nazwisko, e-mail, numer telefonu).

Rozwiązanie:

  • Użycie biblioteki Laravel Excel (maatwebsite/excel),
  • Wczytywanie danych z ToCollection zamiast ToModel (więcej kontroli),
  • Podzielenie danych na chunk w pamięci i wysyłanie ich do kolejki,
  • Wysyłka maila po zakończeniu przetwarzania z podsumowaniem i błędami.
public function collection(Collection $rows)
{
    $rows->chunk(100)->each(function ($chunk) {
        ImportCustomersJob::dispatch($chunk);
    });
}

Wskazówka: zawsze limituj rozmiar uploadu i dodaj walidację struktury.


W kolejnej części zaprezentujemy konkretne narzędzia i biblioteki, które ułatwiają cały proces importu.

5. Narzędzia i biblioteki warte uwagi

Aby efektywnie przetwarzać duże pliki w Laravelu, warto skorzystać z wyspecjalizowanych narzędzi i bibliotek. W tej sekcji prezentujemy te, które najlepiej sprawdzają się w pracy z plikami CSV, XML, Excel oraz w zarządzaniu kolejkami.


📦 Laravel Excel (maatwebsite/excel)

Zastosowanie: Import/eksport plików Excel (.xlsx, .xls, .csv) z dużą elastycznością.

Zalety:

  • obsługa kolejek,
  • import chunkowany,
  • wysoka integracja z Eloquent,
  • walidacja i mapowanie danych.
composer require maatwebsite/excel

Uwaga: Dla bardzo dużych plików lepsze może być prostsze podejście (np. fopen()), bo Laravel Excel tworzy bufor w pamięci.


📦 Spatie Simple Excel

Zastosowanie: ultralekki parser i zapis plików CSV bez zależności zewnętrznych.

composer require spatie/simple-excel

Zalety:

  • obsługuje streaming,
  • niski narzut pamięci,
  • idealny do prostych struktur CSV,
  • działa z LazyCollection.
use Spatie\SimpleExcel\SimpleExcelReader;

SimpleExcelReader::create(storage_path('app/import.csv'))
    ->getRows()
    ->each(function(array $row) {
        dispatch(new ImportCsvRow($row));
    });

📦 League CSV

Zastosowanie: czytanie/zapisywanie CSV z kontrolą delimitera, nagłówków, walidacji.

Zalety:

  • bardzo elastyczna konfiguracja,
  • strumieniowy zapis,
  • stabilna dokumentacja,
  • zgodność z PSR.
composer require league/csv

📦 XMLReader (natywne PHP)

Zastosowanie: streamingowe odczytywanie dużych plików XML, idealne do danych z ERP lub systemów deweloperskich.

Zalety:

  • bardzo szybki,
  • nie ładuje całego drzewa XML do pamięci,
  • minimalne zużycie zasobów.
$reader = new \XMLReader();
$reader->open(storage_path('app/oferty.xml'));
while ($reader->read()) {
    if ($reader->nodeType === XMLReader::ELEMENT && $reader->name === 'local') {
        $xml = $reader->readOuterXML();
        // przetwórz lokal
    }
}

📦 Laravel Horizon

Zastosowanie: monitorowanie i zarządzanie kolejkami Laravel w czasie rzeczywistym.

Zalety:

  • interfejs webowy,
  • śledzenie błędów i retry,
  • obsługa batchy,
  • priorytety i limity przetwarzania.
composer require laravel/horizon
php artisan horizon:install

Można łatwo połączyć go z Redisem i ustawić osobne kolejki do importów:

ImportCsvRow::dispatch($data)->onQueue('imports');

W kolejnym rozdziale pokażemy, jak optymalizować przetwarzanie pod kątem wydajności: limity pamięci, timeouty, priorytety zadań i skalowanie.

6. Wydajność i optymalizacja przetwarzania dużych plików

Przy przetwarzaniu plików ważących setki megabajtów, nawet najlepszy kod nie pomoże, jeśli środowisko nie jest odpowiednio skonfigurowane. W tej sekcji omówimy praktyczne aspekty optymalizacji importu danych w Laravelu – od ustawień PHP, przez konfigurację kolejek, aż po skalowanie i monitoring.


🛠️ 6.1. Ustawienia PHP i środowiska

memory_limit

Zwiększ limit pamięci w php.ini lub .env (jeśli używasz Laravel Sail lub Docker):

memory_limit=512M

max_execution_time

Dla importów uruchamianych z CLI możesz ustawić:

max_execution_time=0

co oznacza brak limitu czasu wykonania.

Obsługa błędów

Zawsze zabezpieczaj import try/catch i loguj błędy, np. do osobnej tabeli import_errors lub pliku w storage/logs/import.log.


⚙️ 6.2. Konfiguracja kolejek

  • Stosuj osobne kolejki dla importu danych (np. imports, high, low),
  • Ustaw limity czasu (timeout) i prób (tries) w klasach Job:
public $timeout = 120;
public $tries = 3;
  • Warto również ograniczyć liczbę zadań przetwarzanych jednocześnie:
php artisan queue:work --queue=imports --max-jobs=100

🧪 6.3. Batch processing i Horizon

Laravel Horizon pozwala tworzyć batch’e zadań i monitorować ich stan. To świetne rozwiązanie, jeśli chcesz:

  • wiedzieć, czy cały import się zakończył,
  • wysłać powiadomienie po sukcesie lub błędzie,
  • zatrzymać przetwarzanie przy przekroczeniu limitu błędów.
Bus::batch([
    new ImportCsvRow($chunk1),
    new ImportCsvRow($chunk2),
])->then(function () {
    // sukces
})->catch(function () {
    // błąd
})->dispatch();

📈 6.4. Skalowanie i monitoring

Redis i więcej workerów:

  • Używaj Redis jako backend dla kolejek,
  • Ustal ilość workerów zależnie od CPU i pamięci RAM:
php artisan horizon

Monitorowanie:

  • Zintegrowany Horizon UI,
  • Możliwość podłączenia do zewnętrznych systemów (Sentry, Slack, e-mail),
  • Śledzenie czasu przetwarzania, liczby błędów, opóźnień.

W kolejnym punkcie skupimy się na doświadczeniu użytkownika – jak poinformować go, że import trwa i zakończył się sukcesem.

7. UX i SEO – jak informować użytkownika o postępie importu danych

Wydajne przetwarzanie plików po stronie serwera to jedno – równie istotne jest to, co widzi i odczuwa użytkownik końcowy. W tej części skupimy się na dobrych praktykach UX w kontekście importu danych oraz aspektach SEO związanych z importem przez frontend.


👀 7.1. Powiadomienia o statusie importu

Użytkownik powinien wiedzieć:

  • czy import się rozpoczął,
  • czy trwa,
  • czy zakończył się sukcesem lub błędem,
  • ile rekordów zostało przetworzonych.

Rozwiązania:

  • Alerty frontendowe: z użyciem Inertia.js / Vue / React,
  • Powiadomienia e-mail/SMS po zakończeniu importu,
  • Webhooki lub eventy WebSocket (Laravel Echo + Pusher lub Soketi),
  • Polling (np. AJAX co 5s pytający o status GET /import-status).

📝 7.2. Raporty z przetwarzania danych

Po zakończeniu importu warto wygenerować raport:

  • liczba poprawnie przetworzonych rekordów,
  • liczba błędnych rekordów,
  • lista błędów z liniami i przyczyną (np. z walidacji),
  • czas przetwarzania.

Można to zapisać w bazie lub jako plik CSV do pobrania.


💡 7.3. Obsługa importu w UI

W widoku aplikacji można:

  • pokazać pasek postępu (np. oparty o % batchu wykonanego),
  • pokazać spinner + komunikat „Import trwa…”
  • zablokować przycisk ponownego wysłania,
  • umożliwić podgląd statusu i historii importów użytkownika.

🔎 7.4. Czy import ma znaczenie dla SEO?

Zazwyczaj nie – import danych to część backendu. Ale są wyjątki:

  • Jeśli dane są używane do generowania widoków (np. katalog produktów), to szybkość ich zaimportowania wpływa na:
    • dostępność nowych podstron,
    • obecność danych w sitemapie,
    • ranking Google w przypadku błędnych/niepełnych danych.

Wniosek: nawet backendowy import może mieć wpływ na SEO, jeśli od jego poprawności zależy to, co widzi robot Google.


W ostatnim rozdziale podsumujemy poznane techniki i wskażemy najlepsze podejścia w zależności od scenariusza.

8. Podsumowanie – które podejście kiedy wybrać?

W tym artykule przedstawiliśmy szereg technik oraz narzędzi, które pozwalają na bezpieczne i wydajne przetwarzanie dużych plików w aplikacjach Laravel. Zakończmy podsumowaniem, które ułatwi Ci decyzję, kiedy i czego używać.


✅ Jeśli masz mały plik CSV (<5MB):

  • Możesz użyć Laravel Excel lub nawet file()/Storage::get() – ale z umiarem.
  • Dobrym wyborem będzie też SimpleExcelReader z bezpośrednim importem do bazy.

✅ Jeśli przetwarzasz średni plik CSV (10–100 MB):

  • Użyj Storage::readStream() + fgets() lub SimpleExcelReader
  • Dane przetwarzaj od razu albo kieruj do kolejki (dla większego bezpieczeństwa).
  • Dodaj try/catch i raportowanie błędów.

✅ Jeśli masz bardzo duży plik CSV/XML (>100 MB, >100k rekordów):

  • Stosuj streaming + chunking + kolejki,
  • Dziel dane przy pomocy LazyCollection lub XMLReader,
  • Zrób batchowanie i przetwarzanie asynchroniczne,
  • Monitoruj postęp za pomocą Laravel Horizon,
  • Zadbaj o powiadomienia i UX użytkownika (mail, status, raport).

✅ Jeśli użytkownik wrzuca plik przez UI:

  • Waliduj plik i jego strukturę,
  • Ogranicz rozmiar uploadu,
  • Obsługuj import przez kolejki,
  • Poinformuj użytkownika o postępie i zakończeniu importu,
  • Przechowuj historię i błędy importów.

📌 Rekomendacje końcowe

  • Zawsze testuj importy na danych testowych (np. 1k / 10k / 100k rekordów).
  • Unikaj przetwarzania wszystkiego w jednej pętli – nawet jeśli działa lokalnie.
  • Loguj błędy, informuj użytkownika i przewiduj wyjątki.
  • Korzystaj z kolejek, Horizon i batchy – Laravel daje wszystko, czego potrzebujesz.

Dobrze zaprojektowany mechanizm importu danych nie tylko chroni Twój serwer przed przeciążeniem, ale też daje użytkownikowi komfort i pewność, że jego dane są bezpieczne.


Dziękuję za lekturę! Jeśli chcesz rozszerzyć ten temat o eksport danych, synchronizację z zewnętrznymi API lub porównanie bibliotek, daj znać – to świetna baza do kolejnych artykułów!

Udostępnij ten post

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *