Pro รุ่น
Stream
ภาพรวมโดยสังเขป
หัวข้อที่มีชื่อว่า “ภาพรวมโดยสังเขป”โมดูล Stream เรนเดอร์เอกสารทีละ batch อย่างคงทนและทำงานพร้อมกัน พร้อม การ commit ภายในเครื่องแบบ exactly-once ไปยังที่จัดเก็บคงทนบนโฮสต์เดียว (exactly-once ข้ามโฮสต์เป็นขอบเขตของ Enterprise Stream) มันแบ่งงานออกเป็นสองความรับผิดชอบที่แยกออกจากกันอย่างชัดเจน คือเอนจินเรนเดอร์ที่แปลงแมนิเฟสต์ที่ตรวจสอบแล้วให้เป็นไบต์ (และไม่ทำอย่างอื่น) กับชุดที่จัดเก็บแบบคงทน ได้แก่ committer, checkpoint, idempotency, dead-letter ซึ่งเผยแพร่ไบต์เหล่านั้นอย่างปลอดภัยและให้การรันดำเนินต่อได้หลังการล่มโดยไม่เผยแพร่เอาต์พุตที่ commit ไปแล้วซ้ำ
การให้บริการและการอนุญาตใช้งาน
หัวข้อที่มีชื่อว่า “การให้บริการและการอนุญาตใช้งาน”ความสามารถนี้มาพร้อมกับ NextPDF Pro (nextpdf/pro) และเปิดใช้งานด้วยซองสิทธิ์ใช้งานระดับ Pro การติดตั้งที่ไม่มีสิทธิ์ดังกล่าวจะไม่โหลดคลาสของความสามารถนี้ เปรียบเทียบรุ่นและขอรับสิทธิ์ใช้งาน
ไม่มีแฟล็กสิทธิ์ใช้งานแยกต่างหากรายฟีเจอร์ การทำงานพร้อมกัน (จำนวน worker) ขนาด batch งบประมาณการลองใหม่ และแบ็กเอนด์ที่จัดเก็บ (ในหน่วยความจำเทียบกับระบบไฟล์แบบคงทน) เป็นพารามิเตอร์ในขณะรันไทม์ ไม่ใช่สวิตช์สิทธิ์ใช้งาน
การติดตั้ง
หัวข้อที่มีชื่อว่า “การติดตั้ง”composer require nextpdf/pro:^3โค้ดอยู่ภายใต้เนมสเปซ NextPDF\Pro\Stream
ภาพรวมเชิงแนวคิด
หัวข้อที่มีชื่อว่า “ภาพรวมเชิงแนวคิด”Stream จัดระเบียบรอบ ๆ รอยต่อที่ถูกตรึงไว้ — NextPDF\Pro\Stream\Engine\RenderEngineInterface — ซึ่งแยกเอนจินด้านปริมาณงานออกจากความหมายของสตรีม:
- เอนจินเรนเดอร์เป็นเจ้าของการทำงานพร้อมกันและหน่วยความจำที่มีขอบเขต มันเรนเดอร์หน้าต่างของแมนิเฟสต์ที่ผ่านการตรวจสอบและกำจัดซ้ำมาก่อนแล้วผ่าน
renderBatch()และคืนค่าEngineRenderResultหนึ่งรายการต่อแมนิเฟสต์ ตามลำดับของอินพุต ที่สำคัญคือเอนจิน ปราศจาก side-effect เมื่อเทียบกับเอาต์พุตสุดท้าย: มันคืนค่าไบต์ที่เรนเดอร์แล้วพร้อมไดเจสต์ sha-256 ของไบต์เหล่านั้น โดยไม่เขียนไปยังคีย์อ็อบเจกต์สุดท้ายเลย ความบริสุทธิ์นั้นเองคือสิ่งที่ทำให้การส่งมอบแบบ exactly-once เป็นไปได้ - ผู้ทำงานร่วมของสตรีมเป็นเจ้าของการส่งมอบ ตัว committer, checkpoint store, idempotency (dedup) store และ dead-letter store เป็นผู้ตัดสินว่าไบต์จะลงที่ใด การรันจะดำเนินต่ออย่างไร งานใดเป็นการเล่นซ้ำ และเกิดอะไรขึ้นกับความล้มเหลวขั้นสิ้นสุด
ความล้มเหลวในการเรนเดอร์ต่อแมนิเฟสต์จะถูกรายงานเป็นผลลัพธ์ Failed (หรือ Timeout) รายรายการ มันไม่เคยยกเลิกทั้ง batch ซองหุ้ม batch จะสำเร็จเสมอพร้อมผลลัพธ์รายรายการ
แนวคิดสำคัญ
หัวข้อที่มีชื่อว่า “แนวคิดสำคัญ”เอนจินเรนเดอร์และ executor
หัวข้อที่มีชื่อว่า “เอนจินเรนเดอร์และ executor”InProcessRenderEngineคือเกณฑ์อ้างอิงความถูกต้องแบบซิงโครนัสกระบวนการเดียว มันตรวจสอบแต่ละแมนิเฟสต์แบบ fail-closed ผ่านRenderManifestValidatorที่มาพร้อมกัน ก่อนเรนเดอร์ผ่านSingleDocumentRendererของ Core ดังนั้นแมนิเฟสต์ที่ไม่ถูกต้องจะกลายเป็นความล้มเหลวรายรายการ (รหัสข้อผิดพลาดSPEC-MANIFEST-INVALID) แทนที่จะไปถึงตัวเรนเดอร์ConcurrentRenderEngineกระจาย batch ออกไปยังRenderUnitExecutorInterfaceและคืนลำดับ batch ที่กำหนดได้แน่นอนตาม unit index เอาต์พุตจะเหมือนกันในระดับไบต์กับการเรนเดอร์แบบลำดับ ไม่ว่าจะเสร็จในลำดับใด การเสร็จที่ขาดหาย ซ้ำ หรือไม่รู้จักจะเป็นความล้มเหลวขั้นเด็ดขาด ไม่ใช่การทิ้งแบบเงียบ ๆ- executor คือรอยต่อของการทำงานพร้อมกัน
InlineRenderUnitExecutorคือเกณฑ์อ้างอิงที่กำหนดได้แน่นอน ส่วนProcessPoolRenderUnitExecutorกระจาย batch ไปยัง subprocess ของ workerphpได้สูงสุด N ตัวที่เรนเดอร์ขนานกัน แล้วเก็บรวบรวมและตรวจสอบความสมบูรณ์ของผลลัพธ์
การ commit ที่คงทนและปราศจาก side-effect
หัวข้อที่มีชื่อว่า “การ commit ที่คงทนและปราศจาก side-effect”OutputCommitterInterface::commit() เผยแพร่ไบต์ที่เรนเดอร์แล้วไปยังปลายทางสุดท้ายเพียงครั้งเดียวเท่านั้น (exactly once): แบบ atomic (ไม่เคยมีการสังเกตเห็นอ็อบเจกต์ที่ไม่สมบูรณ์), แบบ idempotent (การ commit เนื้อหาที่เหมือนกันในระดับไบต์ซ้ำจะไม่ทำการเขียนใด ๆ และคืน CommitReceipt ที่มี idempotentReuse = true — ใบเสร็จใหม่ ไม่ใช่ใบเดิม), ไม่มีการทับซ้อนแบบเงียบ ๆ (ไบต์ที่ต่างกันไปยังคีย์ที่มีอยู่แล้วโดยไม่มี overwrite จะทำให้เกิดความขัดแย้ง) และตรวจสอบความสมบูรณ์ (committer คำนวณไดเจสต์ใหม่ก่อนเขียน) LocalFilesystemCommitter ทำสิ่งนี้สำหรับระบบไฟล์ภายใน
การกู้คืนจากจุดตรวจ
หัวข้อที่มีชื่อว่า “การกู้คืนจากจุดตรวจ”RunCheckpoint คือสิ่งกีดขวางแบบคงทนที่บันทึกว่าการรันได้ commit ไปกี่รายการ พร้อมกับ snapshot ของสถานะแบบมีคีย์ เมื่อกู้คืน processor จะเดินหน้าข้ามอย่างรวดเร็วผ่านออฟเซ็ตที่ commit แล้วและคืนสถานะแบบมีคีย์ ดังนั้นการล่มกลางการรันจะดำเนินต่อโดยไม่เผยแพร่เอาต์พุตที่ commit ไปแล้วซ้ำ FilesystemCheckpointStore คงค่าสิ่งกีดขวางแต่ละอันแบบ atomic
การกำจัดซ้ำด้วย idempotency การลองใหม่ และ dead-letter
หัวข้อที่มีชื่อว่า “การกำจัดซ้ำด้วย idempotency การลองใหม่ และ dead-letter”idempotency store คือ เส้นทางเร็ว ที่ให้ processor ลัดวงจรก่อนเรนเดอร์แมนิเฟสต์ที่ถูกเล่นซ้ำ ส่วนการเปรียบเทียบไดเจสต์ของ committer ยังคงเป็นหลักประกัน exactly-once แบบคงทน ดังนั้นบันทึก dedup ที่สูญหายอย่างเลวร้ายที่สุดทำให้เกิดการเรนเดอร์ซ้ำที่สูญเปล่าซึ่ง committer จะกำจัดซ้ำให้ RetryPolicy ให้การถอยกลับแบบเลขชี้กำลังที่มีขอบเขตและกำหนดได้แน่นอนสำหรับความล้มเหลวชั่วคราว (timeout) งานที่ใช้งบประมาณจนหมดจะถูกเก็บไว้ใน DeadLetterStoreInterface แทนที่จะสูญหาย ที่จัดเก็บแต่ละแห่งมาพร้อมรูปแบบในหน่วยความจำ (ขอบเขตการรันเดียว / การทดสอบ) และรูปแบบระบบไฟล์แบบคงทน
เครื่องหมายบ่งชี้ความคงทน
หัวข้อที่มีชื่อว่า “เครื่องหมายบ่งชี้ความคงทน”ที่จัดเก็บซึ่งสถานะอยู่รอดการรีสตาร์ทกระบวนการได้จะทำตามเครื่องหมาย DurableCapability การรันที่ปลอดภัยต่อการล่มต้องให้ผู้ทำงานร่วมทุกตัวเป็นแบบคงทน เพื่อให้มันล้มเหลวอย่างรวดเร็วแทนที่จะให้สัญญาความหมายแบบ exactly-once ที่ที่จัดเก็บในหน่วยความจำไม่สามารถรักษาไว้ได้ข้ามการรีสตาร์ท
ตัวอย่างโค้ด — เริ่มต้นอย่างรวดเร็ว
หัวข้อที่มีชื่อว่า “ตัวอย่างโค้ด — เริ่มต้นอย่างรวดเร็ว”เรนเดอร์แมนิเฟสต์หนึ่งรายการและ commit ไบต์ของมันเพียงครั้งเดียวเท่านั้น เอนจินคืนค่าไบต์พร้อมไดเจสต์ committer เผยแพร่ไบต์เหล่านั้น
<?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";}ตัวอย่างโค้ด — การใช้งานจริง
หัวข้อที่มีชื่อว่า “ตัวอย่างโค้ด — การใช้งานจริง”เรนเดอร์ batch หนึ่ง กำหนดเส้นทาง timeout ไปยังนโยบายการลองใหม่ และส่งความล้มเหลวขั้นสิ้นสุดไป dead-letter การ commit ปฏิเสธที่จะทับไบต์ที่ต่างกัน ดังนั้นการชนกันของคีย์จะถูกจับและเก็บไว้แทนที่จะสูญหาย
<?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");}เมื่อใดควรใช้
หัวข้อที่มีชื่อว่า “เมื่อใดควรใช้”- การเรนเดอร์ batch ปริมาณสูงที่ปริมาณงานได้ประโยชน์จากการดำเนินการแบบพร้อมกัน (process-pool)
- การรันที่ใช้เวลานานซึ่งต้องอยู่รอดการล่มและดำเนินต่อโดยไม่เผยแพร่เอาต์พุตซ้ำซ้อน
- ไปป์ไลน์ที่ต้องรับประกันการส่งมอบเอกสารที่เรนเดอร์แล้วแต่ละชิ้นไปยังเป้าหมายเพียงครั้งเดียวเท่านั้น
สำหรับเอกสารเดี่ยวแบบเฉพาะกิจ ให้เรนเดอร์โดยตรงด้วยโมดูล Writer คุณค่าของ Stream อยู่ที่ batch ที่คงทน ดำเนินต่อได้ และทำงานพร้อมกัน
ประสิทธิภาพ
หัวข้อที่มีชื่อว่า “ประสิทธิภาพ”ปริมาณงานขยายตามจำนวน worker ใน ProcessPoolRenderUnitExecutor (มีขอบเขตจาก maxWorkers และ maxBatchSize) ขณะที่เอนจินรักษาเอาต์พุตการเรนเดอร์ให้เหมือนกันในระดับไบต์กับเกณฑ์อ้างอิงแบบลำดับ การหมดเวลา wall-clock จำกัด batch ขนานแต่ละชุด เพื่อไม่ให้ worker ที่ค้างบล็อกได้ตลอดไป ไม่มีตัวเลขปริมาณงานคงที่ที่เผยแพร่ มันขึ้นอยู่กับความซับซ้อนของเอกสารและความขนานของโฮสต์ วัดผลด้วยเอกสารที่เป็นตัวแทน
หมายเหตุด้านความปลอดภัย
หัวข้อที่มีชื่อว่า “หมายเหตุด้านความปลอดภัย”แมนิเฟสต์ถูกตรวจสอบแบบ fail-closed ก่อนเรนเดอร์ committer ปฏิเสธการ traversal พาธ ไบต์ null สคีมแบบ stream-wrapper เป้าหมายที่เป็น symlink และเวกเตอร์ NTFS alternate-data-stream (โคลอน) และแก้ทุกคีย์ให้อยู่ภายใต้รากที่กำหนดค่าไว้หนึ่งราก ผลลัพธ์ของ worker ข้ามกระบวนการถูกแฮชใหม่และจับคู่กับไดเจสต์ที่ worker รายงาน เพื่อไม่ให้ worker ที่เพี้ยนทำให้เอาต์พุตเสียหายแบบเงียบ ๆ โมดูลนี้ไม่บันทึกเนื้อหาเอกสาร
หมายเหตุขอบเขตของ Enterprise
หัวข้อที่มีชื่อว่า “หมายเหตุขอบเขตของ Enterprise”ที่จัดเก็บแบบคงทนของ Stream ที่นี่ใช้ระบบไฟล์รองรับและเป็นแบบโฮสต์เดียว ส่วน exactly-once พร้อมกันข้ามโฮสต์ไปยังคีย์เดียวกัน และการกำจัดซ้ำแบบคงทนข้ามการรัน เป็นหน้าที่ของ committer และที่จัดเก็บแบบ object-storage ของ Enterprise ส่วน document-job stream processor ที่ขับเคลื่อนผู้ทำงานร่วมเหล่านี้เป็นเรื่องของ Enterprise Pro ให้เอนจิน สัญญา และการนำไปใช้แบบคงทนภายในเครื่อง
ทางเลือกสำรองของ Core
หัวข้อที่มีชื่อว่า “ทางเลือกสำรองของ Core”หากไม่มี Pro ให้เรนเดอร์เอกสารทีละชิ้นด้วย writer ของ NextPDF Core ส่วนการสตรีม batch แบบคงทน การดำเนินการแบบพร้อมกัน และการ commit แบบ exactly-once เป็นส่วนเพิ่มของ Pro ดู /modules/writer/
ขอบเขตการเผยแพร่
หัวข้อที่มีชื่อว่า “ขอบเขตการเผยแพร่”หน้านี้บันทึกเฉพาะพฤติกรรมที่สังเกตได้จากภายนอกและพื้นผิว public API ที่รองรับเท่านั้น พาธเนมสเปซภายใน คลาสตัวช่วย ตารางกลไก ชื่อไฟล์ runbook และคำนำหน้าตั๋วอยู่นอกขอบเขต