Pemecahan masalah memori dan performa
Cakupan
Bagian berjudul “Cakupan”Entri-entri ini mencakup dua keluarga kegagalan yang Anda hadapi di bawah beban: PHP kehabisan memori selama sebuah render, dan throughput yang anjlok tajam begitu sebuah proses hangat atau jenuh. Setiap entri menamai sebuah gejala, penyebab yang paling mungkin, dan perbaikan yang menggunakan permukaan NextPDF nyata atau kendali PHP-FPM standar. Untuk model streaming yang mendasarinya dan sebuah tutorial worker, baca Streaming dan memori; halaman ini adalah pendamping sisi-insidennya.
Ukur dulu. Sampel memory_get_peak_usage(true) sebelum dan sesudah sebuah render
dan panggil memory_reset_peak_usage() di antara iterasi, sebagaimana benchmark
engine mengisolasi biaya per-render. Menyetel tanpa baseline memindahkan cliff
alih-alih menghapusnya.
Entri: “Allowed memory size exhausted” selama generasi
Bagian berjudul “Entri: “Allowed memory size exhausted” selama generasi”- Gejala. Sebuah render dibatalkan dengan fatal
Allowed memory size of <n> bytes exhausteddari runtime PHP, sering kali pada dokumen yang besar atau padat-gambar. - Kemungkinan penyebab. Jalur tulis default menyusun seluruh dokumen, lalu
menserialkannya, sehingga memori puncak mengikuti total ukuran keluaran. Dokumen
besar, gambar tersemat besar, atau sebuah face font tersemat besar dapat
mendorong permintaan melewati
memory_limit. - Resolusi.
- Batasi cache gambar.
NextPDF\Core\ConfigmengeksposimageCacheBytes(default52428800, yaitu 50 MB). Turunkan dengan wither instance$config->withImageCacheBytes($bytes)(signaturewithImageCacheBytes(int $bytes): self) sehingga sebuah build yang menyematkan banyak gambar gagal cepat pada plafon yang diketahui alih-alih swapping. Ini membatasi cache gambar in-memory; ia tidak me-resample atau mengkode ulang gambarnya sendiri. - Perkecil input sebelum penyematan. Core tidak men-downscale atau mengkode ulang gambar. Resize dan kode ulang seni raster berukuran berlebih sebelum Anda menyematkannya, dan sematkan font yang benar-benar Anda gunakan sehingga subsetting memiliki glyph set kecil untuk dipertahankan (lihat Mengurangi ukuran berkas PDF).
- Jaga kompresi tetap aktif. Sebuah
Configbaru memilikicompressdiseteltrue. Biarkan aktif untuk build normal;withCompress(false)bukan optimasi ukuran (biasanya ia memperbesar keluaran). Gunakan untuk men-debug atau memprofil pipeline — ia menggeser tradeoff CPU/memori (melewati langkah compress) alih-alih mengurangi memori. - Naikkan
memory_limitsecara sengaja, per worker. Ini adalah pengaturan PHP standar, bukan kunci NextPDF. Setel di config pool atau denganini_set('memory_limit', '256M')untuk proses CLI/queue, dan ukur terhadap puncak yang diprofil, bukan tebakan.
- Batasi cache gambar.
- Terkait. Streaming dan memori.
Entri: memori tumbuh seiring jumlah halaman pada dokumen yang sangat besar
Bagian berjudul “Entri: memori tumbuh seiring jumlah halaman pada dokumen yang sangat besar”- Gejala. Sebuah dokumen ribuan-halaman menghabiskan memori meskipun setiap halaman kecil, dan puncak naik kira-kira seiring dengan jumlah halaman.
- Kemungkinan penyebab. Buffered writer menahan seluruh dokumen yang diserialkan di heap. Untuk dokumen yang sangat besar itu adalah biaya yang dominan.
- Resolusi.
- Lebih utamakan jalur tulis streaming. Gunakan jalur tulis streaming
terdokumentasi yang dijelaskan dalam
Streaming dan memori: ia
menserialkan setiap halaman saat disusun dan melepaskan buffer, yang
mengurangi pertumbuhan page-buffer/keluaran; metadata per-object kecil
(offset, page tree) masih dapat menskala dengan jumlah halaman/object. Ikuti
entry point terdokumentasi alih-alih menyalin kelas internal — engine
streaming yang mendasarinya bertier
experimentaldan simbolnya bukan permukaan publik yang stabil. - Untuk parser
writeHtml()native, ingat bahwa memori sisi-input dibatasi oleh guard kedalaman-nesting maupun jumlah-elemen: ADR-001 membatasi nesting padaMAX_NESTING_DEPTH = 100dan menolak dokumen di atasMAX_ELEMENT_COUNT = 50000. Sebuah dokumen yang mencapai batas elemen diberitahu secara eksplisit alih-alih diam-diam menghabiskan memori. Batas ADR-001 ini mengatur parser native saja; Chrome bridge opsional (writeHtmlChrome()) merender di luar proses dan memiliki batas memori/input terpisahnya sendiri, bukan batas-batas ini.
- Lebih utamakan jalur tulis streaming. Gunakan jalur tulis streaming
terdokumentasi yang dijelaskan dalam
Streaming dan memori: ia
menserialkan setiap halaman saat disusun dan melepaskan buffer, yang
mengurangi pertumbuhan page-buffer/keluaran; metadata per-object kecil
(offset, page tree) masih dapat menskala dengan jumlah halaman/object. Ikuti
entry point terdokumentasi alih-alih menyalin kelas internal — engine
streaming yang mendasarinya bertier
- Terkait. Streaming dan memori.
Entri: sebuah worker berumur panjang menghabiskan memori setelah banyak job
Bagian berjudul “Entri: sebuah worker berumur panjang menghabiskan memori setelah banyak job”- Gejala. Render tunggal berhasil, tetapi sebuah queue worker yang merender banyak PDF berturut-turut menghabiskan memori setelah beberapa menit atau jam.
- Kemungkinan penyebab. Sebuah proses PHP berumur panjang mengakumulasi alokasi lintas job. Pertumbuhan lambat yang tidak terlihat dalam satu permintaan bertambah-tambah pada ribuan.
- Resolusi.
- Bagikan registry, buat ulang dokumen. Bangun
FontRegistrydanImageRegistrysekali saat boot dan teruskan ke sebuahDocumentFactory; buat sebuahDocumentbaru per job dengan$factory->create($config). Penguraian font dan gambar kemudian terjadi sekali untuk proses, bukan sekali per job, dan pohon dokumen per-job dikumpulkan ketika keluar dari scope. Ikutiexamples/14-worker-factory.php. - Batasi cache gambar bersama dengan
new ImageRegistry(maxCacheBytes: ...)sehingga ia tidak dapat tumbuh tanpa batas lintas job. - Daur ulang worker — kendali proses, bukan jaminan engine. Pada PHP-FPM,
setel
pm.max_requestssehingga setiap child respawn setelah sejumlah permintaan yang tetap. Pada Laravel queue gunakanqueue:work --max-jobs/--max-time/--memory; pada Symfony Messenger gunakanmessenger:consume --limit/--time-limit/--memory-limit.
- Bagikan registry, buat ulang dokumen. Bangun
- Terkait. Streaming dan memori.
Entri: throughput cliff pada proses yang dingin atau kurang-hangat
Bagian berjudul “Entri: throughput cliff pada proses yang dingin atau kurang-hangat”- Gejala. Render-render pertama pada proses yang baru lambat, atau setiap permintaan membayar biaya parse yang seharusnya tidak dibayar permintaan yang hangat.
- Kemungkinan penyebab. Dua biaya cold-start menumpuk. PHP tanpa opcache
mengompilasi ulang setiap berkas pada setiap permintaan, dan sebuah
FontRegistryyang tidak dipanaskan mengurai setiap face font saat pertama kali digunakan. - Resolusi.
- Aktifkan opcache (dan JIT di tempat yang membantu). Setel
opcache.enable=1danopcache.memory_consumptionyang murah hati; di produksi setelopcache.validate_timestamps=0sehingga cache tidak diperiksa ulang per permintaan. Pengaturan itu membutuhkan proses deploy yang me-restart atau me-reload PHP-FPM (atau jika tidak me-reset opcache, misalnyaopcache_reset()/cachetool) pada setiap rilis — jika tidak opcache terus menyajikan bytecode lama dan kode usang berjalan setelah deploy. Ini adalah pengaturan PHP standar, bukan kunci NextPDF. - Panaskan dan kunci font registry saat boot. Pada sebuah instance
FontRegistry,$fontRegistry->warmup($fontFiles)mengurai face sekali selama boot, dan$fontRegistry->lock()membekukan registry sehingga kode saat-permintaan tidak dapat memutasi state bersama;$fontRegistry->isLocked()melaporkan state-nya. Pada sebuah worker atau application server yang benar-benar berumur panjang — sebuah konsumen queue atau worker RoadRunner/Swoole/Octane yang menjaga proses PHP yang sama tetap hidup lintas banyak permintaan — sebuah registry yang dipanaskan dan dikunci mempertahankan face yang diurai-nya dalam state object, mengubah penguraian font per-permintaan menjadi biaya boot-proses sekali-jalan. Di bawah model permintaan PHP-FPM standar, state object yang dipanaskan itu tidak bertahan lintas permintaan: opcache men-cache kelas dan bytecode yang dikompilasi, bukan state object userland yang dipanaskan, sehingga sebuahFontRegistryyang dipanaskan dibangun ulang per permintaan (dijalankan ulang setiap permintaan dari bootstrap child), bukan ditahan hangat lintas permintaan di dalam sebuah child. Pada PHP-FPM polos, opcache terutama mengamortisasi biaya rekompilasi bytecode; terima bahwa penguraian font dibayar per permintaan, bukan dihilangkan. Amortisasi lintas-permintaan — mengurai setiap face sekali untuk masa hidup proses — hanya berlaku pada proses yang benar-benar berumur panjang seperti worker RoadRunner/Swoole/Octane atau konsumen queue yang menjaga proses PHP yang sama tetap hidup lintas banyak permintaan. - Jangan mengurai ulang template yang sama per permintaan. Resolve font dan
resource yang dapat dipakai ulang sekali saat boot melalui registry bersama;
hanya
Documentper-job yang harus dibuat dalam permintaan.
- Aktifkan opcache (dan JIT di tempat yang membantu). Setel
- Terkait. Streaming dan memori.
Entri: server jenuh dan latensi melonjak di bawah konkurensi
Bagian berjudul “Entri: server jenuh dan latensi melonjak di bawah konkurensi”- Gejala. Latensi per-render baik-baik saja secara terisolasi, tetapi di bawah beban, mesin swap, CPU jenuh, atau permintaan mengantre dan timeout.
- Kemungkinan penyebab. Terlalu banyak worker PHP-FPM untuk RAM yang tersedia, sehingga jumlah puncak worker melebihi memori fisik dan host swap; atau terlalu sedikit worker, sehingga permintaan berderet di belakang pool yang kecil.
- Resolusi.
-
Ukur
pm.max_childrendari puncak yang diprofil. Gunakan rumus standar:pm.max_children = (total RAM - OS/other overhead) / per-worker peak memoryUkur puncak nyata sebuah worker dengan dokumen yang representatif (lihat catatan profiling di Cakupan), sisihkan headroom untuk OS dan layanan terkolokasi mana pun, lalu bagi. Sisakan margin; jangan mengukur ke 100% RAM.
-
Tetapkan biaya kompresi dalam budget Anda. Kompresi Flate dapat menjadi biaya CPU yang signifikan dari penulisan sebuah stream dan menskala dengan volume byte stream yang dapat dikompresi, sehingga jumlah halaman dan volume font tersemat memengaruhi CPU per-render; pemrosesan gambar, font subsetting, dan penguraian input juga dapat mendominasi. Ukur dengan dokumen representatif, dan perhitungkan driver yang sebenarnya saat Anda memilih jumlah worker dan CPU.
-
Setel
pm.max_requestsbersamapm.max_childrensehingga child mendaur ulang dan mengklaim kembali pertumbuhan lambat apa pun, seperti pada entri worker di atas.
-
- Terkait. Streaming dan memori.
Entri: input tidak tepercaya yang besar lambat atau mahal untuk diurai
Bagian berjudul “Entri: input tidak tepercaya yang besar lambat atau mahal untuk diurai”- Gejala. Sebuah render lambat atau berat-memori pada input yang besar atau ber-nesting dalam, terutama HTML atau sebuah font yang tidak Anda produksi.
- Kemungkinan penyebab. Biaya penguraian menskala dengan ukuran dan struktur input. Sebuah input patologis (nesting dalam, jumlah elemen yang besar, atau font yang cacat) dapat mendominasi budget.
- Resolusi.
- Andalkan batas engine. Parser HTML
writeHtml()native menegakkanMAX_NESTING_DEPTH = 100danMAX_ELEMENT_COUNT = 50000(ADR-001); input di atas batas tersebut ditolak alih-alih diizinkan menghabiskan proses. (Chrome bridge opsional,writeHtmlChrome(), berada di luar cakupan batas ADR-001 ini dan menegakkan batas memori/input terpisahnya sendiri.) - Perlakukan font yang disuplai pemanggil sebagai tidak tepercaya. Sebuah font
yang cacat memunculkan
NextPDF\Exception\FontParsingExceptionalih-alih merusak keluaran, jadi tangkap exception spesifik itu dan tolak input alih-alih mencoba ulang. - Validasi dan ukur input pada batas Anda, dan terapkan batas tingkat-permintaan pada ukuran dokumen untuk konten yang dipengaruhi pemanggil.
- Andalkan batas engine. Parser HTML
- Terkait. Penanganan masalah: font dan tagging.
Tabel keputusan: gejala ke tuas
Bagian berjudul “Tabel keputusan: gejala ke tuas”| Gejala | Tuas yang paling mungkin |
|---|---|
Allowed memory size … exhausted pada satu render | Turunkan $config->withImageCacheBytes(); perkecil gambar sebelum penyematan; naikkan memory_limit per-worker |
| Memori puncak naik seiring jumlah halaman | Gunakan jalur tulis streaming terdokumentasi |
| Memori worker memanjat selama banyak job | Bagikan FontRegistry/ImageRegistry via DocumentFactory; setel pm.max_requests / --max-jobs |
| Permintaan pertama lambat, biaya parse per-permintaan | Aktifkan opcache; $fontRegistry->warmup() lalu ->lock() saat boot |
| Host swap / latensi melonjak di bawah beban | Ukur pm.max_children = (RAM − overhead) / puncak per-worker |
| Lambat atau berat pada input besar/tidak tepercaya | Andalkan batas ADR-001; tolak font cacat pada FontParsingException |
Kasus tepi & jebakan
Bagian berjudul “Kasus tepi & jebakan”imageCacheBytesadalah plafon memori, bukan tuas ukuran. Menurunkannya membatasi cache sehingga sebuah build gagal cepat; ia tidak pernah me-resample atau mengkode ulang gambar yang Anda sematkan. Core tidak memiliki kendali kualitas gambar.withCompress(false)membuat berkas lebih besar dan merupakan alat debugging/profiling. Ia bukan optimasi ukuran; ia menggeser tradeoff CPU/memori (melewati langkah compress) alih-alih mengurangi memori.- Profil memori persis engine streaming adalah properti bertier
experimentaldan mungkin bergeser antar rilis minor. Perlakukan setiap pengukuran tunggal sebagai observasi, bukan konstanta yang portabel. memory_limit,opcache.*,pm.max_children, danpm.max_requestsadalah pengaturan PHP / PHP-FPM standar. NextPDF tidak mengekspos kuncinya sendiri untuknya; konfigurasi keduanya di runtime Anda, bukan diConfig.
Lihat juga
Bagian berjudul “Lihat juga”- Streaming dan memori — model streaming, batas ADR-001, dan tutorial batch-worker lengkap.
- Mengurangi ukuran berkas PDF — kompresi dan font subsetting, dua kendali ukuran yang nyata.
- Penanganan masalah: font dan tagging — kegagalan resolusi, penguraian, dan subsetting font.
- Indeks basis pengetahuan
Glosarium: streaming writer · font subsetting