Rozwiązywanie problemów z pamięcią i wydajnością
Te wpisy obejmują dwie rodziny awarii, na które natrafiasz pod obciążeniem: PHP wyczerpujące pamięć podczas renderowania oraz przepustowość, która spada z klifu, gdy proces jest rozgrzany lub nasycony. Każdy wpis nazywa objaw, najbardziej prawdopodobną przyczynę oraz poprawkę korzystającą z rzeczywistej powierzchni NextPDF lub standardowych mechanizmów sterowania PHP-FPM. Dla bazowego modelu strumieniowania i samouczka o procesach roboczych przeczytaj Strumieniowanie i pamięć; ta strona jest jej towarzyszem po stronie incydentu.
Najpierw mierz. Próbkuj memory_get_peak_usage(true) przed i po renderowaniu oraz
wywołuj memory_reset_peak_usage() między iteracjami, tak jak benchmark silnika
izoluje koszt per renderowanie. Dostrajanie bez wartości bazowej przesuwa klif, zamiast
go usuwać.
Wpis: „Allowed memory size exhausted” podczas generowania
Dział zatytułowany „Wpis: „Allowed memory size exhausted” podczas generowania”- Objaw. Renderowanie przerywa się błędem krytycznym
Allowed memory size of <n> bytes exhaustedze środowiska uruchomieniowego PHP, często na dużym lub bogatym w obrazy dokumencie. - Prawdopodobna przyczyna. Domyślna ścieżka zapisu komponuje cały dokument, a
potem go serializuje, więc szczytowe zużycie pamięci śledzi całkowity rozmiar
wyjścia. Duży dokument, duże osadzone obrazy lub duży osadzony krój czcionki mogą
przepchnąć żądanie poza
memory_limit. - Rozwiązanie.
- Ogranicz pamięć podręczną obrazów.
NextPDF\Core\ConfigudostępniaimageCacheBytes(domyślnie52428800, czyli 50 MB). Obniż ją witherem instancji$config->withImageCacheBytes($bytes)(sygnaturawithImageCacheBytes(int $bytes): self), aby kompilacja osadzająca wiele obrazów zawiodła szybko na znanym pułapie, zamiast korzystać ze swapu. To ogranicza pamięć podręczną obrazów w pamięci; nie próbkuje ponownie ani nie kompresuje samych obrazów. - Zmniejsz wejścia przed osadzeniem. Core nie skaluje w dół ani nie kompresuje obrazów ponownie. Zmień rozmiar i przekoduj zbyt dużą grafikę rastrową zanim ją osadzisz oraz osadzaj czcionki, których faktycznie używasz, aby tworzenie podzbioru miało mały zestaw glifów do zachowania (zobacz Zmniejszanie rozmiaru pliku PDF).
- Pozostaw kompresję włączoną. Świeży
Configmacompressustawione natrue. Pozostaw to włączone dla normalnych kompilacji;withCompress(false)nie jest optymalizacją rozmiaru (zwykle zwiększa wyjście). Sięgaj po to, aby debugować lub profilować potok — przesuwa kompromis CPU/pamięć (pomijając krok kompresji), a nie redukuje pamięć. - Podnieś
memory_limitświadomie, per proces roboczy. To standardowe ustawienie PHP, a nie klucz NextPDF. Ustaw je w konfiguracji puli albo za pomocąini_set('memory_limit', '256M')dla procesu CLI/kolejki i wymiaruj je względem sprofilowanego szczytu, a nie zgadywania.
- Ogranicz pamięć podręczną obrazów.
- Powiązane. Strumieniowanie i pamięć.
Wpis: pamięć rośnie wraz z liczbą stron w bardzo dużych dokumentach
Dział zatytułowany „Wpis: pamięć rośnie wraz z liczbą stron w bardzo dużych dokumentach”- Objaw. Dokument liczący wiele tysięcy stron wyczerpuje pamięć, mimo że każda strona jest mała, a szczyt rośnie z grubsza w takt liczby stron.
- Prawdopodobna przyczyna. Buforowany zapisujący przechowuje cały zserializowany dokument na stercie. Dla bardzo dużych dokumentów to dominujący koszt.
- Rozwiązanie.
- Preferuj strumieniową ścieżkę zapisu. Użyj udokumentowanej strumieniowej ścieżki
zapisu opisanej w
Strumieniowanie i pamięć:
serializuje każdą stronę w miarę jej komponowania i zwalnia bufor, co redukuje
wzrost bufora strony/wyjścia; małe metadane per obiekt (przesunięcia, drzewo
stron) wciąż mogą skalować się z liczbą stron/obiektów. Trzymaj się
udokumentowanego punktu wejścia, zamiast kopiować klasy wewnętrzne — bazowy silnik
strumieniowania jest na poziomie
experimental, a jego symbole nie są stabilną powierzchnią publiczną. - Dla natywnego parsera
writeHtml()pamiętaj, że pamięć po stronie wejścia jest ograniczona zarówno przez zabezpieczenie głębokości zagnieżdżenia, jak i liczby elementów: ADR-001 ogranicza zagnieżdżenie doMAX_NESTING_DEPTH = 100i odrzuca dokumenty powyżejMAX_ELEMENT_COUNT = 50000. Dokument, który osiąga limit elementów, otrzymuje o tym jawną informację, zamiast po cichu wyczerpywać pamięć. Te limity ADR-001 rządzą wyłącznie natywnym parserem; opcjonalny mostek Chrome (writeHtmlChrome()) renderuje poza procesem i ma własne, odrębne limity pamięci/wejścia, a nie te limity.
- Preferuj strumieniową ścieżkę zapisu. Użyj udokumentowanej strumieniowej ścieżki
zapisu opisanej w
Strumieniowanie i pamięć:
serializuje każdą stronę w miarę jej komponowania i zwalnia bufor, co redukuje
wzrost bufora strony/wyjścia; małe metadane per obiekt (przesunięcia, drzewo
stron) wciąż mogą skalować się z liczbą stron/obiektów. Trzymaj się
udokumentowanego punktu wejścia, zamiast kopiować klasy wewnętrzne — bazowy silnik
strumieniowania jest na poziomie
- Powiązane. Strumieniowanie i pamięć.
Wpis: długowieczny proces roboczy wyczerpuje pamięć po wielu zadaniach
Dział zatytułowany „Wpis: długowieczny proces roboczy wyczerpuje pamięć po wielu zadaniach”- Objaw. Pojedyncze renderowania się udają, ale proces roboczy kolejki, który renderuje wiele plików PDF jeden po drugim, wyczerpuje pamięć po minutach lub godzinach.
- Prawdopodobna przyczyna. Długowieczny proces PHP akumuluje alokacje między zadaniami. Powolny wzrost, niewidoczny w jednym żądaniu, kumuluje się na przestrzeni tysięcy.
- Rozwiązanie.
- Współdziel rejestry, twórz dokumenty na nowo. Zbuduj
FontRegistryorazImageRegistryraz przy starcie i przekaż je doDocumentFactory; twórz świeżyDocumentper zadanie za pomocą$factory->create($config). Parsowanie czcionek i obrazów zachodzi wtedy raz dla procesu, a nie raz na zadanie, a drzewo dokumentu per zadanie jest zbierane, gdy wychodzi poza zakres. Trzymaj sięexamples/14-worker-factory.php. - Ogranicz współdzieloną pamięć podręczną obrazów za pomocą
new ImageRegistry(maxCacheBytes: ...), aby nie mogła rosnąć bez ograniczeń między zadaniami. - Recykluj proces roboczy — sterowanie procesami, a nie gwarancja silnika. W
PHP-FPM ustaw
pm.max_requests, aby każde dziecko respawnowało się po stałej liczbie żądań. W kolejkach Laravel użyjqueue:work --max-jobs/--max-time/--memory; w Symfony Messenger użyjmessenger:consume --limit/--time-limit/--memory-limit.
- Współdziel rejestry, twórz dokumenty na nowo. Zbuduj
- Powiązane. Strumieniowanie i pamięć.
Wpis: klif przepustowości na zimnym lub niedostatecznie rozgrzanym procesie
Dział zatytułowany „Wpis: klif przepustowości na zimnym lub niedostatecznie rozgrzanym procesie”- Objaw. Pierwsze renderowania w świeżym procesie są wolne albo każde żądanie płaci koszt parsowania, którego rozgrzane żądania płacić nie powinny.
- Prawdopodobna przyczyna. Kumulują się dwa koszty zimnego startu. PHP bez opcache
rekompiluje każdy plik przy każdym żądaniu, a nierozgrzany
FontRegistryparsuje każdy krój czcionki za pierwszym jego użyciem. - Rozwiązanie.
- Włącz opcache (i JIT tam, gdzie pomaga). Ustaw
opcache.enable=1oraz hojneopcache.memory_consumption; na produkcji ustawopcache.validate_timestamps=0, aby pamięć podręczna nie była ponownie sprawdzana per żądanie. To ustawienie wymaga procesu wdrażania, który restartuje lub przeładowuje PHP-FPM (albo w inny sposób resetuje opcache, np.opcache_reset()/cachetool) przy każdym wydaniu — w przeciwnym razie opcache nadal serwuje stary bajtkod, a po wdrożeniu działa nieaktualny kod. To standardowe ustawienia ini PHP, a nie klucze NextPDF. - Rozgrzej i zablokuj rejestr czcionek przy starcie. Na instancji
FontRegistry$fontRegistry->warmup($fontFiles)parsuje kroje raz podczas startu, a$fontRegistry->lock()zamraża rejestr, aby kod w czasie żądania nie mógł mutować współdzielonego stanu;$fontRegistry->isLocked()raportuje stan. W genuinie długowiecznym procesie roboczym lub serwerze aplikacji — konsumencie kolejki albo procesie roboczym RoadRunner/Swoole/Octane, który utrzymuje ten sam proces PHP przy życiu między wieloma żądaniami — rozgrzany, zablokowany rejestr utrwala swoje sparsowane kroje w stanie obiektu, zamieniając parsowanie czcionek per żądanie w jednorazowy koszt startu procesu. W standardowym modelu żądań PHP-FPM ten rozgrzany stan obiektu nie przeżywa między żądaniami: opcache buforuje skompilowane klasy i bajtkod, a nie rozgrzany stan obiektu userland, więc rozgrzanyFontRegistryjest odbudowywany per żądanie (uruchamiany ponownie przy każdym żądaniu z bootstrapu dziecka), a nie trzymany rozgrzanym między żądaniami w obrębie dziecka. Na zwykłym PHP-FPM opcache głównie amortyzuje koszt rekompilacji bajtkodu; przyjmij, że parsowanie czcionek jest płacone per żądanie, a nie eliminowane. Amortyzacja między żądaniami — parsowanie każdego kroju raz na czas życia procesu — dotyczy wyłącznie genuinie długowiecznego procesu, takiego jak proces roboczy RoadRunner/Swoole/Octane albo konsument kolejki, który utrzymuje ten sam proces PHP przy życiu między wieloma żądaniami. - Nie parsuj ponownie tego samego szablonu per żądanie. Rozwiąż czcionki i
wielokrotnie używane zasoby raz przy starcie przez współdzielone rejestry; tylko
Documentper zadanie powinien być tworzony w żądaniu.
- Włącz opcache (i JIT tam, gdzie pomaga). Ustaw
- Powiązane. Strumieniowanie i pamięć.
Wpis: serwer nasyca się, a opóźnienia skaczą pod współbieżnością
Dział zatytułowany „Wpis: serwer nasyca się, a opóźnienia skaczą pod współbieżnością”- Objaw. Opóźnienie per renderowanie jest w izolacji w porządku, ale pod obciążeniem maszyna swapuje, CPU się nasyca albo żądania ustawiają się w kolejce i wygasają.
- Prawdopodobna przyczyna. Zbyt wiele procesów roboczych PHP-FPM jak na dostępną pamięć RAM, więc suma szczytów procesów roboczych przekracza pamięć fizyczną, a host swapuje; albo zbyt mało procesów roboczych, więc żądania serializują się za małą pulą.
- Rozwiązanie.
-
Wymiaruj
pm.max_childrenze sprofilowanego szczytu. Użyj standardowego wzoru:pm.max_children = (total RAM - OS/other overhead) / per-worker peak memoryZmierz rzeczywisty szczyt procesu roboczego reprezentatywnym dokumentem (zobacz notkę o profilowaniu w sekcji Zakres), zarezerwuj zapas na system operacyjny i wszelkie skolokowane usługi i podziel. Zostaw margines; nie wymiaruj do 100% RAM.
-
Przypnij koszt kompresji w swoim budżecie. Kompresja Flate może być znaczącym kosztem CPU zapisu strumienia i skaluje się z wolumenem kompresowalnych bajtów strumienia, więc liczba stron i wolumen osadzonych czcionek wpływają na CPU per renderowanie; przetwarzanie obrazów, tworzenie podzbiorów czcionek oraz parsowanie wejścia również mogą dominować. Mierz reprezentatywnymi dokumentami i uwzględnij rzeczywisty czynnik dominujący, gdy wybierasz liczbę procesów roboczych i CPU.
-
Ustaw
pm.max_requestsobokpm.max_children, aby dzieci się recyklowały i odzyskiwały wszelki powolny wzrost, jak we wpisie o procesie roboczym powyżej.
-
- Powiązane. Strumieniowanie i pamięć.
Wpis: duże niezaufane wejście jest wolne lub kosztowne w parsowaniu
Dział zatytułowany „Wpis: duże niezaufane wejście jest wolne lub kosztowne w parsowaniu”- Objaw. Renderowanie jest wolne lub pamięciochłonne na dużym albo głęboko zagnieżdżonym wejściu, zwłaszcza HTML lub czcionce, której nie wytworzyłeś.
- Prawdopodobna przyczyna. Koszt parsowania skaluje się z rozmiarem i strukturą wejścia. Patologiczne wejście (głębokie zagnieżdżenie, ogromna liczba elementów lub zniekształcona czcionka) może zdominować budżet.
- Rozwiązanie.
- Oprzyj się na granicach silnika. Natywny parser HTML
writeHtml()wymuszaMAX_NESTING_DEPTH = 100orazMAX_ELEMENT_COUNT = 50000(ADR-001); wejścia ponad tymi limitami są odrzucane, zamiast pozwolić im wyczerpać proces. (Opcjonalny mostek Chrome,writeHtmlChrome(), jest poza zakresem tych limitów ADR-001 i wymusza własne, odrębne limity pamięci/wejścia.) - Traktuj czcionki dostarczone przez wywołującego jako niezaufane. Zniekształcona
czcionka zgłasza
NextPDF\Exception\FontParsingException, zamiast uszkodzić wyjście, więc przechwyć konkretny wyjątek i odrzuć wejście, zamiast ponawiać. - Waliduj i wymiaruj wejścia na swojej granicy oraz stosuj limity na poziomie żądania dla rozmiaru dokumentu w przypadku treści wpływanej przez wywołującego.
- Oprzyj się na granicach silnika. Natywny parser HTML
- Powiązane. Rozwiązywanie problemów: czcionki i tagowanie.
Tablica decyzyjna: objaw na dźwignię
Dział zatytułowany „Tablica decyzyjna: objaw na dźwignię”| Objaw | Najbardziej prawdopodobna dźwignia |
|---|---|
Allowed memory size … exhausted na pojedynczym renderowaniu | Obniż $config->withImageCacheBytes(); zmniejsz obrazy przed osadzeniem; podnieś per proces roboczy memory_limit |
| Szczyt pamięci rośnie wraz z liczbą stron | Użyj udokumentowanej strumieniowej ścieżki zapisu |
| Pamięć procesu roboczego rośnie na przestrzeni wielu zadań | Współdziel FontRegistry/ImageRegistry przez DocumentFactory; ustaw pm.max_requests / --max-jobs |
| Pierwsze żądania wolne, koszt parsowania per żądanie | Włącz opcache; $fontRegistry->warmup(), a potem ->lock() przy starcie |
| Host swapuje / skoki opóźnień pod obciążeniem | Wymiaruj pm.max_children = (RAM − narzut) / szczyt per proces roboczy |
| Wolne lub ciężkie na dużym/niezaufanym wejściu | Oprzyj się na limitach ADR-001; odrzucaj zniekształcone czcionki na FontParsingException |
Przypadki brzegowe i pułapki
Dział zatytułowany „Przypadki brzegowe i pułapki”imageCacheBytesto pułap pamięci, a nie pokrętło rozmiaru. Obniżenie go ogranicza pamięć podręczną, aby kompilacja zawiodła szybko; nigdy nie próbkuje ponownie ani nie przekodowuje osadzanych przez Ciebie obrazów. Core nie ma kontroli jakości obrazu.withCompress(false)sprawia, że pliki są większe, i jest pomocą debugowania/profilowania. Nie jest optymalizacją rozmiaru; przesuwa kompromis CPU/pamięć (pomija krok kompresji), a nie redukuje pamięć.- Dokładny profil pamięci silnika strumieniowania jest właściwością na poziomie
experimentali może się zmieniać między wydaniami pomocniczymi. Traktuj każdy pojedynczy pomiar jako obserwację, a nie przenośną stałą. memory_limit,opcache.*,pm.max_childrenorazpm.max_requeststo standardowe ustawienia PHP / PHP-FPM. NextPDF nie udostępnia własnych kluczy dla nich; konfiguruj je w swoim środowisku uruchomieniowym, a nie wConfig.
Zobacz także
Dział zatytułowany „Zobacz także”- Strumieniowanie i pamięć — model strumieniowania, granice ADR-001 oraz pełny samouczek o procesie roboczym wsadowym.
- Zmniejszanie rozmiaru pliku PDF — kompresja i tworzenie podzbiorów czcionek, dwie rzeczywiste kontrole rozmiaru.
- Rozwiązywanie problemów: czcionki i tagowanie — awarie rozwiązywania, parsowania i tworzenia podzbiorów czcionek.
- Indeks bazy wiedzy
Słownik: strumieniowy zapisujący · tworzenie podzbioru czcionki