Lewati ke konten

Pembuatan dokumen bervolume tinggi

Spec: ISO 24495-1:2023, §5 Spec: ISO 9241-112:2025, §6.1.2.3 Evidence: Benchmark-backed

Membuat satu PDF bisa hanya berupa satu panggilan fungsi. Membuat seratus ribu PDF secara terjadwal adalah persoalan sistem: memori yang harus tetap terbatas, pekerjaan yang harus berjalan paralel, dan angka yang harus bermakna. Halaman ini menelusuri skenario pembuatan dokumen batch, dari pertanyaan tentang throughput hingga deployment yang tahan beban. Halaman ini menegaskan secara terus terang bahwa jawaban yang jujur adalah “ukurlah pada dokumen Anda sendiri”, bukan satu angka unggulan.

Pembuatan dokumen batch biasanya gagal dalam dua pola. Yang pertama adalah pembengkakan memori secara bertahap. Sebuah worker yang berjalan lama menumpuk state yang tertahan, dokumen demi dokumen, hingga akhirnya dihentikan paksa di tengah batch; proses itu tidak selesai, sekaligus tidak gagal secara bersih. Yang kedua adalah angka yang terkesan meyakinkan tetapi tidak bermakna: benchmark dari dokumen sederhana dipakai untuk menentukan ukuran armada yang merender dokumen kompleks, dan kekeliruannya baru terlihat saat berada di bawah beban produksi.

Anda dapat menghindari keduanya, tetapi hanya jika Anda merancang model memori dan metode pengukuran sejak awal, bukan menambahkannya setelah insiden pertama terjadi.

  • Satuan kerjanya adalah dokumen sekali pakai, bukan dokumen yang dipakai bersama. Simpan data sepanjang umur proses (fon, cache gambar) di registry bersama; buat dan buang dokumen pada setiap render.
  • Memori terdiri dari dua bagian, dan hanya satu yang penting bagi worker yang berjalan lama. Puncak sesaat selama render wajar terjadi; memori tertahan yang tidak kembali adalah kebocoran yang mengakhiri sebuah batch.
  • Throughput berasal dari paralelisme dan biaya per render yang terkendali. Bentuk yang tahan beban adalah antrean yang memasok pekerjaan ke worker tanpa state, masing-masing merender lalu melepaskan.
  • Angka tanpa metodenya bukanlah angka. NextPDF melaporkan pengukuran per render sebagai data yang Anda kumpulkan, dan menolak klaim kecepatan tanpa konteks. Angka yang paling penting adalah angka yang Anda ukur pada template Anda sendiri (ISO 24495-1 §5.x11 — letakkan pesan penting di tempat pembaca menemukannya).

Arsitekturnya bertumpu pada satu keputusan: state yang hidup sepanjang umur proses bersifat bersama dan tidak dapat diubah; state yang hidup selama satu render bersifat baru dan dibuang. Fon adalah data struktural yang diurai sekali lalu dikunci, sehingga tidak ada render yang dapat mengubahnya dan mencemari render berikutnya. Cache gambar adalah penyimpanan least-recently-used terbatas dan tidak pernah dikunci, sehingga memori tetap terbatas tanpa bocor antarpermintaan. Factory dokumen adalah singleton tanpa state; setiap dokumen yang dibuatnya bersifat sekali pakai.

Pemisahan inilah yang membuat sebuah worker aman dijalankan berjam-jam di bawah Octane, RoadRunner, atau Swoole. Secara struktural, ini menghilangkan mode kegagalan ketika “permintaan N merusak permintaan N+1”, alih-alih hanya berharap dokumen mereset dirinya sendiri.

Skenario ini memiliki empat tahap.

  1. Warm the shared state once On worker boot, parse and lock the font registry and size the image cache. This cost is paid once, not per document.
  2. Enqueue the work A queue holds the render jobs. The queue is the throughput dial — workers scale horizontally behind it.
  3. Render on a disposable document Each worker creates a fresh document from the factory, renders, emits the bytes, and lets the document go.
  4. Measure, then size Collect per-render time and peak memory. Size the fleet from measurements on your own templates, not a generic figure.
