ข้ามไปยังเนื้อหา
getnextpdf.com

Pro รุ่น

Stream — การอ้างอิงเชิงลึก

หน้านี้บันทึกสัญญาสาธารณะ คลาส เมธอด และโหมดความล้มเหลวของระบบย่อย NextPDF\Pro\Stream ที่เกินกว่าหน้าภาพรวม ทุกชนิดด้านล่างเป็นส่วนหนึ่งของพื้นผิวสาธารณะ Pro ที่จัดทำเอกสารไว้

ความสามารถนี้มาพร้อมกับ NextPDF Pro (nextpdf/pro) และเปิดใช้งานด้วยซองสิทธิ์ใช้งานระดับ Pro การนำไปใช้งานที่ไม่มีสิทธิ์นั้นจะไม่โหลดคลาสของความสามารถนี้ เปรียบเทียบรุ่นและขอรับสิทธิ์ใช้งาน

ไม่มีแฟล็กสิทธิ์ใช้งานรายฟีเจอร์ โค้ดมาพร้อมกับรุ่น Pro จำนวน worker ขนาด batch งบประมาณการลองใหม่ และแบ็กเอนด์ที่จัดเก็บเป็นพารามิเตอร์ในขณะรันไทม์

NextPDF\Pro\Stream\Engine\RenderEngineInterface คือสัญญาระหว่างเอนจินด้านปริมาณงานกับ document-job stream processor เอนจินเป็นผู้ทำสัญญานี้ (เป็นเจ้าของการทำงานพร้อมกัน วงจรชีวิตของ worker-pool แรงดันย้อนกลับ หน่วยความจำที่มีขอบเขต) ส่วน stream processor เป็นผู้บริโภคมัน (เป็นเจ้าของสถานะแบบมีคีย์ การกำจัดซ้ำ การลองใหม่ จุดตรวจ และการ commit แบบ exactly-once) เอนจินคืนค่า ไบต์พร้อม sha-256 ไม่เคยคืนตำแหน่งที่ commit แล้ว — ความปลอดจาก side-effect นั้นเองคือสิ่งที่ให้ processor จัดเตรียม commit และทำจุดตรวจเพียงครั้งเดียวเท่านั้น

public function renderBatch(array $manifests, array $variablesByJobId = []): array; // list<EngineRenderResult>, input order
public function maxBatchSize(): int; // int<1, max> backpressure hint
public function isAvailable(): bool;

$manifests คือ list<RenderManifest> ขนาดไม่เกิน maxBatchSize() ส่วน $variablesByJobId แมป job id ไปยังตัวแปรเทมเพลต array<string, scalar> ความล้มเหลวต่อแมนิเฟสต์คือผลลัพธ์ Failed/Timeout รายรายการ และไม่เคยยกเลิกทั้ง batch

เกณฑ์อ้างอิงแบบซิงโครนัสกระบวนการเดียว ตรวจสอบแต่ละแมนิเฟสต์แบบ fail-closed ผ่าน RenderManifestValidator (เพดาน inline-payload 16 MiB, allow-list ของ conformance/signature, รูปแบบ content-hash sha-256, ไวยากรณ์โลแคล BCP-47) ก่อนเรนเดอร์ผ่าน SingleDocumentRenderer ของ Core ข้อผิดพลาดการตรวจสอบที่บล็อกจะลัดวงจรไปยัง EngineRenderResult::failed(jobId, 'SPEC-MANIFEST-INVALID', ...) ส่วน exception ของการเรนเดอร์จะกลายเป็น 'SPEC-RENDER-EXCEPTION' คอนสตรักเตอร์: __construct(SingleDocumentRenderer $renderer, int $maxBatchSize = 64, ?RenderManifestValidator $validator = null)maxBatchSize < 1 โยน InvalidArgumentException isAvailable() เป็น true เสมอ

final readonly, __construct(RenderUnitExecutorInterface $executor) ห่อแต่ละแมนิเฟสต์ไว้ใน RenderUnit ที่มีดัชนี รันผ่าน executor และจัดเรียงการเสร็จใหม่ตามดัชนีเพื่อให้เอาต์พุตเหมือนกันในระดับไบต์กับการเรนเดอร์แบบลำดับ ดัชนีการเสร็จที่อยู่นอก [0, count) โยน RenderEngineException::unknownUnit() ดัชนีที่ซ้ำโยน duplicateResult() ดัชนีที่ขาดหายโยน missingResult() maxBatchSize() และ isAvailable() มอบหมายต่อให้ executor

public function execute(array $units): iterable; // iterable<CompletedRenderUnit>, any order
public function maxBatchSize(): int;
public function isAvailable(): bool;

