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ą:
- CPU throttling — cichy morderca. Gdy proces zużyje za dużo CPU, CloudLinux nie go zabija — „uspokaja", podając mniejsze dawki procesora. Skrypt, który na mocnym serwerze przetwarza 10 000 zdjęć w 20 minut, na throttlowanym sharedzie chodzi godzinami i blokuje sloty.
- Timeout 30 s — twardy limit requestu. Jeśli wtyczka próbuje przetworzyć całość w jednym requestcie, po 30–60 s serwer zrywa połączenie: 504 dla odwiedzającego i półprzetworzona biblioteka — część plików skonwertowana, część nie, a metadane wskazują na pliki, które nie istnieją.
- Wyczerpanie RAM. Dekodowanie obrazka 4000×3000 w pamięci to setki MB. WooCommerce oficjalnie wymaga min. 256 MB PHP memory (zalecane 512 MB / 1 GB+). Pętla bez resetu pamięci dociąga do limitu w kilka minut.
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:
- Skrypt nie przetwarza — wrzuca listę do kolejki (stan zapisany w bazie) i od razu się zamyka. Request odwiedzającego trwa ułamek sekundy.
- Worker w tle (cron) odbiera partię, np. 3 obrazy, przetwarza, zapisuje postęp, rezerwuje następną partię.
- Każda partia ma własny budżet pamięci i czasu — kończy się, zanim dojdzie do limitów hostingu.
- 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:
- BATCH_SIZE = 3 obrazy na tick — celowo mała partia, mieszcząca się w limitach nawet najsłabszego shareda;
- Stan w bazie (
pmm_bulk_queue): status, lista, licznikprocessedi błędów. Błędy też są liczone, więc pętla nigdy nie „zawiesi się" na jednym złym pliku; - Limitowanie per partia: każda partia startuje z limitem pamięci 512 MB i czasu 600 s — i resetuje się po zakończeniu. RAM nie rośnie monolitycznie przez całą noc;
- Idempotencja: plik już w WebP jest pomijany bez pracy — restart nie konwertuje niczego dwa razy;
- Natychmiastowe wznawianie przerwanych zadań: postęp zapisywany jest w bazie po każdej partii, więc timeout, restart hostingu czy błąd nie resetują pracy — kolejka wznawia się od następnego nieprzetworzonego obrazka;
- Live status: frontend polluje stan kolejki i pokazuje postęp na żywo. Koniec z odświeżaniem strony i pytaniem „czy to jeszcze działa?".
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.
- A — „traditional": cała biblioteka w jednej synchronicznej pętli (jeden proces);
- B — kolejka tła: partie po 3 obrazy, self-chaining, reset pamięci per partia — dokładnie jak
QueueManagerw PackshotPhoto AI.
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:
- Najdłuższy pojedynczy request decyduje o 504. W scenariuszu A jeden „request" trwa {{TRAD_WALL}} s — przy
max_execution_time30–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. - 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ć.
- 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)