Lewati ke konten
getnextpdf.com

Pemecahan masalah memori dan performa

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 exhausted dari 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.
    1. Batasi cache gambar. NextPDF\Core\Config mengekspos imageCacheBytes (default 52428800, yaitu 50 MB). Turunkan dengan wither instance $config->withImageCacheBytes($bytes) (signature withImageCacheBytes(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.
    2. 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).
    3. Jaga kompresi tetap aktif. Sebuah Config baru memiliki compress disetel true. 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.
    4. Naikkan memory_limit secara sengaja, per worker. Ini adalah pengaturan PHP standar, bukan kunci NextPDF. Setel di config pool atau dengan ini_set('memory_limit', '256M') untuk proses CLI/queue, dan ukur terhadap puncak yang diprofil, bukan tebakan.
  • 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.
    1. 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 experimental dan simbolnya bukan permukaan publik yang stabil.
    2. Untuk parser writeHtml() native, ingat bahwa memori sisi-input dibatasi oleh guard kedalaman-nesting maupun jumlah-elemen: ADR-001 membatasi nesting pada MAX_NESTING_DEPTH = 100 dan menolak dokumen di atas MAX_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.
  • 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.
    1. Bagikan registry, buat ulang dokumen. Bangun FontRegistry dan ImageRegistry sekali saat boot dan teruskan ke sebuah DocumentFactory; buat sebuah Document baru 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. Ikuti examples/14-worker-factory.php.
    2. Batasi cache gambar bersama dengan new ImageRegistry(maxCacheBytes: ...) sehingga ia tidak dapat tumbuh tanpa batas lintas job.
    3. Daur ulang worker — kendali proses, bukan jaminan engine. Pada PHP-FPM, setel pm.max_requests sehingga setiap child respawn setelah sejumlah permintaan yang tetap. Pada Laravel queue gunakan queue:work --max-jobs / --max-time / --memory; pada Symfony Messenger gunakan messenger:consume --limit / --time-limit / --memory-limit.
  • 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 FontRegistry yang tidak dipanaskan mengurai setiap face font saat pertama kali digunakan.
  • Resolusi.
    1. Aktifkan opcache (dan JIT di tempat yang membantu). Setel opcache.enable=1 dan opcache.memory_consumption yang murah hati; di produksi setel opcache.validate_timestamps=0 sehingga 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, misalnya opcache_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.
    2. 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 sebuah FontRegistry yang 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.
    3. Jangan mengurai ulang template yang sama per permintaan. Resolve font dan resource yang dapat dipakai ulang sekali saat boot melalui registry bersama; hanya Document per-job yang harus dibuat dalam permintaan.
  • 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.
    1. Ukur pm.max_children dari puncak yang diprofil. Gunakan rumus standar:

      pm.max_children = (total RAM - OS/other overhead) / per-worker peak memory

      Ukur 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.

    2. 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.

    3. Setel pm.max_requests bersama pm.max_children sehingga 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.
    1. Andalkan batas engine. Parser HTML writeHtml() native menegakkan MAX_NESTING_DEPTH = 100 dan MAX_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.)
    2. Perlakukan font yang disuplai pemanggil sebagai tidak tepercaya. Sebuah font yang cacat memunculkan NextPDF\Exception\FontParsingException alih-alih merusak keluaran, jadi tangkap exception spesifik itu dan tolak input alih-alih mencoba ulang.
    3. Validasi dan ukur input pada batas Anda, dan terapkan batas tingkat-permintaan pada ukuran dokumen untuk konten yang dipengaruhi pemanggil.
  • Terkait. Penanganan masalah: font dan tagging.
GejalaTuas yang paling mungkin
Allowed memory size … exhausted pada satu renderTurunkan $config->withImageCacheBytes(); perkecil gambar sebelum penyematan; naikkan memory_limit per-worker
Memori puncak naik seiring jumlah halamanGunakan jalur tulis streaming terdokumentasi
Memori worker memanjat selama banyak jobBagikan FontRegistry/ImageRegistry via DocumentFactory; setel pm.max_requests / --max-jobs
Permintaan pertama lambat, biaya parse per-permintaanAktifkan opcache; $fontRegistry->warmup() lalu ->lock() saat boot
Host swap / latensi melonjak di bawah bebanUkur pm.max_children = (RAM − overhead) / puncak per-worker
Lambat atau berat pada input besar/tidak tepercayaAndalkan batas ADR-001; tolak font cacat pada FontParsingException
  • imageCacheBytes adalah 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 experimental dan mungkin bergeser antar rilis minor. Perlakukan setiap pengukuran tunggal sebagai observasi, bukan konstanta yang portabel.
  • memory_limit, opcache.*, pm.max_children, dan pm.max_requests adalah pengaturan PHP / PHP-FPM standar. NextPDF tidak mengekspos kuncinya sendiri untuknya; konfigurasi keduanya di runtime Anda, bukan di Config.

Glosarium: streaming writer · font subsetting