การนำไปใช้อาจ yield การเสร็จในลำดับใดก็ได้ ConcurrentRenderEngine คืนลำดับตามดัชนี

final readonly, __construct(RenderEngineInterface $inner) เรนเดอร์แต่ละ unit ตามลำดับผ่านเอนจินภายใน — เกณฑ์อ้างอิงความถูกต้องที่กำหนดได้แน่นอนซึ่ง executor แบบขนานต้องเทียบให้ตรงทุกไบต์ ไม่มีเวลา กระบวนการ เธรด หรือความสุ่ม

final readonly กระจาย batch ไปยัง subprocess ของ worker php ได้สูงสุด maxWorkers ตัว (ชิ้นละหนึ่ง) ที่เรนเดอร์ขนานกัน เอาต์พุตเหมือนกันในระดับไบต์กับเกณฑ์อ้างอิงแบบ inline คอนสตรักเตอร์:

__construct(
int $maxWorkers = 4,
int $maxBatchSize = 64,
?string $phpBinary = null,
?string $workerScript = null,
?string $autoload = null,
?int $timeoutSeconds = 300, // null disables the wall-clock watchdog
)

สัญญาความทนทาน:

  • ปราศจาก deadlock ปลอดภัยบน Windows payload ของ unit และผลลัพธ์เดินทางผ่าน temp file ไม่ใช่ pipe ตัวแม่ poll proc_get_status() และดึง pipe จนถึง EOF เฉพาะหลังจาก worker ออกแล้วเท่านั้น ดังนั้น worker จึงไม่สามารถทำให้ตัวแม่ค้างได้
  • การรอที่มีขอบเขต timeoutSeconds จำกัดการเรนเดอร์ขนานทั้งหมด เมื่อหมดเวลา worker ทุกตัวที่ยังรันอยู่จะถูกยุติและโยน RenderEngineException
  • สุขอนามัยของทรัพยากร บล็อก finally ปิด pipe ทำการพยายามยุติและเก็บ worker ที่เหลือรอดแบบมีขอบเขต (ยุติอย่างนุ่มนวล → บังคับ kill → เก็บ; child ที่ไม่ถูกสังเกตว่าหยุดภายในผ่อนผันที่มีขอบเขตจะถูกละทิ้งแทนที่จะเสี่ยงต่อการบล็อกไม่มีกำหนด) และ unlink temp file ทุกไฟล์ในทุกเส้นทาง
  • การเชื่อมโยงที่เชื่อถือได้ worker แต่ละตัวต้องคืนชุดดัชนีที่ได้รับมอบหมายให้ตรงพอดี (ไม่มีดัชนีที่ขาดหาย ซ้ำ หรือแปลกปลอม) ไบต์ของผลลัพธ์ที่เรนเดอร์แล้วแต่ละชิ้นถูกแฮชใหม่และจับคู่กับ sha-256 ที่ worker รายงาน และสถานะใด ๆ นอกเหนือจาก rendered/failed จะล้มเหลวขั้นเด็ดขาด ความล้มเหลว ในการเรนเดอร์ ต่อแมนิเฟสต์เป็นผลลัพธ์ Failed รายหน่วย มีเพียงความผิดพลาดด้านโครงสร้างพื้นฐาน (exit ไม่เป็นศูนย์ เอาต์พุตที่อ่านไม่ได้/เพี้ยน timeout) เท่านั้นที่ทำให้ executor ล้มเหลวขั้นเด็ดขาด

isAvailable() ต้องการให้ทั้งไฟล์ autoload และ worker script มีอยู่ ขอบเขตที่ไม่เป็นบวกหรือ timeout ที่เป็นลบจะโยน InvalidArgumentException

final readonlyint<0, max> $index, RenderManifest $manifest, array<string, scalar> $variables การเชื่อมโยงเป็นไปตาม index ไม่เคยตาม job id (ไม่รับประกันว่า job id ไม่ซ้ำกันภายใน batch)

final readonlyint $index (ไม่เชื่อถือ ตรวจสอบโดยเอนจิน), EngineRenderResult $result

final readonly ฟิลด์: jobId, EngineRenderStatus $status, ?string $bytes, ?string $sha256, int $pageCount, ?string $errorCode, ?string $errorMessage, array<non-empty-string, float> $timings แฟกทอรี: rendered(jobId, bytes, sha256, pageCount, timings = []), failed(jobId, errorCode, errorMessage), timedOut(jobId, errorMessage) (รหัส SPEC-ENGINE-TIMEOUT) isRendered() รายงานสถานะ ผลลัพธ์ที่เรนเดอร์แล้วพกไบต์และไดเจสต์ ไม่เคยพกตำแหน่งที่ commit แล้ว

