Pro edisi
Stream
Sekilas pandang
Bagian berjudul “Sekilas pandang”Modul Stream me-render batch dokumen secara durable dan konkuren, dengan commit lokal exactly-once ke store durable satu-host (exactly-once lintas-host adalah batas Enterprise Stream). Modul ini membagi pekerjaan menjadi dua tanggung jawab yang terpisah dengan rapi: sebuah mesin render yang mengubah manifes tervalidasi menjadi byte (dan tidak lebih dari itu), serta sekumpulan store durable — committer, checkpoint, idempotensi, dead-letter — yang memublikasikan byte tersebut secara aman dan memungkinkan suatu run dilanjutkan setelah crash tanpa memublikasikan ulang keluaran yang sudah di-commit.
Ketersediaan & lisensi
Bagian berjudul “Ketersediaan & lisensi”Kapabilitas ini disertakan dalam NextPDF Pro (nextpdf/pro) dan diaktifkan dengan envelope lisensi tier Pro. Deployment tanpa entitlement tersebut tidak memuat kelas-kelas kapabilitas ini. Bandingkan edisi dan dapatkan lisensi.
Tidak ada flag lisensi per-fitur yang terpisah. Konkurensi (jumlah worker), ukuran batch, anggaran retry, dan backend store (in-memory versus filesystem durable) adalah parameter runtime, bukan sakelar lisensi.
Pemasangan
Bagian berjudul “Pemasangan”composer require nextpdf/pro:^3Kode berada di bawah namespace NextPDF\Pro\Stream.
Tinjauan konseptual
Bagian berjudul “Tinjauan konseptual”Stream ditata mengelilingi sebuah seam beku — NextPDF\Pro\Stream\Engine\RenderEngineInterface — yang memisahkan mesin throughput dari semantik stream:
- Mesin render memegang konkurensi dan memori terbatas. Mesin ini me-render satu window manifes yang sudah pra-validasi dan pra-dedup melalui
renderBatch()dan mengembalikan satuEngineRenderResultper manifes, dalam urutan input. Yang krusial, mesin ini bebas efek-samping terhadap keluaran final: ia mengembalikan byte hasil render ditambah digest sha-256-nya, tidak pernah menulis ke object key final. Kemurnian itulah yang membuat pengiriman exactly-once menjadi mungkin. - Kolaborator stream memegang pengiriman. Committer, checkpoint store, idempotensi (dedup) store, dan dead-letter store memutuskan di mana byte mendarat, bagaimana suatu run dilanjutkan, pekerjaan mana yang merupakan replay, dan apa yang terjadi pada kegagalan terminal.
Kegagalan render per-manifes dilaporkan sebagai hasil Failed (atau Timeout) per-item; hal itu tidak pernah membatalkan batch. Envelope batch selalu berhasil dengan hasil per-item.
Konsep utama
Bagian berjudul “Konsep utama”Mesin render dan executor
Bagian berjudul “Mesin render dan executor”InProcessRenderEngineadalah baseline kebenaran yang sinkron dan satu-proses. Ia memvalidasi setiap manifes secara fail-closed melaluiRenderManifestValidatoryang sudah disertakan sebelum me-render-nya melalui CoreSingleDocumentRenderer, sehingga manifes yang buruk menjadi kegagalan per-item (kode galatSPEC-MANIFEST-INVALID) alih-alih mencapai renderer.ConcurrentRenderEnginemenyebar sebuah batch keRenderUnitExecutorInterfacedan memulihkan urutan batch deterministik berdasarkan indeks unit. Keluaran identik secara byte dengan render sekuensial terlepas dari urutan penyelesaian; penyelesaian yang hilang, duplikat, atau tidak dikenal adalah kegagalan keras, tidak pernah pembuangan diam-diam.- Executor adalah seam konkurensi.
InlineRenderUnitExecutoradalah baseline deterministik;ProcessPoolRenderUnitExecutormendistribusikan sebuah batch ke hingga N subproses workerphpyang me-render secara paralel, lalu mengumpulkan dan memeriksa integritas hasilnya.
Commit durable dan bebas efek-samping
Bagian berjudul “Commit durable dan bebas efek-samping”OutputCommitterInterface::commit() memublikasikan byte hasil render ke tujuan finalnya tepat sekali: secara atomik (objek parsial tidak pernah teramati), idempoten (commit ulang konten yang identik secara byte tidak melakukan penulisan dan mengembalikan CommitReceipt dengan idempotentReuse = true — sebuah receipt baru, bukan yang asli), tanpa clobber diam-diam (byte yang berbeda ke key yang sudah terisi tanpa overwrite memunculkan konflik), dan terperiksa integritasnya (committer menghitung ulang digest sebelum menulis). LocalFilesystemCommitter mengimplementasikan ini untuk filesystem lokal.
Pemulihan checkpoint
Bagian berjudul “Pemulihan checkpoint”RunCheckpoint adalah barrier durable yang merekam berapa banyak item yang sudah di-commit oleh suatu run ditambah snapshot state berkunci. Pada pemulihan, prosesor mempercepat-maju melewati offset yang sudah di-commit dan memulihkan state berkunci, sehingga crash di tengah run dilanjutkan tanpa memublikasikan ulang keluaran yang sudah di-commit. FilesystemCheckpointStore menyimpan setiap barrier secara atomik.
Dedup idempotensi, retry, dan dead-letter
Bagian berjudul “Dedup idempotensi, retry, dan dead-letter”Idempotensi store adalah jalur cepat yang memungkinkan prosesor melakukan short-circuit sebelum me-render manifes yang di-replay; perbandingan digest milik committer tetap menjadi jaminan exactly-once yang durable, sehingga rekaman dedup yang hilang paling buruk hanya menyebabkan render ulang yang sia-sia yang akan didedup oleh committer. RetryPolicy menyediakan exponential backoff yang terbatas dan deterministik untuk kegagalan transien (timeout); job yang menghabiskan anggarannya ditangkap di sebuah DeadLetterStoreInterface alih-alih hilang. Setiap store menyertakan varian in-memory (cakupan single-run / pengujian) dan varian filesystem durable.
Marker durabilitas
Bagian berjudul “Marker durabilitas”Store yang state-nya bertahan melewati restart proses mengimplementasikan marker DurableCapability. Run yang crash-safe mensyaratkan setiap kolaborator bersifat durable agar gagal cepat alih-alih menjanjikan semantik exactly-once yang tidak dapat dipertahankan oleh store in-memory melewati sebuah restart.
Contoh kode — Quick start
Bagian berjudul “Contoh kode — Quick start”Render satu manifes dan commit byte-nya tepat sekali. Mesin mengembalikan byte ditambah sebuah digest; committer memublikasikannya.
<?php
declare(strict_types=1);
use NextPDF\Manifest\OutputObjectKey;use NextPDF\Manifest\Render\SingleDocumentRenderer;use NextPDF\Manifest\RenderManifestBuilder;use NextPDF\Manifest\TemplateRef;use NextPDF\Pro\Stream\Commit\LocalFilesystemCommitter;use NextPDF\Pro\Stream\Engine\InProcessRenderEngine;
$outputRoot = __DIR__ . '/out';\is_dir($outputRoot) || \mkdir($outputRoot, 0o775, true);
// The engine renders bytes only — it never writes the final object.$engine = new InProcessRenderEngine(SingleDocumentRenderer::standalone());
$target = OutputObjectKey::file('out', 'invoices/1001.pdf');
$manifest = RenderManifestBuilder::create('invoice-1001') ->withInlineInput('<h1>Invoice 1001</h1><p>Amount due: 42.00</p>') ->withTemplate(TemplateRef::html()) ->withOutputKey($target) ->build();
$result = $engine->renderBatch([$manifest])[0];
// A durable committer publishes the rendered bytes exactly once.$committer = new LocalFilesystemCommitter($outputRoot);
if ($result->isRendered()) { $receipt = $committer->commit($result->jobId, $target, $result->bytes, $result->sha256); echo $receipt->target->toUri(), ' (', $receipt->bytesWritten, " bytes)\n";}Contoh kode — Production
Bagian berjudul “Contoh kode — Production”Render sebuah batch, arahkan timeout ke retry policy, dan dead-letter-kan kegagalan terminal. Commit menolak meng-clobber byte yang berbeda, sehingga tabrakan key tertangkap dan disimpan alih-alih hilang.
<?php
declare(strict_types=1);
use DateTimeImmutable;use NextPDF\Manifest\OutputObjectKey;use NextPDF\Manifest\Render\SingleDocumentRenderer;use NextPDF\Manifest\RenderManifest;use NextPDF\Manifest\RenderManifestBuilder;use NextPDF\Manifest\TemplateRef;use NextPDF\Pro\Stream\Commit\LocalFilesystemCommitter;use NextPDF\Pro\Stream\Engine\EngineRenderStatus;use NextPDF\Pro\Stream\Engine\InProcessRenderEngine;use NextPDF\Pro\Stream\Exception\OutputCommitConflictException;use NextPDF\Pro\Stream\Retry\DeadLetterRecord;use NextPDF\Pro\Stream\Retry\InMemoryDeadLetterStore;use NextPDF\Pro\Stream\Retry\RetryPolicy;
$outputRoot = __DIR__ . '/out';\is_dir($outputRoot) || \mkdir($outputRoot, 0o775, true);
$engine = new InProcessRenderEngine(SingleDocumentRenderer::standalone(), maxBatchSize: 64);$committer = new LocalFilesystemCommitter($outputRoot);$deadLetter = new InMemoryDeadLetterStore();$retry = RetryPolicy::default(); // 3 attempts, 100ms base, 30s cap.
/** * Build one manifest and remember its output target for the commit stage. * * @return array{RenderManifest, OutputObjectKey} */$makeJob = static function (string $jobId, string $html): array { $target = OutputObjectKey::file('out', 'invoices/' . $jobId . '.pdf'); $manifest = RenderManifestBuilder::create($jobId) ->withInlineInput($html) ->withTemplate(TemplateRef::html()) ->withOutputKey($target) ->build();
return [$manifest, $target];};
/** @var array<non-empty-string, OutputObjectKey> $targets */$targets = [];$manifests = [];foreach (['inv-2001' => '<h1>2001</h1>', 'inv-2002' => '<h1>2002</h1>'] as $id => $html) { [$manifest, $target] = $makeJob($id, $html); $manifests[] = $manifest; $targets[$id] = $target;}
foreach ($engine->renderBatch($manifests) as $result) { // A timeout is transient — the policy decides whether to re-enqueue it. if ($result->status === EngineRenderStatus::Timeout && $retry->shouldRetry(1)) { // Re-enqueue on the caller's work queue after delayMsForAttempt(1) ms. continue; }
if (!$result->isRendered()) { $deadLetter->add(new DeadLetterRecord( jobId: $result->jobId, idempotencyKeyValue: $result->jobId, attempts: $retry->maxAttempts, lastErrorCode: $result->errorCode ?? 'SPEC-RENDER-EXCEPTION', lastErrorMessage: $result->errorMessage ?? '', failedAt: new DateTimeImmutable(), ));
continue; }
try { // overwrite=false: identical bytes are an idempotent no-op; divergent // bytes to an occupied key raise SPEC-COMMIT-409 instead of clobbering. $receipt = $committer->commit( $result->jobId, $targets[$result->jobId], $result->bytes, $result->sha256, ); } catch (OutputCommitConflictException $e) { $deadLetter->add(new DeadLetterRecord( jobId: $result->jobId, idempotencyKeyValue: $result->jobId, attempts: 1, lastErrorCode: $e->specCode(), lastErrorMessage: $e->getMessage(), failedAt: new DateTimeImmutable(), ));
continue; }
echo $receipt->idempotentReuse ? "reused {$receipt->target->toUri()}\n" : "committed {$receipt->target->toUri()}\n";}
if ($deadLetter->count() > 0) { \fwrite(\STDERR, $deadLetter->count() . " job(s) dead-lettered\n");}Kapan digunakan
Bagian berjudul “Kapan digunakan”- Rendering batch bervolume tinggi di mana throughput diuntungkan oleh eksekusi konkuren (process-pool).
- Run berdurasi panjang yang harus bertahan dari crash dan dilanjutkan tanpa memublikasikan keluaran dua kali.
- Pipeline yang harus menjamin pengiriman exactly-once setiap dokumen hasil render ke targetnya.
Untuk satu dokumen ad-hoc, render langsung dengan modul Writer; nilai Stream terletak pada batch yang durable, resumable, dan konkuren.
Kinerja
Bagian berjudul “Kinerja”Throughput menskala bersama jumlah worker di ProcessPoolRenderUnitExecutor (dibatasi oleh maxWorkers dan maxBatchSize), sementara mesin menjaga keluaran render identik secara byte dengan baseline sekuensial. Timeout wall-clock membatasi setiap batch paralel sehingga worker yang macet tidak dapat memblokir selamanya. Tidak ada angka throughput tetap yang dipublikasikan; angka itu bergantung pada kompleksitas dokumen dan paralelisme host. Ukur dengan dokumen yang representatif.
Catatan keamanan
Bagian berjudul “Catatan keamanan”Manifes divalidasi secara fail-closed sebelum render. Committer menolak path traversal, null byte, skema stream-wrapper, target ber-symlink, dan vektor NTFS alternate-data-stream (titik dua), serta meresolusi setiap key di bawah satu root yang dikonfigurasi. Hasil worker lintas-proses di-hash ulang dan dicocokkan terhadap digest yang dilaporkan worker sehingga worker yang rusak tidak dapat merusak keluaran secara diam-diam. Modul ini tidak mencatat isi dokumen.
Catatan batas Enterprise
Bagian berjudul “Catatan batas Enterprise”Store durable Stream di sini berbasis filesystem dan satu-host. Exactly-once konkuren lintas-host ke key yang sama, serta dedup durable lintas run, adalah tugas committer dan store penyimpanan-objek Enterprise; prosesor stream document-job yang menggerakkan kolaborator ini merupakan perkara Enterprise. Pro menyediakan mesin, kontrak, dan implementasi durable lokal.
Fallback / alternatif Core
Bagian berjudul “Fallback / alternatif Core”Tanpa Pro, render dokumen satu per satu dengan writer NextPDF Core; batch streaming durable, eksekusi konkuren, dan commit exactly-once adalah tambahan Pro. Lihat /modules/writer/.
Batas publikasi
Bagian berjudul “Batas publikasi”Halaman ini hanya mendokumentasikan perilaku yang teramati secara eksternal dan permukaan API publik yang didukung. Path namespace internal, kelas helper, tabel mekanisme, nama file runbook, dan prefiks tiket berada di luar cakupan.