Pro edición
Stream
De un vistazo
Sección titulada «De un vistazo»El módulo Stream renderiza lotes de documentos de forma duradera y concurrente, con confirmación local exactamente una vez en almacenes duraderos de un solo host (la concurrencia entre hosts exactamente una vez es el límite de Stream de Enterprise). Divide el trabajo en dos responsabilidades claramente separadas: un motor de renderizado que convierte manifiestos validados en bytes (y nada más), y un conjunto de almacenes duraderos —confirmador, punto de control, idempotencia, mensajes muertos— que publican esos bytes de forma segura y permiten que una ejecución se reanude tras una caída sin volver a publicar la salida ya confirmada.
Disponibilidad y licencias
Sección titulada «Disponibilidad y licencias»Esta funcionalidad se incluye en NextPDF Pro (nextpdf/pro) y se activa con un sobre de licencia de nivel Pro. Un despliegue sin ese derecho no carga las clases de la funcionalidad. Compare ediciones y obtenga una licencia.
No hay ningún indicador de licencia por característica independiente. La concurrencia (número de workers), el tamaño de lote, el presupuesto de reintentos y el backend de almacén (en memoria frente a sistema de archivos duradero) son parámetros en tiempo de ejecución, no interruptores de licencia.
Instalación
Sección titulada «Instalación»composer require nextpdf/pro:^3El código reside bajo el espacio de nombres NextPDF\Pro\Stream.
Descripción conceptual
Sección titulada «Descripción conceptual»Stream se organiza en torno a una costura congelada —NextPDF\Pro\Stream\Engine\RenderEngineInterface— que separa el motor de rendimiento de la semántica del flujo:
- El motor de renderizado posee la concurrencia y la memoria acotada. Renderiza una ventana de manifiestos previamente validados y deduplicados mediante
renderBatch()y devuelve unEngineRenderResultpor manifiesto, en el orden de entrada. Lo crucial es que el motor no tiene efectos secundarios respecto a la salida final: devuelve los bytes renderizados más su resumen sha-256, y nunca escribe en una clave de objeto final. Esa pureza es lo que hace posible la entrega exactamente una vez. - Los colaboradores del flujo poseen la entrega. El confirmador, el almacén de puntos de control, el almacén de idempotencia (deduplicación) y el almacén de mensajes muertos deciden dónde acaban los bytes, cómo se reanuda una ejecución, qué trabajo es una repetición y qué ocurre con los fallos terminales.
Un fallo de renderizado por manifiesto se informa como un resultado Failed (o Timeout) por elemento; nunca aborta el lote. El sobre del lote siempre tiene éxito, con resultados por elemento.
Conceptos clave
Sección titulada «Conceptos clave»Motores de renderizado y ejecutores
Sección titulada «Motores de renderizado y ejecutores»InProcessRenderEnginees la línea de base de corrección síncrona, de un solo proceso. Valida cada manifiesto de fallo cerrado mediante elRenderManifestValidatorincluido antes de renderizarlo a través delSingleDocumentRendererde Core, de modo que un manifiesto incorrecto se convierte en un fallo por elemento (código de errorSPEC-MANIFEST-INVALID) en lugar de alcanzar el renderizador.ConcurrentRenderEnginereparte un lote a unRenderUnitExecutorInterfacey restaura el orden determinista del lote por índice de unidad. La salida es idéntica byte a byte a un renderizado secuencial, con independencia del orden de finalización; una finalización ausente, duplicada o desconocida es un fallo grave, nunca un descarte silencioso.- Los ejecutores son la costura de la concurrencia.
InlineRenderUnitExecutores la línea de base determinista;ProcessPoolRenderUnitExecutordistribuye un lote entre hasta N subprocesos workerphpque renderizan en paralelo, y luego recopila y comprueba la integridad de sus resultados.
Confirmación duradera y sin efectos secundarios
Sección titulada «Confirmación duradera y sin efectos secundarios»OutputCommitterInterface::commit() publica los bytes renderizados en su destino final exactamente una vez: de forma atómica (nunca se observa un objeto parcial), idempotente (volver a confirmar contenido idéntico byte a byte no realiza ninguna escritura y devuelve un CommitReceipt con idempotentReuse = true —un recibo nuevo, no el original—), sin sobrescritura silenciosa (escribir bytes divergentes en una clave ocupada sin overwrite provoca un conflicto) y con integridad comprobada (el confirmador recalcula el resumen antes de escribir). LocalFilesystemCommitter lo implementa para el sistema de archivos local.
Recuperación por punto de control
Sección titulada «Recuperación por punto de control»Un RunCheckpoint es una barrera duradera que registra cuántos elementos ha confirmado una ejecución más una instantánea del estado con clave. En la recuperación, el procesador avanza rápidamente más allá del desplazamiento confirmado y restaura el estado con clave, de modo que una caída a mitad de ejecución se reanuda sin volver a publicar la salida confirmada. FilesystemCheckpointStore persiste cada barrera de forma atómica.
Deduplicación por idempotencia, reintentos y mensajes muertos
Sección titulada «Deduplicación por idempotencia, reintentos y mensajes muertos»El almacén de idempotencia es la ruta rápida que permite al procesador hacer un cortocircuito antes de renderizar un manifiesto repetido; la comparación de resúmenes del confirmador sigue siendo la garantía duradera de exactamente una vez, de modo que un registro de deduplicación perdido provoca, en el peor de los casos, un re-renderizado malgastado que el confirmador deduplica. RetryPolicy proporciona retroceso exponencial acotado y determinista para los fallos transitorios (timeout); un trabajo que agota su presupuesto se captura en un DeadLetterStoreInterface en lugar de perderse. Cada almacén incluye una variante en memoria (alcance de una sola ejecución / pruebas) y una variante duradera en sistema de archivos.
Marcador de durabilidad
Sección titulada «Marcador de durabilidad»Los almacenes cuyo estado sobrevive al reinicio de un proceso implementan el marcador DurableCapability. Una ejecución a prueba de caídas requiere que todos los colaboradores sean duraderos, de modo que falle rápidamente en lugar de prometer una semántica de exactamente una vez que un almacén en memoria no puede mantener tras un reinicio.
Ejemplo de código — Inicio rápido
Sección titulada «Ejemplo de código — Inicio rápido»Renderice un manifiesto y confirme sus bytes exactamente una vez. El motor devuelve los bytes más un resumen; el confirmador los publica.
<?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";}Ejemplo de código — Producción
Sección titulada «Ejemplo de código — Producción»Renderice un lote, dirija los timeouts a la política de reintentos y envíe a mensajes muertos los fallos terminales. La confirmación se niega a sobrescribir bytes divergentes, de modo que una colisión de claves se detecta y captura en lugar de perderse.
<?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");}Cuándo usarlo
Sección titulada «Cuándo usarlo»- Renderizado por lotes de alto volumen donde el rendimiento se beneficia de la ejecución concurrente (con grupo de procesos).
- Ejecuciones de larga duración que deben sobrevivir a una caída y reanudarse sin publicar la salida por duplicado.
- Pipelines que deben garantizar la entrega exactamente una vez de cada documento renderizado a su destino.
Para un único documento ad hoc, renderice directamente con el módulo Writer; el valor de Stream está en los lotes duraderos, reanudables y concurrentes.
Rendimiento
Sección titulada «Rendimiento»El rendimiento escala con el número de workers en ProcessPoolRenderUnitExecutor (acotado por maxWorkers y maxBatchSize), mientras el motor mantiene la salida del renderizado idéntica byte a byte a la línea de base secuencial. Un tiempo de espera de reloj de pared limita cada lote paralelo para que un worker colgado no pueda bloquear para siempre. No hay ninguna cifra de rendimiento fija publicada; depende de la complejidad del documento y del paralelismo del host. Mida con documentos representativos.
Notas de seguridad
Sección titulada «Notas de seguridad»Los manifiestos se validan de fallo cerrado antes de renderizar. El confirmador rechaza el cruce de rutas, los bytes nulos, los esquemas de envoltorio de flujo, los destinos con enlaces simbólicos y los vectores de flujo de datos alternativo de NTFS (dos puntos), y resuelve cada clave bajo una única raíz configurada. Los resultados de los workers entre procesos se vuelven a hashear y se cotejan con el resumen informado por el worker, de modo que un worker corrupto no pueda dañar la salida en silencio. Este módulo no registra ningún contenido del documento.
Nota sobre el límite de Enterprise
Sección titulada «Nota sobre el límite de Enterprise»Los almacenes duraderos de Stream aquí están respaldados por el sistema de archivos y son de un solo host. La concurrencia entre hosts exactamente una vez a la misma clave, y la deduplicación duradera entre ejecuciones, son competencia de los confirmadores y almacenes de almacenamiento de objetos de Enterprise; el procesador de flujo de trabajos de documento que dirige a estos colaboradores es un asunto de Enterprise. Pro proporciona el motor, los contratos y las implementaciones duraderas locales.
Recurso alternativo de Core
Sección titulada «Recurso alternativo de Core»Sin Pro, renderice documentos uno a uno con el writer de NextPDF Core; el streaming por lotes duradero, la ejecución concurrente y la confirmación exactamente una vez son adiciones de Pro. Consulte /modules/writer/.
Límite de publicación
Sección titulada «Límite de publicación»Esta página documenta únicamente el comportamiento observable externamente y la superficie pública de API compatible. Las rutas de espacios de nombres internas, las clases auxiliares, las tablas de mecanismos, los nombres de archivo de runbooks y los prefijos de tickets quedan fuera de alcance.