enum ที่อิงสตริง: Rendered, Failed, Timeout isRetryable() เป็น true เฉพาะสำหรับ Timeout ดังนั้นผู้เรียกใช้จึงจัดประเภท timeout เป็นชั่วคราวได้โดยไม่ต้องตรวจสอบข้อผิดพลาดซ้ำ

public function commit(
string $jobId,
OutputObjectKey $target,
string $bytes,
string $sha256,
bool $overwrite = false,
): CommitReceipt;

การเผยแพร่แบบ exactly-once: แบบ atomic, แบบ idempotent (การ commit ซ้ำที่เหมือนกันในระดับไบต์ไม่ทำการเขียนใด ๆ และคืน CommitReceipt ที่มี idempotentReuse = true — ใบเสร็จใหม่ ไม่ใช่ใบเดิม โดย committedAt ของมันคือนาฬิกาปัจจุบัน) ไม่มีการทับซ้อนแบบเงียบ ๆ และตรวจสอบความสมบูรณ์ (committer คำนวณไดเจสต์ใหม่) โหมดความล้มเหลว: CommitIntegrityException (sha-256 ที่ประกาศไม่ตรงกับไบต์) OutputCommitConflictException (ไบต์ที่ต่างกันที่คีย์ซึ่งมีอยู่แล้วโดย overwrite = false) UnsupportedTargetException (สคีมเป้าหมายที่ไม่รองรับ)

final readonly, ทำตาม OutputCommitterInterface, DurableCapability __construct(string $rootDirectory, ?AtomicFileWriter $writer = null, ?ClockInterface $clock = null) ให้บริการเฉพาะสคีม file แก้ทุกเป้าหมายให้อยู่ภายใต้รากที่กำหนดค่าไว้หนึ่งราก และเขียนผ่าน atomic writer (O_EXCL temp → fsync → rename บนวอลุ่มเดียวกัน) ส่วนวิกฤติทั้งหมด (รวมถึงการสร้าง parent-directory) รันภายใต้ flock แบบเอ็กซ์คลูซีฟบนไฟล์ล็อกต่อหนึ่งราก ที่เก็บไว้ นอก keyspace ของเอาต์พุต และการ commit จะ fail-closed หากเปิดหรือยึดล็อกไม่ได้ มันปฏิเสธองค์ประกอบสุดท้ายที่เป็น symlink และคีย์ใด ๆ ที่มีโคลอน (เวกเตอร์ NTFS alternate-data-stream) ส่วน exactly-once พร้อมกันข้ามโฮสต์ไปยังคีย์เดียวกันต้องใช้ committer ของ Enterprise แบบคงทน รากที่เป็นหรือมีไดเรกทอรี temp ของระบบจะโยน InvalidArgumentException

final readonlyjobId, OutputObjectKey $target, sha256, int<0, max> $bytesWritten, bool $idempotentReuse, DateTimeImmutable $committedAt toArray() / fromArray() round-trip ได้ครบถ้วน (เป้าหมายมีโครงสร้าง ไม่ใช่ URI ที่สูญเสียข้อมูล) fromArray() เข้มงวดและโยน InvalidArgumentException เมื่อฟิลด์ขาดหายหรือผิดรูปแบบ

load(string $runId): ?RunCheckpoint และ save(RunCheckpoint $checkpoint): void (คงทนและ atomic — ผู้อ่านไม่เคยเห็นจุดตรวจที่เขียนค้างกลางคัน)

final readonlyrunId, int<0, max> $committedOffset, array $keyedState, DateTimeImmutable $updatedAt; SCHEMA_VERSION = '1.0' แฟกทอรี start(runId, at) และ advancedTo(committedOffset, keyedState, at) toArray()/toJson()/fromArray()/fromJson() ทำการ serialise; fromArray() ต้องการ run id ที่ไม่ว่างและ updated_at ที่ถูกต้อง ปฏิเสธ schema_version ที่เข้ากันไม่ได้ (ไม่ใช่ 1.x) และปรับ keyed state ให้เป็นมาตรฐานโดยทิ้งค่าที่ serialise เป็น JSON ไม่ได้ในทุกระดับความลึก เพื่อให้สถานะที่กู้คืนสามารถ serialise ใหม่ได้เสมอ เมื่อกู้คืน processor จะเดินหน้าข้ามอย่างรวดเร็วผ่าน committedOffset และคืน keyed state ส่วนสถานะที่ถูกเปลี่ยนหลังสิ่งกีดขวางล่าสุดจะถูกคำนวณไปข้างหน้าใหม่ ไม่เคยเป็นข้อผิดพลาด เพราะ exactly-once แบบคงทนมาจากการกำจัดซ้ำด้วยไดเจสต์ของ committer

