Infrastruktura & Skalowalność Serwerowa

Jak zoptymalizować 10 000 zdjęć w WooCommerce bez zarzynania procesora serwera i błędów 504 Gateway Timeout?

Masz 10 000 produktów w sklepie. Dostawca przysłał nowe zdjęcia, albo wreszcie chcesz przekonwertować całą bibliotekę na WebP — żeby Core Web Vitals przestały gryźć w Google. Klikasz „Optymalizuj wszystko". I wtedy sklep zwisa: panel administracyjny nie ładuje się, a odwiedzający dostają 504 Gateway Timeout. Host w biletce: „Wykryto nadmierne zużycie zasobów przez proces PHP".

To nie teoria — to scenariusz, który co tydzień opisują w supportach hostingów i na forach WooCommerce. I którego da się całkowicie uniknąć. Ale tylko jeśli zrozumiesz, co dzieje się pod maską, gdy skrypt próbuje przełknąć 10 000 plików naraz.

1. Koszmar menedżera e-commerce: 10 000 produktów i zawieszony serwer

„10 000 zdjęć" to nie przypadek egzotyczny. Oficjalne FAQ WooCommerce mówi o skalowaniu do 100 000+ produktów, a agencje prowadzą sklepy z 20 000–50 000 pozycji. Sklep odzieżowy z 10 000 SKU × 6–8 zdjęć spokojnie przekracza 50 000 plików.

A tu haczyk: jedno „zdjęcie" w WordPressie to nie jeden plik. Każde wgranie generuje 6–10 rozmiarów: oryginał, woocommerce_thumbnail (300 px), woocommerce_single (600 px), miniaturkę galerii (100 px) i subsizesy motywu. Zatem 10 000 „zdjęć" w mediach to realnie 60 000–100 000 plików na dysku. Tyle musi przełknąć Twój hosting.

Dlaczego to robimy? Bo wolny sklep to strata przychodu: Amazon (badanie Grega Lindena) — każde +100 ms = −1% sprzedaży; Walmart Labs — utrata 1 s = −2% konwersji; Google — 53% użytkowników mobile odchodzi, gdy strona ładuje się dłużej niż 3 s. Nieskompresowane grafiki to najczęstsza przyczyna wolnych stron produktowych, a 78% zdjęć produktów w WooCommerce ma puste alt (dane ImageSEO). Optymalizacja masowa to nie „fajny dodatek" — to wpływ na konwersję i SEO.

2. Dlaczego serwery współdzielone i VPS-y padają: CPU throttling, timeout 30 s, wyczerpanie RAM

Twój hosting ma twarde limity, które zadziałają niezależnie od tego, co napiszesz w wp-config.php (dane z polityk Bluehost, Kualo, Stablehost/CloudLinux):

Limit Typ (shared) Po przekroczeniu
max_execution_time 30–60 s Fatal error: Maximum execution time exceeded → 500/504
memory_limit 128–512 MB Allowed memory size exhausted → 500
CPU (CloudLinux/CAGE) 1 rdzeń / 100% proces przypierany do snu (throttling)
Entry / PHP processes 15–100 nowi odwiedzający dostają 508 „Resource Limit Reached"
I/O (dysk) 1–50 MB/s throttling przy masowym zapisie

Trzy mechanizmy zabijają optymalizację masową:

A set_time_limit(0) to nie rozwiązanie — oficjalna dokumentacja WooCommerce (Product CSV Import Suite) ostrzega: „set_time_limit is not supported by all hosting environments". Zwiększanie limitów to leczenie objawu: zajęty slot entry process i tak blokuje zwykły ruch. Tak działał opisany w supportach przypadek Google for WooCommerce: batchowe zadanie cURL zrzucało cały panel admina na 504, mimo że „działało poprawnie" — zjadało wszystkie zasoby serwera.

3. Synchroniczna pętla vs asynchroniczna kolejka w tle

Wszystkie awarie z poprzedniej sekcji mają jedną przyczynę: architekturę. Typowa wtyczka „bulk optimize" przejmuje całą pracę w jednym requestcie PHP — pętla foreach po całej bibliotece, bez podziału, bez wyjścia awaryjnego. Jeden zły plik (0 KB, brak obsługi HEIC) potrafi przerwać partię i zostawić półprzetworzoną bibliotekę.

Asynchroniczna kolejka to inny model. Praca jest rozbita na setki małych partii:

  1. Skrypt nie przetwarza — wrzuca listę do kolejki (stan zapisany w bazie) i od razu się zamyka. Request odwiedzającego trwa ułamek sekundy.
  2. Worker w tle (cron) odbiera partię, np. 3 obrazy, przetwarza, zapisuje postęp, rezerwuje następną partię.
  3. Każda partia ma własny budżet pamięci i czasu — kończy się, zanim dojdzie do limitów hostingu.
  4. Stan w bazie (co zrobione, co błędne) oznacza, że przerwa — timeout, restart, błąd — nie niszczy pracy: kolejka wznawia się od dokładnie tego miejsca, gdzie stanęła.