Skenario volume tinggi dari hulu ke hilir: state bersama yang tidak dapat diubah dihangatkan satu kali; setiap pekerjaan merender pada dokumen sekali pakai lalu melepaskannya; throughput diskalakan dengan menambah worker, bukan dengan memperbesar satu worker.

Jembatan framework membuat bentuk ini menjadi bawaan, bukan sesuatu yang harus Anda rangkai sendiri. Service provider Laravel mendaftarkan registry fon sebagai singleton yang sudah dihangatkan dan dikunci, lalu mengikat dokumen sebagai instance baru pada setiap resolve. Provider ini menyertakan job berbasis antrean dengan jumlah percobaan terbatas, timeout, dan backoff eksponensial. Job tersebut memvalidasi jalur outputnya di sisi worker, karena payload antrean yang diserialisasi dapat dirusak dalam perjalanan. Integrasi Symfony dan CodeIgniter mengikuti disiplin yang sama: dokumen sekali pakai dan registry bersama.

Model memori ini didukung kode. Evidence: Code-backed NextPdfServiceProvider di Laravel mendaftarkan FontRegistry sebagai singleton yang dihangatkan lalu di-lock(), ImageRegistry sebagai singleton LRU-terbatas yang sengaja tidak dikunci, dan Document sebagai binding per resolve melalui factory tanpa state. Model dokumen sekali pakai ada di dalam wiring-nya, bukan sekadar dalam uraian teks. GeneratePdfJob membawa tries, timeout, dan backoff serta memvalidasi ulang jalur outputnya di dalam handle().

Permukaan pengukuran ini didukung benchmark. Evidence: Benchmark-backed Engine menghasilkan sebuah RenderReport yang tidak dapat diubah untuk setiap pembuatan, yang memuat waktu render dalam milidetik, memori puncak dalam byte, jumlah halaman, jumlah peringatan, dan kemunculan fallback — yaitu masukan yang persis Anda butuhkan untuk menentukan ukuran armada. Sebuah penganalisis fragmentasi memori terpisah membedakan memori puncak (sesaat) dari memori tertahan. Pembedaan inilah yang memberi tahu Anda apakah worker yang berjalan lama masih sehat atau perlahan mengalami kebocoran. Harness benchmark itu sendiri dikonfigurasi untuk putaran berulang dengan pemanasan, karena satu kali pengukuran waktu hanyalah derau.

Disiplin ini merupakan prinsip desain: Evidence: Design principle NextPDF melaporkan performa beserta metodenya dan menolak klaim kecepatan tanpa konteks. Ini konsisten dengan cara dokumentasi ini ditulis — Spec: ISO 24495-1:2023, §5 menempatkan pesan yang penting di tempat pembaca akan menemukannya. Pesan penting di sini adalah “ukurlah beban kerja Anda sendiri”.

Kode di bawah ini adalah loop dokumen sekali pakai yang dilengkapi pengukuran. Engine menghasilkan RenderReport; antrean adalah bagian dari infrastruktur Anda.

<?php
declare(strict_types=1);
use NextPDF\Contracts\DocumentFactoryInterface;
use NextPDF\Observability\RenderReport;
use Psr\Log\LoggerInterface;
/**
* One batch worker iteration: render, emit, release, measure.
*
* The factory and its registries are process-lifetime singletons; the
* document is disposable. Retained memory must return to baseline between
* iterations or the worker is leaking.
*
* @param iterable<int, callable(\NextPDF\Core\Document): \NextPDF\Core\Document> $jobs
*/
function runBatch(
DocumentFactoryInterface $factory,
LoggerInterface $logger,
iterable $jobs,
): void {
foreach ($jobs as $jobId => $build) {
$startedAt = hrtime(true);
// Fresh, disposable document — shares the warmed registries.
$doc = $factory->create();
$doc = $build($doc);
$bytes = $doc->getPdfData();
// Hand the bytes off to your sink (object store, response, etc.).
unset($doc, $bytes); // let the per-render state go
$elapsedMs = (hrtime(true) - $startedAt) / 1_000_000;
$logger->info('pdf.render.complete', [
'job_id' => $jobId,
'render_time_ms' => round($elapsedMs, 2),
'peak_memory_mb' => round(memory_get_peak_usage(true) / 1_048_576, 2),
]);
}
}