final readonly, ทำตาม CheckpointStoreInterface, DurableCapability หนึ่งไฟล์ JSON ต่อการรัน เขียนแบบ atomic run id ต้องตรงกับ [A-Za-z0-9._-]+ และไม่มี .. ไดเรกทอรีที่ไม่มีอยู่จะโยน InvalidArgumentException

isCommitted(IdempotencyKey $key): bool, markCommitted(IdempotencyKey $key, CommitReceipt $receipt): void, receiptFor(IdempotencyKey $key): ?CommitReceipt เส้นทางเร็วที่ลัดวงจรก่อนเรนเดอร์การเล่นซ้ำ ส่วนการเปรียบเทียบไดเจสต์ของ committer ยังคงเป็นหลักประกันแบบคงทน ดังนั้นบันทึกที่สูญหายอย่างเลวร้ายที่สุดทำให้สิ้นเปลืองการเรนเดอร์ซ้ำที่ committer กำจัดซ้ำให้

  • InMemoryIdempotencyStore — ขอบเขตการรันเดียว / การทดสอบ (สูญหายเมื่อล่ม)
  • FilesystemIdempotencyStoreDurableCapability; หนึ่งไฟล์ JSON แบบ atomic ต่อคีย์ที่ commit แล้ว (ใบเสร็จที่ serialize แล้ว) ตั้งชื่อตามแฮชของค่าคีย์ การทำเครื่องหมายเป็น idempotent การทำเครื่องหมายซ้ำพร้อมกันแข่งกันอย่างไม่เป็นอันตรายบนไฟล์เดียว ไดเรกทอรีที่ไม่มีอยู่จะโยน InvalidArgumentException

final readonlypositive-int $maxAttempts, positive-int $baseDelayMs, positive-int $maxDelayMs __construct(int $maxAttempts = 3, int $baseDelayMs = 100, int $maxDelayMs = 30000) พร้อมความคงตัว maxAttempts >= 1 และ 1 <= baseDelayMs <= maxDelayMs <= 7 days (มิฉะนั้น InvalidArgumentException) แฟกทอรี default() และ none() (ความพยายามครั้งเดียว) shouldRetry(int $attempt): bool delayMsForAttempt(int $attempt): int<0, max> คือการถอยกลับแบบเลขชี้กำลังที่กำหนดได้แน่นอน baseDelayMs * 2^(attempt-1) จำกัดที่ maxDelayMs (ไม่มี jitter ในตัว ให้ใส่ที่จุดเรียกใช้)

add(DeadLetterRecord $record): void, all(): list<DeadLetterRecord>, count(): int<0, max>

final readonlyjobId, idempotencyKeyValue, positive-int $attempts, lastErrorCode, lastErrorMessage, DateTimeImmutable $failedAt, ?string $runId (ตัวเลือก), int<1, max> $sourceOffset (ตัวเลือก) dedupKey() คือ runId:sourceOffset เมื่อทราบทั้งสอง มิฉะนั้นคือค่าคีย์ idempotency fromArray() แจง failed_at อย่างเข้มงวดเป็น ATOM (ปฏิเสธนิพจน์แบบสัมพัทธ์หรือที่ไม่ใช่ ATOM) ดังนั้น serialise/deserialise จึงสมมาตรกัน

  • InMemoryDeadLetterStore — ขอบเขตการรันเดียว / การทดสอบ
  • FilesystemDeadLetterStoreDurableCapability; หนึ่งไฟล์ JSON แบบ atomic ต่อบันทึก ตั้งชื่อตามแฮช SHA-256 ของ dedup key (….dlq.json) ดังนั้นการเพิ่มรายการเดิมซ้ำเมื่อดำเนินต่อจึงเป็น idempotent all() อ่านบันทึกในลำดับที่กำหนดได้แน่นอน (เรียงลำดับแล้ว) และเปิดเผยบันทึกที่เสียหายด้วยการโยน ส่วน count() คือการนับไฟล์ที่ต้นทุนต่ำ ไม่ใช่การตรวจสอบความถูกต้อง