Synchroniczna pętla Asynchroniczna kolejka
Czas requestu minuty–godziny ułamek sekundy
Odwiedzający 504 / 508 / zawieszenie nic — sklep działa normalnie
RAM rośnie monolitycznie reset po każdej partii
Błąd na 1 pliku zabija partię odnotowany, kolejka idzie dalej
Przerwa strata pracy wznowienie od ostatniego punktu

W WordPressie kolejki realizuje się na kilka sposobów — od WP-Cron (self-chaining), przez Action Scheduler (standard WooCommerce: partie po 25, stop przy 90% pamięci lub ~30 s, przetestowany na kolejkach 50 000+ jobów), po REST API batching (WP 5.6+). Kluczem nie jest biblioteka, lecz zasada: małe partie + stan w DB + tło.

4. Rozwiązanie w PackshotPhoto AI: bezpieczne przetwarzanie wsadowe (Batch Processing)

Jak to wygląda w praktyce? Rozpakujmy architekturę wtyczki PackshotPhoto AI (AI packshoty produktowe + masowa konwersja WebP i resize w WooCommerce).

Hybrydowy routing. Frontend sprawdza liczbę wybranych obrazów: ≤ 10 idzie na foreground (chunki po 5, pasek postępu — szybka interakcja). Powyżej 10 — tu zawsze trafia 10 000 zdjęć — praca idzie do kolejki tła. Przycisk „Optymalizuj 10 000" nie blokuje niczego: formularz jest submitowany, kolejka dostaje zadanie, panel wraca do pracy.

Kolejka tła (QueueManager) — serce rozwiązania. Mechanizm to self-chaining single event: po każdej partii worker natychmiast rezerwuje kolejną. Kluczowe parametry:

Ochrona biblioteki mediów. Przy 10 000 wierszy liczą się mikrooptymalizacje: wtyczka zapisuje flagę _pmm_has_file_backup w bazie, co eliminuje 8 operacji filesystem per wiersz przy każdym renderze tabeli — przy 10 000 wierszy to ok. 80 000 zbędnych stat/file_exists mniej. Oryginał trafia do bezpiecznego backupu z one-click restore.

Uczciwie: kolejka opiera się na WP-Cron (self-chaining), nie na Action Scheduler. Działa, ale WP-Cron startuje przy ruchu na stronie. Przy 10 000 zdjęć / 3 na tick to ~3 300 ticków, więc na produkcji warto dodać realny cron:

*/5 * * * * cd /var/www/sklep && php wp-cron.php

(z DISABLE_WP_CRON w wp-config.php). Na VPS-ach jeszcze lepiej: WP-CLI jako driver kolejki — oficjalnie zalecana droga do dużych kolejek. Dzięki temu przetwarzanie idzie równo niezależnie od ruchu.

5. Benchmark obciążenia serwera: tradycyjna pętla vs kolejka tła

Sprawdzamy na twardych liczbach. Test: 10 000 realnych obrazków (480×360, JPEG, ~830 MB), ta sama operacja w obu scenariuszach (dekod → WebP q85 → zapis). Jedyna zmienna: architektura.

Wyniki (maszyna testowa: 4 rdzenie, 16 GB RAM, Linux; dane: benchmark_results.json):

Metryka Traditional (1 pętla) Kolejka tła (batch=3)
Przetworzone / błędy 10 000 / 0 10 000 / 0
Najdłuższy pojedynczy „request" {{TRAD_WALL}} s → timeout 30–60 s = 504 gwarantowany {{PK_TICK}} ms na partię
Peak RAM procesu {{TRAD_RSS}} MB (rosnący monolitycznie) {{PK_RSS}} MB (reset per partia)
CPU procesu {{TRAD_CPU}}% {{PK_CPU}}%
Łączny czas {{TRAD_WALL}} s {{PK_WALL}} s
Wyjście {{TRAD_MB}} MB (WebP) {{PK_MB}} MB (WebP)