unset() bukan sekadar hiasan. State per render memang harus dilepaskan pada setiap iterasi agar memori tertahan kembali ke baseline. Worker yang baseline-nya terus naik dari iterasi ke iterasi adalah kegagalan yang ingin dicegah oleh loop ini.

Kesalahpahaman utama adalah “berapa banyak PDF per detik yang bisa dibuat NextPDF?” seolah-olah ada satu jawaban tunggal. Padahal tidak ada, dan mengutip satu angka adalah cara keliru mengukur armada. Biaya render didominasi oleh dokumen, jadi satu-satunya angka yang layak ditindaklanjuti adalah angka yang diukur pada template Anda sendiri menggunakan laporan per render dari engine itu sendiri. Angka tanpa dokumen, perangkat keras, dan metode di baliknya hanyalah hiasan, bukan data.

Kesalahpahaman kedua adalah bahwa memori puncaklah yang harus diperhatikan. Puncak bersifat sesaat dan memang diharapkan — ia akan kembali. Angka yang mengakhiri sebuah batch adalah memori tertahan yang tidak kembali. Itulah tepatnya alasan mengapa engine memisahkan keduanya.

  • Tidak ada angka throughput universal, dan halaman ini sengaja tidak menyebutkan satu pun. Biaya render bergantung pada dokumen Anda; ukurlah dengan laporan per render.
  • Memori yang terbatas bergantung pada penggunaan model dokumen sekali pakai. Mempertahankan sebuah dokumen untuk banyak render, atau berbagi state per render yang dapat diubah, membatalkan jaminan tersebut. Jembatan framework secara bawaan menggunakan bentuk yang aman. Wiring yang dibuat sendiri harus menirukannya.
  • Cache gambar bersifat terbatas, bukan tak terbatas. Di bawah beban kerja gambar unik yang berat, LRU akan mengeluarkan entri. Itulah rancangannya, bukan sebuah regresi.
  • Penentuan ukuran worker-pool, pemilihan antrean, dan autoscaling adalah keputusan deployment di luar engine. NextPDF menyediakan pengukuran dan primitif yang terbatas. Ia tidak menjalankan antrean Anda.
  • RenderReport adalah data, bukan vonis. Ia memberi tahu Anda apa yang terjadi pada sebuah render. Mengubahnya menjadi rencana kapasitas adalah analisis Anda.
  • Halaman ini didukung benchmark untuk permukaan pengukuran dan didukung kode untuk model memori. Halaman ini tidak menegaskan laju tertentu apa pun.
Queued high-volume generation primitives — edition availability
Edition Availability
Core

Model dokumen sekali pakai, registry bersama yang tidak dapat diubah, RenderReport per render, dan penganalisis fragmentasi memori adalah bagian dari Core. Pembuatan PDF bervolume tinggi yang biasa tidak memerlukan tier komersial.

Pro

Primitif yang sama; fitur komersial (penandatanganan, PDF/A) menambah biaya per render yang sebaiknya Anda ukur, bukan diasumsikan.

Enterprise

Primitif yang sama; pekerjaan faktur terstruktur dan validasi menambah biaya per render lagi, yang berskala mengikuti ukuran payload dan kumpulan aturan.

  • Dokumen sekali pakai — instance dokumen yang dibuat untuk satu kali render dan dibuang setelahnya, sehingga tidak ada state yang bocor ke render berikutnya.
  • Registry bersama — state sepanjang umur proses yang tidak dapat diubah setelah pemanasan (fon, cache gambar) dan dipakai ulang di berbagai render tanpa biaya per render.
  • Memori puncak — titik tertinggi sesaat selama sebuah render; wajar terjadi dan kembali ke baseline.
  • Memori tertahan — memori yang masih dipegang setelah sebuah render selesai; baseline tertahan yang terus naik antarrender adalah kebocoran.
  • Worker — proses berumur panjang yang menarik job render dari antrean; memorinya harus tetap terbatas agar bertahan sepanjang satu batch.
  • RenderReport — snapshot metrik per render dari engine yang tidak dapat diubah (waktu, memori puncak, jumlah halaman, peringatan) yang dipakai untuk menentukan kapasitas dari data nyata.