has, get, put, remove, clear บวกกับ snapshot(): array และ restore(array $snapshot): void สำหรับขอบเขตจุดตรวจ ค่าต้อง serialise เป็น JSON ได้ สำหรับเวิร์กโหลด render-and-commit แบบปริยายจะไม่ใช้ keyed state ใด ๆ มันมีอยู่สำหรับส่วนขยายการรวมกลุ่ม/การจัดหน้าต่าง InMemoryKeyedStateStore คือการนำไปใช้แบบการรันเดียว การสูญเสียมันเมื่อกู้คืนไม่มีผลเชิงความหมายสำหรับเวิร์กโหลดแบบปริยาย เพราะ exactly-once มาจากการกำจัดซ้ำด้วยไดเจสต์ของ committer

final readonly, __construct(string $tenantField = 'tenant_id', string $documentField = 'document_id') keyFor(RenderManifest $manifest): non-empty-string ได้คีย์พาร์ทิชันจาก metadata ของแมนิเฟสต์เป็น rawurlencode(tenant):rawurlencode(document) (การเข้ารหัสนี้ป้องกันไม่ให้ ("a:b","c") ชนกับ ("a","b:c")) โดยถอยกลับไปใช้ job id เมื่อฟิลด์ใดฟิลด์หนึ่งหายไป — ดังนั้นทุกแมนิเฟสต์จึงแก้เป็นคีย์ที่เสถียรและไม่ว่าง

NextPDF\Pro\Stream\DurableCapability คืออินเทอร์เฟซเครื่องหมายสำหรับ store/committer ใด ๆ ที่สถานะอยู่รอดการรีสตาร์ทกระบวนการได้ การรันที่ปลอดภัยต่อการล่มต้องให้ผู้ทำงานร่วมทุกตัวทำตามมัน เพื่อให้มันล้มเหลวอย่างรวดเร็วแทนที่จะให้สัญญา exactly-once ที่ที่จัดเก็บในหน่วยความจำไม่สามารถรักษาไว้ได้

exception ทั้งหมดของระบบย่อยทำตาม NextPDF\Pro\Stream\Exception\StreamException (ขยาย Throwable) ดังนั้นผู้เรียกใช้จึง catch (StreamException) ได้อย่างสม่ำเสมอ:

  • RenderEngineException (RuntimeException) — executor ละเมิดสัญญา batch (unit ที่ไม่รู้จัก ซ้ำ หรือขาดหาย; ความผิดพลาดของ worker; timeout)
  • CommitIntegrityException (RuntimeException) — sha-256 ที่ประกาศไม่ตรงกับ payload; รหัส spec SPEC-COMMIT-422
  • OutputCommitConflictException (RuntimeException) — ไบต์ที่ต่างกันที่คีย์ซึ่งมีอยู่แล้วโดยปิดใช้งาน overwrite; รหัส spec SPEC-COMMIT-409 (เปิดเผยผ่าน specCode())
  • UnsupportedTargetException (InvalidArgumentException) — สคีมเป้าหมายที่ committer ไม่สามารถให้บริการได้

เอนจินตรวจสอบแมนิเฟสต์เทียบกับแบบจำลองแมนิเฟสต์ของ Core และสร้างไบต์ที่กำหนดได้แน่นอนพร้อมไดเจสต์ sha-256 ส่วน committer บังคับใช้การเขียนแบบ atomic ตรวจสอบความสมบูรณ์ และ exactly-once โมดูลนี้ไม่ดำเนินการเข้ารหัสลับใด ๆ นอกเหนือจากไดเจสต์เนื้อหา sha-256 และไม่นิยามพฤติกรรมเฉพาะของ FIPS

  • renderBatch() ไม่เคยยกเลิกเมื่อมีความล้มเหลวต่อแมนิเฟสต์ ให้ตรวจสอบ EngineRenderResult แต่ละรายการ
  • ProcessPoolRenderUnitExecutor เชื่อมโยงอย่างเข้มงวดตามดัชนีและแฮชไบต์ของ worker ใหม่ worker ที่มีบั๊กจะล้มเหลวขั้นเด็ดขาดแทนที่จะทำให้เอาต์พุตเสียหาย
  • LocalFilesystemCommitter เป็นแบบโฮสต์เดียว ส่วน exactly-once ข้ามโฮสต์ต้องใช้ committer ของ Enterprise แบบคงทน
  • การรันที่ปลอดภัยต่อการล่มต้องใช้ที่จัดเก็บแบบ DurableCapability (ระบบไฟล์) ตลอด ไม่ใช่รูปแบบในหน่วยความจำ

หน้านี้บันทึกเฉพาะพฤติกรรมที่สังเกตได้จากภายนอกและพื้นผิว public API ที่รองรับเท่านั้น เส้นทาง namespace ภายใน คลาสตัวช่วย ตารางกลไก ชื่อไฟล์ runbook และคำนำหน้า ticket อยู่นอกขอบเขต