Co z tego wynika dla Twojego hostingu:

  1. Najdłuższy pojedynczy request decyduje o 504. W scenariuszu A jeden „request" trwa {{TRAD_WALL}} s — przy max_execution_time 30–60 s serwer zrywa go po minucie i praca idzie do kosza. W scenariuszu B najdłuższy fragment to {{PK_TICK}} ms — mieści się z zapasem nawet w najcięższych limitach.
  2. CPU: oba scenariusze zużywają porównywalne CPU ({{TRAD_CPU}}% vs {{PK_CPU}}%) — i to jest punkt, o którym mało kto mówi: kolejka nie oszczędza CPU, oszczędza sloty. Praca trwa tyle samo (fizyki nie oszukasz), ale w partiach nie blokuje entry processów, nie trzyma requestu odwiedzającego i nie buduje szczytu, który CloudLinux miałby throttle'ować.
  3. RAM: scena A trzyma dekodowane obrazy w pamięci przez całą pętlę; kolejka resetuje pamięć po każdej partii. Na sharedzie z limitem 512 MB scenariusz A dociąga do ściany przy większych obrazkach, B trzyma się w budżecie.

Wniosek: różnica między „sklep padł" a „sklep działał cały czas" nie wynika z mocy serwera, tylko z architektury. Małe partie + tło + stan w DB = przewidywalne zużycie zasobów mieszczące się w limitach najtańszego hostingu.

6. FAQ

1. Ile zdjęć mogę zoptymalizować naraz, zanim serwer padnie?

Nie ma magicznej liczby — liczy się architektura. Na typowym sharedzie (30–60 s, 128–512 MB) żadna synchroniczna pętla nie przeżyje 10 000 obrazów; przy 500–1000 bywa już problematycznie. W kolejce tła w partiach po 3–10 obrazów skala nie ma znaczenia: 10 000, 50 000 czy 100 000 plików to kwestia czasu, nie ryzyka awarii.

2. Czym różni się WP-Cron od Action Scheduler? Co wybrać do masowej optymalizacji?

WP-Cron to pseudocron działający jako request HTTP wyzwalany ruchem (loopback do wp-cron.php): bez odwiedzających kolejka stoi, a długie partie nakładają się z ruchem. Action Scheduler (dostarczany z WooCommerce) to prawdziwa kolejka w tabelach DB: partie po 25, stop przy 90% pamięci lub ~30 s, retry i logi, testowany na 50 000+ jobów. Dla dużych kolejek oba warto napędzać realnym cronem (php wp-cron.php co 5 min) lub WP-CLI.

3. Czy masowa konwersja na WebP zaszkodzi produktom? Da się cofnąć?

Nie, jeśli wtyczka robi to poprawnie: oryginał jest backupowany (w PackshotPhoto AI do pmm-trash/ z metadanymi), a konwersja jest idempotentna — plik już w WebP jest pomijany, więc restart nie dubluje pracy. One-click restore przywraca oryginał. WebP przy tej samej ostrości jest zwykle 25–35% lżejszy niż JPEG.

4. Mam CloudLinux — czy kolejka w tle ucieknie przed CPU throttlingiem?

Częściowo. Throttling działa na proces — każda partia to krótki, przewidywalny proces (3 obrazy, ~0,1 s), więc nie buduje szczytu do duszenia. Ale przy twardym limicie I/O (1–50 MB/s) masowy zapis i tak będzie ograniczany — rozłóż czas: partie co 5–15 min zamiast non-stop. Praca trwa dłużej, ale sklep nigdy nie cierpi.

5. Jak przyspieszyć przetwarzanie 10 000 zdjęć bez przepalania hostingu?

Kolejność: (1) realny cron systemowy zamiast WP-Cron-on-traffic — */5 * * * * php wp-cron.php + DISABLE_WP_CRON; (2) na VPS: WP-CLI jako driver kolejki (wp action-scheduler run --batch-size=100); (3) pamięć 512 MB dla procesu; (4) partie 3–10 obrazów, nie „500 na raz". Throughput rośnie, ryzyko 504/508 spada do zera.


Podsumowanie: 10 000 zdjęć w WooCommerce nie wymaga mocniejszego serwera — wymaga innej architektury. Małe partie, kolejka w tle, stan w bazie, idempotencja i limitowanie pamięci per partia zamieniają „zawieszony sklep + 504 Gateway Timeout" w proces działający w tle, który przetrwa każdy restart i zostawi lżejszą, szybszą bibliotekę mediów. W PackshotPhoto AI dokładnie tak to działa — a benchmark z tym artykułem (10 000 realnych obrazków) potwierdza: najdłuższy pojedynczy fragment pracy to milisekundy, nie minuty.

Pełne dane benchmarku: benchmark_results.json; skrypt: benchmark_10k.py (katalog marketing/ wtyczki).

Zabezpiecz i zoptymalizuj sklep WooCommerce już dziś

Zainstaluj darmową wtyczkę PackshotPhoto AI z katalogu WordPress.org i zautomatyzuj optymalizację mediów w 1 kliknięcie.

Pobierz z WordPress.org (Darmowe 10 kredytów)