Enterprise edycja
Stream: przetwarzanie zadań dokumentowych
W skrócie
Dział zatytułowany „W skrócie”NextPDF\Enterprise\Stream\DocumentJobStreamProcessor zamienia strumień manifestów renderowania w trwałe, rozliczalne wyniki. Konsumuje iterable<RenderManifest> jako generator, renderuje ograniczone okna przez silnik renderujący Pro i finalizuje każde zadanie w kolejności źródłowej. Każde zadanie kończy się dokładnie jednym stanem końcowym: wynik zatwierdzony, rozpoznany jako już zatwierdzony lub skierowany do dead-letter. Postęp jest zapisywany w checkpointach, więc przerwany awaryjnie przebieg wznawia się bez ponownej publikacji czegokolwiek.
Historia Stream dzieli się na dwie edycje, a podział jest celowy. Pro dostarcza trwały, współbieżny silnik renderujący oraz lokalne, jednohostowe magazyny na systemie plików — połowę wewnątrzprocesową. Enterprise dostarcza ten procesor strumienia zadań dokumentowych plus elementy przekraczające granice hostów: committer magazynu obiektów (ObjectStorageCommitter) oraz trwały outbox zdarzeń końcowych (FilesystemOutboxEmitter). Strona Pro Stream wyraża tę samą granicę ze swojej strony.
Dostępność i licencjonowanie
Dział zatytułowany „Dostępność i licencjonowanie”Ta funkcja jest dostarczana w NextPDF Enterprise (nextpdf/enterprise) i aktywuje się z kopertą licencyjną poziomu Enterprise. Wdrożenie bez tego uprawnienia nie ładuje klas tej funkcji. Porównaj edycje i zdobądź licencję.
Instalacja
Dział zatytułowany „Instalacja”composer require nextpdf/enterpriseKlasy na tej stronie znajdują się w NextPDF\Enterprise\Stream oraz NextPDF\Enterprise\Stream\Storage. Konsumują zamrożone kontrakty Pro w NextPDF\Pro\Stream — interfejsy silnika, committera, checkpointu, idempotencji, ponawiania i dead-letter.
Przegląd koncepcyjny
Dział zatytułowany „Przegląd koncepcyjny”Zadaniem procesora jest semantyka dostawy, nie renderowanie. Grupuje strumień manifestów w okna offsetów źródłowych nie większe niż rozmiar batcha silnika. Każde okno renderuje się przez RenderEngineInterface::renderBatch(), z ograniczonym, deterministycznym ponawianiem przekroczeń czasu poszczególnych elementów. Następnie każdy element jest finalizowany w kolejności offsetów źródłowych do stanu końcowego.
Granica dokładnie raz obowiązuje na element i jest zakotwiczona w committerze, nie w koordynacji. Trwały postęp to najwyższy znacznik offsetu liczonego od 1: każdy offset na poziomie checkpointu lub poniżej osiągnął stan końcowy. Kolejność bariery jest stała: zatwierdź bajty, przesuń znacznik, zapisz checkpoint, opróżnij zbuforowane znaczniki idempotencji, następnie emituj zdarzenia końcowe. Awaria między zatwierdzeniem a checkpointem powoduje idempotentne ponowne zatwierdzenie przy wznowieniu, ponieważ committer porównuje sygnatury. Awaria po checkpointcie przewija do przodu poza offset, więc nic nie publikuje się dwa razy.
ObjectStorageCommitter implementuje OutputCommitterInterface z Pro wobec magazynu obiektów przez minimalny ObjectStorageClientInterface. container celu to bucket, a jego key to klucz obiektu. Ponowne zatwierdzenie identycznych bajtów to operacja pusta z porównaniem sygnatury. Rozbieżne bajty bez overwrite podnoszą konflikt SPEC-COMMIT-409. Świeży obiekt jest tworzony wyłącznie atomowym zapisem warunkowym putIfAbsent(); przegranie tego wyścigu wyzwala ograniczoną pętlę ponownego odczytu i rozstrzygnięcia. Dokładnie raz między zapisującymi obowiązuje zatem dokładnie w takim stopniu, w jakim putIfAbsent() Twojego adaptera jest prawdziwym zapisem warunkowym — If-None-Match: * na S3, ifGenerationMatch: 0 na GCS. Ten cykl dostarcza interfejs plus wewnątrzpamięciowy NullObjectStorageClient; działający adapter S3/GCS jest dostarczany przez hosta.
Zdarzenia końcowe domykają pętlę dla systemów pobocznych. Po barierze checkpointu procesor próbuje wyemitować JobTerminalEvent dla każdego sfinalizowanego zadania — identyfikatory, status, potwierdzenie, szczegóły błędu, liczbę prób i nigdy żadnych bajtów PDF. Przy zwykłym emiterze zwrotnym emisja jest co najwyżej raz: zdarzenia po checkpointcie mogą zostać pominięte przy wznowieniu po awarii. FilesystemOutboxEmitter czyni każde zdarzenie trwałym z chwilą uruchomienia emit(): każde zdarzenie to jeden atomowy plik JSON nazwany hashem swojego deterministycznego eventId, więc ponowna emisja po wznowieniu jest idempotentna, przekaźnik dostarcza co najmniej raz, a konsumenci deduplikują po eventId. Jedna granica pozostaje w każdym przypadku: emisja następuje po barierze checkpointu, więc awaria między checkpoint.save() a emit() pomija zdarzenie końcowe tego elementu przy wznowieniu. Systemy poboczne wymagające kompletnego rejestru zdarzeń powinny uzgadniać wobec zatwierdzonych obiektów (magazyn jest źródłem prawdy), nie wobec samego outbox.
Licencjonowanie jest wpięte w ścieżkę wyjściową. Fabryka withBrandingFromLicense() rozwiązuje strategię brandingu ewaluacyjnego raz na przebieg z licencji. Płatna licencja rozwiązuje się do transformacji tożsamościowej. Ewaluacyjna lub brakująca licencja znakuje wodnym znakiem każdy zatwierdzony dokument, a dokument, którego nie da się zabrandować, trafia do dead-letter — procesor nigdy nie zatwierdza niezabrandowanych bajtów ewaluacyjnych.
Dlaczego działa w ten sposób
Dział zatytułowany „Dlaczego działa w ten sposób”Nośną decyzją jest to, że dokładnie raz opiera się na porównywanym sygnaturą, warunkowo tworzonym obiekcie committera — nie na rozproszonych blokadach czy konsensusie. Zapis warunkowy magazynu obiektów jest jedynym prymitywem atomowym, jakiego wymaga projekt, a wszystko inne może zawieść i się odtworzyć. Dlatego silnik renderujący musi pozostać wolny od efektów ubocznych, dlaczego kluczowany stan i wewnątrzprzebiegowe pamięci podręczne deduplikacji traktowane są jako przeliczalne przyspieszenia i dlaczego niejednoznaczne zatwierdzenie przerywa przebieg zamiast zgadywać: ścieżka wznowienia zbiega się przez to samo porównanie sygnatur. To także powód, dla którego pojedynczy zapisujący na runId jest deklarowanym wymaganiem, a nie egzekwowaną dzierżawą — magazyn checkpointu celowo pozostaje prosty, a warstwa zatwierdzania pozostaje siatką bezpieczeństwa.
Tło projektowe: Generowanie dokumentów o dużej objętości.
Powierzchnia API
Dział zatytułowany „Powierzchnia API”DocumentJobStreamProcessor
Dział zatytułowany „DocumentJobStreamProcessor”Hosty powinny konstruować przez fabrykę, aby kontrola licencja-branding nigdy nie pozostała niepodłączona:
public static function withBrandingFromLicense( RenderEngineInterface $engine, OutputCommitterInterface $committer, IdempotencyStoreInterface $idempotency, CheckpointStoreInterface $checkpoints, KeyedStateStoreInterface $state, DeadLetterStoreInterface $deadLetters, RetryPolicy $retryPolicy, ClockInterface $clock, EntitlementEvaluator $entitlementEvaluator, ?LicenseKey $license, ?StreamProcessorProbe $probe = null, ?JobCompletionEmitterInterface $emitter = null, ?BrandingApplicator $brandingApplicator = null,): self$clock to Symfony\Component\Clock\ClockInterface (backoff ponawiania śpi przez niego). Licencja null rozwiązuje się fail-closed do brandingu ewaluacyjnego.
Jedyny punkt wejścia przetwarza jeden przebieg i zwraca jego liczniki:
public function process(iterable $manifests, StreamProcessorConfig $config): ProcessingSummaryRzuca lub zawodzi z: NextPDF\Enterprise\Stream\Exception\StreamProcessorException, gdy nie są spełnione warunki wstępne bezpieczeństwa po awarii (przebieg crashSafe z nietrwałymi współpracownikami) lub gdy zatwierdzenie jest niejednoznaczne; InvalidArgumentException, gdy windowSize przekracza maxBatchSize() silnika.
StreamProcessorConfig
Dział zatytułowany „StreamProcessorConfig”public function __construct( public string $runId, int $windowSize = 32, int $checkpointIntervalJobs = 100, public bool $crashSafe = true, public bool $emitSkippedCompletions = false,)Rzuca lub zawodzi z: InvalidArgumentException, gdy windowSize lub checkpointIntervalJobs jest poniżej 1. $runId to stabilny identyfikator przebiegu z pojedynczym zapisującym, który jest kluczem wznowienia z checkpointu.
ObjectStorageCommitter
Dział zatytułowany „ObjectStorageCommitter”public function __construct( private ObjectStorageClientInterface $client, private string $scheme, private ClockInterface $clock,) {}$scheme nazywa schemat celu, który obsługuje ten committer (na przykład s3 lub gcs); $clock tutaj to Psr\Clock\ClockInterface.
public function commit( string $jobId, OutputObjectKey $target, string $bytes, string $sha256, bool $overwrite = false,): CommitReceiptRzuca lub zawodzi z: UnsupportedTargetException przy niezgodności schematu; RenderManifestException, gdy klucz celu nie jest bezpieczny względem kontenera; CommitIntegrityException, gdy zadeklarowany sha-256 nie pasuje do bajtów; OutputCommitConflictException (SPEC-COMMIT-409) przy rozbieżnych bajtach bez overwrite; RuntimeException, gdy wyścig tworzenia nie może się zbiec po 5 próbach przy współbieżnej mutacji.
ObjectStorageClientInterface
Dział zatytułowany „ObjectStorageClientInterface”Minimalna powierzchnia adaptera, którą implementuje działająca integracja S3/GCS:
public function shaOf(string $bucket, string $key): ?string;
public function put(string $bucket, string $key, string $bytes, string $sha256): void;
public function putIfAbsent(string $bucket, string $key, string $bytes, string $sha256): bool;putIfAbsent() musi być prawdziwym atomowym tworzeniem warunkowym (If-None-Match: * na S3, ifGenerationMatch: 0 na GCS) i zwraca true tylko wtedy, gdy to wywołanie zapisało obiekt. put() to bezwarunkowe nadpisanie używane wyłącznie, gdy manifest zażądał overwrite.
JobCompletionEmitterInterface i FilesystemOutboxEmitter
Dział zatytułowany „JobCompletionEmitterInterface i FilesystemOutboxEmitter”public function emit(JobTerminalEvent $event): void;Emiter jest opcjonalny w procesorze. Zdarzenia wystrzeliwują dopiero po trwałym sfinalizowaniu elementu. FilesystemOutboxEmitter to dostarczana trwała implementacja:
public function __construct(string $directory, ?AtomicFileWriter $writer = null)Rzuca lub zawodzi z: InvalidArgumentException, gdy katalog nie istnieje; emit() rzuca RuntimeException, jeśli zdarzenia nie da się zakodować do JSON. hasEvent(string $eventId): bool sprawdza outbox; count(): int raportuje niedostarczone zdarzenia.
JobTerminalEvent i JobTerminalStatus
Dział zatytułowany „JobTerminalEvent i JobTerminalStatus”public function __construct( public string $eventId, public string $runId, public int $sourceOffset, public string $jobId, public string $idempotencyKeyValue, public JobTerminalStatus $status, public ?CommitReceipt $receipt, public ?string $errorCode, public ?string $errorMessage, public int $attempts, public DateTimeImmutable $occurredAt,) {}eventId jest deterministyczny — runId:sourceOffset:idempotencyKey:status — co właśnie umożliwia deduplikację w outbox. toArray() serializuje zdarzenie do transportu; nie niesie żadnych bajtów PDF. JobTerminalStatus to enum łańcuchowy: Committed (committed), DeadLettered (dead_lettered), Skipped (skipped).
ProcessingSummary
Dział zatytułowany „ProcessingSummary”Niezmienne liczniki zwracane przez process(): runId, sourceRead, fastForwardedByCheckpoint, skippedByIdempotency, windows, renderBatchCalls, renderRetries, commitReceipts, deadLettered, checkpointSaves oraz finalCommittedOffset (końcowy najwyższy znacznik stanu końcowego).
Przykład kodu — Szybki start
Dział zatytułowany „Przykład kodu — Szybki start”Zatwierdzanie obiektu do magazynu dokładnie raz w izolacji. Wewnątrzpamięciowy NullObjectStorageClient zastępuje Twój adapter S3/GCS; semantyka, którą obserwujesz, jest tą, którą działający adapter musi zachować.
<?php
declare(strict_types=1);
require __DIR__ . '/vendor/autoload.php';
use NextPDF\Enterprise\Stream\Storage\NullObjectStorageClient;use NextPDF\Enterprise\Stream\Storage\ObjectStorageCommitter;use NextPDF\Manifest\OutputObjectKey;use NextPDF\Pro\Stream\Exception\OutputCommitConflictException;use Symfony\Component\Clock\NativeClock;
$committer = new ObjectStorageCommitter( client: new NullObjectStorageClient(), // swap in your S3/GCS adapter scheme: 's3', clock: new NativeClock(),);
$target = new OutputObjectKey(scheme: 's3', container: 'invoices', key: '2026/07/inv-1001.pdf');$bytes = '%PDF-1.7 example-rendered-bytes';$sha = hash('sha256', $bytes);
$first = $committer->commit('inv-1001', $target, $bytes, $sha);$replay = $committer->commit('inv-1001', $target, $bytes, $sha); // crash-resume replay
printf("first : reuse=%s, %d bytes\n", var_export($first->idempotentReuse, true), $first->bytesWritten);printf("replay: reuse=%s\n", var_export($replay->idempotentReuse, true));
try { $divergent = '%PDF-1.7 different-bytes'; $committer->commit('inv-1001', $target, $divergent, hash('sha256', $divergent));} catch (OutputCommitConflictException $conflict) { echo 'conflict: ' . $conflict->specCode() . "\n"; // no silent clobber}Oczekiwane wyjście:
first : reuse=false, 31 bytesreplay: reuse=trueconflict: SPEC-COMMIT-409Przykład kodu — Produkcja
Dział zatytułowany „Przykład kodu — Produkcja”Pełny przebieg bezpieczny po awarii: trwałe magazyny Pro, committer magazynu obiektów, trwały outbox oraz branding rozwiązany z licencji. Ponowne uruchomienie tego samego runId po awarii przewija do przodu i zbiega się.
<?php
declare(strict_types=1);
require __DIR__ . '/vendor/autoload.php';
use NextPDF\Enterprise\Licensing\EntitlementEvaluator;use NextPDF\Enterprise\Stream\DocumentJobStreamProcessor;use NextPDF\Enterprise\Stream\Exception\StreamProcessorException;use NextPDF\Enterprise\Stream\FilesystemOutboxEmitter;use NextPDF\Enterprise\Stream\Storage\ObjectStorageCommitter;use NextPDF\Enterprise\Stream\StreamProcessorConfig;use NextPDF\Manifest\Render\SingleDocumentRenderer;use NextPDF\Manifest\RenderManifest;use NextPDF\Pro\Stream\Checkpoint\FilesystemCheckpointStore;use NextPDF\Pro\Stream\Dedup\FilesystemIdempotencyStore;use NextPDF\Pro\Stream\Engine\InProcessRenderEngine;use NextPDF\Pro\Stream\Retry\FilesystemDeadLetterStore;use NextPDF\Pro\Stream\Retry\RetryPolicy;use NextPDF\Pro\Stream\State\InMemoryKeyedStateStore;use Symfony\Component\Clock\NativeClock;
// Production requires a host-supplied adapter whose putIfAbsent() is a TRUE// atomic conditional create (S3 If-None-Match: *, GCS ifGenerationMatch: 0)// and whose shaOf() reads durable object state. NullObjectStorageClient is// for the quick start only - it keeps nothing across processes.$s3Client = new \Aws\S3\S3Client(['region' => 'eu-central-1', 'version' => 'latest']);$objectClient = new \Acme\Storage\S3ObjectStorageClient($s3Client); // implements ObjectStorageClientInterface
$stateDir = '/var/lib/nextpdf/stream';foreach (['checkpoints', 'idempotency', 'dead-letters', 'outbox'] as $sub) { if (!is_dir($stateDir . '/' . $sub)) { mkdir($stateDir . '/' . $sub, 0770, true); }}
// One manifest per JSONL line; the generator never materialises the batch.$manifests = (static function (string $path): Generator { $handle = fopen($path, 'rb'); if ($handle === false) { throw new RuntimeException('Cannot open job stream: ' . $path); } try { while (($line = fgets($handle)) !== false) { if (trim($line) !== '') { yield RenderManifest::fromJson(trim($line)); } } } finally { fclose($handle); }})('/var/spool/nextpdf/jobs.jsonl');
$license = null; // your licensing bootstrap yields a LicenseKey; null = evaluation branding
$processor = DocumentJobStreamProcessor::withBrandingFromLicense( engine: new InProcessRenderEngine(SingleDocumentRenderer::standalone()), // For a live bucket, implement ObjectStorageClientInterface over your S3/GCS SDK. committer: new ObjectStorageCommitter($objectClient, 's3', new NativeClock()), idempotency: new FilesystemIdempotencyStore($stateDir . '/idempotency'), checkpoints: new FilesystemCheckpointStore($stateDir . '/checkpoints'), state: new InMemoryKeyedStateStore(), // recomputable; durability not required here deadLetters: new FilesystemDeadLetterStore($stateDir . '/dead-letters'), retryPolicy: new RetryPolicy(maxAttempts: 3, baseDelayMs: 200, maxDelayMs: 5_000), clock: new NativeClock(), entitlementEvaluator: new EntitlementEvaluator(), license: $license, emitter: new FilesystemOutboxEmitter($stateDir . '/outbox'),);
$config = new StreamProcessorConfig( runId: 'nightly-invoices-2026-07-03', windowSize: 32, checkpointIntervalJobs: 100, crashSafe: true,);
try { $summary = $processor->process($manifests, $config);} catch (StreamProcessorException $e) { // Ambiguous commit or a non-durable collaborator: the finalized prefix is // checkpointed. Re-run the SAME runId; the committer converges by digest. fwrite(STDERR, 'Run aborted for safe resume: ' . $e->getMessage() . PHP_EOL); exit(1);}
printf( "run %s: read=%d committed=%d dedup-skipped=%d dead-lettered=%d checkpoints=%d final-offset=%d\n", $summary->runId, $summary->sourceRead, $summary->commitReceipts, $summary->skippedByIdempotency, $summary->deadLettered, $summary->checkpointSaves, $summary->finalCommittedOffset,);Przykładowe wyjście (liczniki zależą od Twojego strumienia zadań):
run nightly-invoices-2026-07-03: read=1200 committed=1187 dedup-skipped=13 dead-lettered=0 checkpoints=12 final-offset=1200Przypadki brzegowe i pułapki
Dział zatytułowany „Przypadki brzegowe i pułapki”- Pojedynczy zapisujący na
runIdto Twoja odpowiedzialność. Magazyn checkpointu nie ma dzierżawy ani compare-and-swap. Dwaj współbieżni zapisujący na jednymrunIdsą poza kontraktem; wymuszaj wyłączność w swoim harmonogramie. crashSafe: truezawodzi szybko przy nietrwałych współpracownikach. Committer oraz magazyny checkpointu, idempotencji i dead-letter muszą wszystkie implementować znacznikDurableCapability, w przeciwnym razieprocess()rzucaStreamProcessorExceptionwskazując winowajców. Kluczowany magazyn stanu jest celowo wyłączony: utracony kluczowany stan jest przeliczany od checkpointu w przód.windowSizemusi zmieścić się w silniku. Okno większe niżmaxBatchSize()rzucaInvalidArgumentException, zanim rozpocznie się jakakolwiek praca.- Niejednoznaczne zatwierdzenie przerywa; konflikt nie.
SPEC-COMMIT-409to deterministyczny końcowy konflikt: element trafia do dead-letter, a przebieg trwa dalej. Każda inna porażka zatwierdzenia jest niejednoznaczna: sfinalizowany prefiks jest zapisany w checkpointcie, a przebieg rzuca dla bezpiecznego wznowienia. - Porażki renderowania nigdy nie przerywają przebiegu. Wynik
Failedposzczególnego elementu, wyczerpany budżet ponawiania lub niezabrandowalne bajty ewaluacyjne — wszystkie kierują ten element do dead-letter i kontynuują. - Zduplikowane wartości
jobIdsą bezpieczne; zduplikowana praca jest kluczowana poidempotencyKey. Wyniki korelują z elementami po unikatowym offsecie źródłowym, nigdy pojobId. Zduplikowany klucz idempotencji jest rozpoznawany nawet w tym samym interwale bariery, przed ponownym renderowaniem. - Trwałość emitera decyduje o semantyce zdarzeń. Zwykły emiter zwrotny jest tylko obserwatorem i co najwyżej raz przy awarii.
FilesystemOutboxEmitterczyni outbox trwałym i deduplikowanym po kluczu; dostawa przekaźnika jest wtedy co najmniej raz, a dokładnie raz po stronie pobocznej wymaga deduplikacji konsumenta poeventId. Jego katalog (jak każdego magazynu na systemie plików) musi istnieć wcześniej, w przeciwnym razie konstruktor rzucaInvalidArgumentException. - Zdarzenia pominięte są domyślnie wyłączone. Ustaw
emitSkippedCompletions: true, aby emitować także końcowe zdarzenieSkippeddla elementów odciętych przez deduplikację.
Uwagi bezpieczeństwa
Dział zatytułowany „Uwagi bezpieczeństwa”- Klucze wyjściowe zawodzą bezpiecznie.
commit()ponownie potwierdza, że klucz celu jest bezpieczny względem kontenera: brak przejścia.., brak absolutnej ucieczki, brak bajtu null, brak osadzonego schematu stream-wrapper i brak dwukropka (który zamyka wektor alternatywnego strumienia danych NTFS). Niebezpieczne klucze rzucają przed jakimkolwiek wywołaniem magazynu. - Integralność jest ponownie weryfikowana na granicy. Committer przelicza sha-256 na rzeczywistych bajtach i odrzuca niezgodność z
CommitIntegrityException, więc uszkodzone przekazanie nie może wylądować po cichu. - Zdarzenia nie niosą treści dokumentu.
JobTerminalEventi wiersze outbox zawierają tylko identyfikatory, sygnatury, znaczniki czasu i łańcuchy błędów. Komunikaty błędów mogą powtarzać diagnostykę silnika; oczyść je oraz każdy schematjobIdidentyfikujący najemcę, zanim wyślesz pliki outbox do zewnętrznych ujść. - Wyjście ewaluacyjne nigdy nie jest publikowane bez brandingu. Gdy branding jest wymagany i nie da się go zastosować, element trafia do dead-letter zamiast zostać zatwierdzony.
- Dokładnie raz między zapisującymi jest tak silne, jak Twój adapter. Jeśli
putIfAbsent()nie jest prawdziwym atomowym zapisem warunkowym, gwarancja degraduje się do semantyki pojedynczego zapisującego. Poświadczenia magazynu obiektów i polityka bucketu to sprawy hosta; moduł nigdy nimi nie zarządza.
Zgodność
Dział zatytułowany „Zgodność”Żaden opublikowany standard nie definiuje zachowania tego modułu. Gwarancje dokładnie raz, checkpointu i outbox na tej stronie są inżynierskimi kontraktami API NextPDF Enterprise, podanymi tu jako zewnętrznie obserwowalne zachowanie — nie są zgodnością z żadnym standardem ani certyfikacją wobec niego. Wewnętrzne użycie SHA-256 jako sygnatury integralności jest podobnie instalacją, nie deklaracją zgodności. Jak wszędzie w NextPDF: wsparcie to nie zgodność, a zgodność to nie certyfikacja. NextPDF nie posiada certyfikacji i żadnej nie udziela; to, czy wdrożenie zbudowane na tym module spełnia Twoje obowiązki regulacyjne lub kontraktowe, jest rozstrzygnięciem Twoich asesorów.
Kontrakt zachowania
Dział zatytułowany „Kontrakt zachowania”Zdarzenia końcowe nie są częścią transakcji checkpointu: emisja odbywa się po checkpoint.save(), więc outbox trzyma trwale każde wyemitowane zdarzenie, ale nie jest kompletnym rejestrem przez awarie. Zatwierdzone obiekty pozostają źródłem prawdy.
- Każdy offset źródłowy na poziomie
finalCommittedOffsetlub poniżej osiągnął dokładnie jeden stan końcowy:Committed,SkippedlubDeadLettered. - Elementy są finalizowane w kolejności offsetów źródłowych; kolejność bariery to zatwierdzenie, zapis checkpointu, opróżnienie znaczników idempotencji, następnie emisja zdarzeń.
- Ponowne uruchomienie przebiegu z tym samym
runIdnigdy nie publikuje podwójnie: offsety z checkpointu przewijają do przodu, a bajtowo identyczne ponowne zatwierdzenia to operacje puste z porównaniem sygnatury zidempotentReuse: true. - Świeży obiekt jest tworzony wyłącznie atomowym tworzeniem warunkowym; rozbieżne bajty pod zajętym kluczem bez
overwriteto deterministyczny dead-letterSPEC-COMMIT-409, nigdy nadpisanie. - Niejednoznaczne zatwierdzenie zapisuje w checkpointcie sfinalizowany prefiks i przerywa z
StreamProcessorException; nieudany offset nie jest przesuwany. - Przebieg
crashSafeodmawia nietrwałych współpracowników committera, checkpointu, idempotencji lub dead-letter przed odczytaniem jakiegokolwiek wejścia. - Identyfikatory zdarzeń są czystą funkcją przebiegu, offsetu, klucza idempotencji i statusu, więc trwały outbox trzyma co najwyżej jeden wiersz na zdarzenie.
Rozwiązanie awaryjne Core
Dział zatytułowany „Rozwiązanie awaryjne Core”NextPDF Core renderuje jeden dokument naraz przez writer i kontrakt render-manifest — zobacz Writer. Sam Core nie ma trwałych strumieni zadań, wznowienia z checkpointu, deduplikacji idempotencji, zatwierdzania do magazynu obiektów ani outbox zdarzeń końcowych. NextPDF Pro dodaje trwały, współbieżny silnik renderujący oraz jednohostowe magazyny na systemie plików (Stream w Pro). Połowa międzyhostowa — ten procesor, committer magazynu obiektów i trwały outbox — wymaga NextPDF Enterprise.
Granica publikacji
Dział zatytułowany „Granica publikacji”Ta strona dokumentuje wyłącznie zewnętrznie obserwowalne zachowanie i wspieraną publiczną powierzchnię API. Wewnętrzne ścieżki przestrzeni nazw, klasy pomocnicze, tabele mechanizmów, nazwy plików runbooków i prefiksy zgłoszeń są poza zakresem.
Zobacz także
Dział zatytułowany „Zobacz także”- Stream (Pro) — połowa wewnątrzprocesowa: silnik renderujący, executory i lokalne trwałe magazyny.
- Stream — Głęboka referencja — referencja na poziomie kontraktu dla współdzielonych interfejsów Stream.
- Output Pipeline (Enterprise) — orkiestracja wsadowa nad manifestami pipeline.
- Wersja próbna i branding — jak branding ewaluacyjny jest rozwiązywany i stosowany.
- Generowanie dokumentów o dużej objętości — scenariusz, dla którego istnieje ten moduł.
- Eksploatacja NextPDF w produkcji — postawa wdrożeniowa dla długo działających workerów.