İçeriğe geç
getnextpdf.com

Enterprise sürüm

Stream: belge-işi işleme

NextPDF\Enterprise\Stream\DocumentJobStreamProcessor, bir render manifest akışını kalıcı, hesap verebilir sonuçlara dönüştürür. Bir iterable<RenderManifest>’i generator olarak tüketir, sınırlı pencereleri Pro render motoru üzerinden işler ve her işi kaynak sırasına göre sonlandırır. Her iş tam olarak bir terminal durumda biter: çıktı commit’lenmiş, halihazırda commit’lenmiş olarak tanınmış veya dead-letter’a alınmış. İlerleme checkpoint’lenir, böylece çöken bir çalıştırma hiçbir şeyi yeniden yayınlamadan devam eder.

Stream hikayesi iki sürüm arasında bölünür ve bu bölünme kasıtlıdır. Pro, kalıcı, eşzamanlı render motorunu ve yerel, tek-host dosya sistemi depolarını — süreç-içi yarıyı — sağlar. Enterprise, bu belge-işi akış işleyicisini ve host sınırlarını aşan parçaları sağlar: nesne-depolama commit’çisi (ObjectStorageCommitter) ve terminal olayların kalıcı outbox’ı (FilesystemOutboxEmitter). Pro Stream sayfası aynı sınırı kendi tarafından belirtir.

Bu yetenek NextPDF Enterprise (nextpdf/enterprise) ile gelir ve bir Enterprise katmanı lisans zarfıyla etkinleşir. Bu hak olmadan bir dağıtım, yeteneğin sınıflarını yüklemez. Sürümleri karşılaştırın ve bir lisans edinin.

Terminal window
composer require nextpdf/enterprise

Bu sayfadaki sınıflar NextPDF\Enterprise\Stream ve NextPDF\Enterprise\Stream\Storage altında yer alır. NextPDF\Pro\Stream içindeki dondurulmuş Pro sözleşmelerini tüketirler — motor, commit’çi, checkpoint, idempotency, yeniden deneme ve dead-letter arayüzleri.

İşleyicinin görevi render değil, teslim semantiğidir. Manifest akışını, motorun batch boyutundan büyük olmayan kaynak-ofset pencerelerine gruplar. Her pencere RenderEngineInterface::renderBatch() üzerinden, öğe başına zaman aşımlarının sınırlı, deterministik yeniden denemesiyle işlenir. Ardından her öğe kaynak-ofset sırasına göre bir terminal sonuca sonlandırılır.

Tam-bir-kez sınırı öğe başınadır ve koordinasyonda değil, commit’çide sabitlenir. Kalıcı ilerleme, 1-tabanlı bir ofset üst-su-işaretidir (high-watermark): checkpoint’te veya altındaki her ofset bir terminal sonuca ulaşmıştır. Bariyer sırası sabittir: baytları commit’le, su işaretini ilerlet, checkpoint’i kaydet, tamponlanmış idempotency işaretlerini boşalt, sonra terminal olayları yay. Commit ile checkpoint arasındaki bir çökme, devam sırasında idempotent olarak yeniden commit’ler, çünkü commit’çi digest’leri karşılaştırır. Checkpoint sonrası bir çökme ofseti hızlı-ileri sararak geçer, böylece hiçbir şey iki kez yayınlanmaz.

ObjectStorageCommitter, minimal ObjectStorageClientInterface aracılığıyla bir nesne deposuna karşı Pro OutputCommitterInterface’i uygular. Hedefin container’ı bucket, key’i ise nesne anahtarıdır. Aynı baytları yeniden commit’lemek, digest-karşılaştırmalı bir no-op’tur. overwrite olmadan farklı baytlar SPEC-COMMIT-409 çakışmasını doğurur. Yeni bir nesne yalnızca atomik putIfAbsent() koşullu yazmasıyla oluşturulur; bu yarışı kaybetmek, sınırlı bir yeniden-oku-ve-çöz döngüsünü tetikler. Bu nedenle yazıcılar arası tam-bir-kez, adaptörünüzün putIfAbsent()’i gerçek bir koşullu yazma olduğu ölçüde geçerlidir — S3’te If-None-Match: *, GCS’te ifGenerationMatch: 0. Bu döngü, arayüzü ve bellek-içi NullObjectStorageClient’i içerir; canlı S3/GCS adaptörü host tarafından sağlanır.

Terminal olaylar, aşağı akış sistemleri için döngüyü kapatır. Checkpoint bariyerinden sonra işleyici, sonlandırılan her iş için bir JobTerminalEvent yaymaya çalışır — tanımlayıcılar, durum, makbuz, hata ayrıntıları, deneme sayısı ve asla herhangi bir PDF baytı değil. Düz bir callback yayıcısıyla yayma en-fazla-bir-kez’dir: bir checkpoint’ten sonraki olaylar, çökme-sonrası devamda atlanabilir. FilesystemOutboxEmitter, emit() çalıştığında her olayı kalıcı yapar: her olay, deterministik eventId’sinin bir hash’iyle adlandırılan tek bir atomik JSON dosyasıdır, böylece bir devamdan sonra yeniden yayma idempotenttir, bir röle en-az-bir-kez teslim eder ve tüketiciler eventId üzerinden yinelenmeyi giderir. Her iki durumda da bir sınır kalır: yayma, checkpoint bariyerinden sonra gerçekleşir, dolayısıyla checkpoint.save() ile emit() arasındaki bir çökme, o öğenin terminal olayını devamda atlar. Eksiksiz bir olay defteri gerektiren aşağı akış sistemleri, yalnızca outbox’a karşı değil, commit’lenmiş nesnelere karşı (depo, doğruluk kaynağıdır) mutabakat yapmalıdır.

Lisanslama, çıktı yoluna bağlanmıştır. withBrandingFromLicense() fabrikası, çalıştırma başına bir kez lisanstan bir değerlendirme-markalama stratejisi çözer. Ücretli bir lisans, bir kimlik dönüşümüne çözülür. Bir değerlendirme veya eksik lisans, commit’lenen her belgeyi filigranlar ve markalanamayan bir belge dead-letter’a alınır — işleyici asla markasız değerlendirme baytlarını commit’lemez.

Yük taşıyan karar, tam-bir-kez’in dağıtık kilitlere veya konsensüse değil, commit’çinin digest-karşılaştırmalı, koşullu olarak oluşturulmuş nesnesine dayanmasıdır. Nesne deposunun koşullu yazması, tasarımın gerektirdiği tek atomik ilkeldir ve diğer her şeyin başarısız olup toparlanmasına izin verilir. Render motorunun yan-etkisiz kalması gerekmesinin, anahtarlı durumun ve çalıştırma-içi dedup önbelleklerinin yeniden hesaplanabilir hızlandırmalar olarak ele alınmasının ve belirsiz bir commit’in tahmin yürütmek yerine çalıştırmayı iptal etmesinin nedeni budur: devam yolu, aynı digest karşılaştırması aracılığıyla yakınsar. runId başına tek-yazıcı’nın, zorunlu kılınan bir kira yerine belirtilmiş bir gereksinim olmasının nedeni de budur — checkpoint deposu bilinçli olarak basit kalır ve commit katmanı güvenlik ağı olarak kalır.

Tasarım arka planı: Yüksek hacimli belge üretimi.

Host’lar fabrika aracılığıyla oluşturmalıdır, böylece lisans-markalama kontrolü asla bağlanmamış bırakılmaz:

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, Symfony\Component\Clock\ClockInterface’tir (yeniden deneme geri çekilmesi onun üzerinden uyur). null bir lisans, hataya-kapalı olarak değerlendirme markalamasına çözülür.

Tek giriş noktası bir çalıştırmayı işler ve sayaçlarını döndürür:

public function process(iterable $manifests, StreamProcessorConfig $config): ProcessingSummary

Şununla fırlatır veya başarısız olur: çökme-güvenliği önkoşulları başarısız olduğunda (kalıcı-olmayan iş ortaklarıyla bir crashSafe çalıştırması) veya bir commit belirsiz olduğunda NextPDF\Enterprise\Stream\Exception\StreamProcessorException; windowSize, motorun maxBatchSize()’ini aştığında InvalidArgumentException.

public function __construct(
public string $runId,
int $windowSize = 32,
int $checkpointIntervalJobs = 100,
public bool $crashSafe = true,
public bool $emitSkippedCompletions = false,
)

Şununla fırlatır veya başarısız olur: windowSize veya checkpointIntervalJobs 1’in altında olduğunda InvalidArgumentException. $runId, checkpoint devamını anahtarlayan kararlı, tek-yazıcı çalıştırma tanımlayıcısıdır.

public function __construct(
private ObjectStorageClientInterface $client,
private string $scheme,
private ClockInterface $clock,
) {}

$scheme, bu commit’çinin hizmet verdiği hedef şemayı adlandırır (örneğin s3 veya gcs); buradaki $clock, Psr\Clock\ClockInterface’tir.

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

Şununla fırlatır veya başarısız olur: şema uyuşmazlığında UnsupportedTargetException; hedef anahtar container-göreli-güvenli olmadığında RenderManifestException; bildirilen sha-256 baytlarla eşleşmediğinde CommitIntegrityException; overwrite olmadan farklı baytlarda OutputCommitConflictException (SPEC-COMMIT-409); eşzamanlı mutasyon altında oluşturma yarışı 5 denemeden sonra yakınsayamadığında RuntimeException.

Canlı bir S3/GCS entegrasyonunun uyguladığı minimal adaptör yüzeyi:

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(), gerçek bir atomik koşullu oluşturma olmalıdır (S3’te If-None-Match: *, GCS’te ifGenerationMatch: 0) ve yalnızca bu çağrı nesneyi yazdığında true döndürür. put(), yalnızca manifest overwrite istediğinde kullanılan koşulsuz üzerine yazmadır.

JobCompletionEmitterInterface ve FilesystemOutboxEmitter

“JobCompletionEmitterInterface ve FilesystemOutboxEmitter” başlıklı bölüm
public function emit(JobTerminalEvent $event): void;

Yayıcı, işleyicide isteğe bağlıdır. Olaylar yalnızca bir öğe kalıcı olarak sonlandırıldıktan sonra tetiklenir. FilesystemOutboxEmitter, gönderilen kalıcı uygulamadır:

public function __construct(string $directory, ?AtomicFileWriter $writer = null)

Şununla fırlatır veya başarısız olur: dizin mevcut olmadığında InvalidArgumentException; bir olay JSON-kodlanamıyorsa emit() RuntimeException fırlatır. hasEvent(string $eventId): bool outbox’ı denetler; count(): int teslim edilmemiş olayları raporlar.

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 deterministiktir — runId:sourceOffset:idempotencyKey:status — ki bu, outbox dedup’unu mümkün kılan şeydir. toArray(), olayı taşıma için serileştirir; hiçbir PDF baytı taşımaz. JobTerminalStatus, bir string enum’dur: Committed (committed), DeadLettered (dead_lettered), Skipped (skipped).

process() tarafından döndürülen değiştirilemez sayaçlar: runId, sourceRead, fastForwardedByCheckpoint, skippedByIdempotency, windows, renderBatchCalls, renderRetries, commitReceipts, deadLettered, checkpointSaves ve finalCommittedOffset (nihai terminal üst-su-işareti).

İzole olarak tam-bir-kez nesne-depolama commit’i. Bellek-içi NullObjectStorageClient, S3/GCS adaptörünüzün yerini tutar; gözlemlediğiniz semantik, canlı bir adaptörün korumak zorunda olduğu semantiktir.

stream-object-commit-quickstart.php
<?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
}

Beklenen çıktı:

first : reuse=false, 31 bytes
replay: reuse=true
conflict: SPEC-COMMIT-409

Tam çökmeye dayanıklı bir çalıştırma: kalıcı Pro depoları, nesne-depolama commit’çisi, kalıcı bir outbox ve lisansla-çözülmüş markalama. Bir çökmeden sonra aynı runId’i yeniden çalıştırmak hızlı-ileri sarar ve yakınsar.

stream-run-production.php
<?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,
);

Örnek çıktı (sayaçlar iş akışınıza bağlıdır):

run nightly-invoices-2026-07-03: read=1200 committed=1187 dedup-skipped=13 dead-lettered=0 checkpoints=12 final-offset=1200
  • runId başına tek-yazıcı sizin sorumluluğunuzdadır. Checkpoint deposunda kira veya compare-and-swap yoktur. Bir runId üzerindeki iki eşzamanlı yazıcı sözleşmenin dışındadır; münhasırlığı zamanlayıcınızda zorunlu kılın.
  • crashSafe: true, kalıcı-olmayan iş ortaklarında hızlı başarısız olur. Commit’çi, checkpoint, idempotency ve dead-letter depolarının tümü DurableCapability işaretleyicisini uygulamalıdır, aksi halde process(), ihlalcileri adlandıran bir StreamProcessorException fırlatır. Anahtarlı durum deposu bilinçli olarak muaftır: kaybedilen anahtarlı durum, checkpoint’ten ileriye doğru yeniden hesaplanır.
  • windowSize motora sığmalıdır. maxBatchSize()’ten büyük bir pencere, herhangi bir iş başlamadan önce InvalidArgumentException fırlatır.
  • Belirsiz bir commit iptal eder; bir çakışma etmez. SPEC-COMMIT-409, deterministik bir terminal çakışmasıdır: öğe dead-letter’a alınır ve çalıştırma devam eder. Diğer herhangi bir commit başarısızlığı belirsizdir: sonlandırılmış önek checkpoint’lenir ve çalıştırma, güvenli devam için fırlatır.
  • Render başarısızlıkları çalıştırmayı asla iptal etmez. Öğe başına bir Failed sonucu, tükenmiş bir yeniden deneme bütçesi veya markalanamayan değerlendirme baytları, o öğeyi dead-letter’a alır ve devam eder.
  • Yinelenen jobId değerleri güvenlidir; yinelenen iş idempotencyKey üzerinden anahtarlanır. Sonuçlar, öğelerle asla jobId ile değil, benzersiz kaynak ofsetiyle ilişkilendirilir. Yinelenen bir idempotency anahtarı, aynı bariyer aralığı içinde bile, yeniden render’dan önce tanınır.
  • Yayıcı kalıcılığı olay semantiğini belirler. Düz bir callback yayıcısı yalnızca gözlemcidir ve bir çökme boyunca en-fazla-bir-kez’dir. FilesystemOutboxEmitter, outbox’ı kalıcı ve dedup-anahtarlı yapar; röle teslimi o zaman en-az-bir-kez’dir ve aşağı akış tam-bir-kez’i, eventId üzerinde tüketici dedup’unu gerektirir. Dizini (her dosya sistemi deposununki gibi) önceden var olmalıdır, aksi halde yapıcı InvalidArgumentException fırlatır.
  • Atlanan olaylar varsayılan olarak kapalıdır. Dedup ile kısa devre yapılan öğeler için de bir Skipped terminal olayı yaymak üzere emitSkippedCompletions: true ayarlayın.
  • Çıktı anahtarları hataya-kapalıdır. commit(), hedef anahtarın container-göreli-güvenli olduğunu yeniden doğrular: .. geçişi yok, mutlak kaçış yok, null bayt yok, gömülü stream-wrapper şeması yok ve iki nokta üst üste yok (ki bu NTFS alternate-data-stream vektörünü kapatır). Güvenli olmayan anahtarlar, herhangi bir depolama çağrısından önce fırlatır.
  • Bütünlük sınırda yeniden doğrulanır. Commit’çi, gerçek baytlar üzerinden sha-256’yı yeniden hesaplar ve bir uyuşmazlığı CommitIntegrityException ile reddeder, böylece bozuk bir devir sessizce yerleşemez.
  • Olaylar belge içeriği taşımaz. JobTerminalEvent ve outbox satırları yalnızca tanımlayıcıları, digest’leri, zaman damgalarını ve hata dizelerini tutar. Hata mesajları motor tanılamalarını yansıtabilir; outbox dosyalarını üçüncü taraf hedeflere göndermeden önce bunları ve kiracıyı-tanımlayan herhangi bir jobId şemasını temizleyin.
  • Değerlendirme çıktısı asla markasız yayınlanmaz. Markalama gerektiğinde ve uygulanamadığında, öğe commit’lenmek yerine dead-letter’a alınır.
  • Yazıcılar arası tam-bir-kez yalnızca adaptörünüz kadar güçlüdür. putIfAbsent() gerçek bir atomik koşullu yazma değilse, garanti tek-yazıcı semantiğine düşer. Nesne-deposu kimlik bilgileri ve bucket politikası host konularıdır; modül bunları asla yönetmez.

Yayımlanmış hiçbir standart bu modülün davranışını tanımlamaz. Bu sayfadaki tam-bir-kez, checkpoint ve outbox garantileri, NextPDF Enterprise API’sinin mühendislik sözleşmeleridir ve burada dışarıdan gözlemlenebilir davranış olarak belirtilmiştir — bunlar herhangi bir standarda uygunluk veya ona karşı sertifikasyon değildir. Bütünlük digest’i olarak SHA-256’nın dahili kullanımı da benzer şekilde tesisattır, bir uyumluluk iddiası değildir. NextPDF’in her yerinde olduğu gibi: destek uygunluk değildir ve uygunluk sertifikasyon değildir. NextPDF hiçbir sertifikasyona sahip değildir ve hiçbirini vermez; bu modül üzerine inşa edilmiş bir dağıtımın düzenleyici veya sözleşmesel yükümlülüklerinizi karşılayıp karşılamadığı, değerlendiricilerinizin vereceği bir karardır.

Terminal olaylar checkpoint işleminin parçası değildir: yayma, checkpoint.save()’ten sonra çalışır, dolayısıyla outbox her yayılan olayı kalıcı olarak tutar ancak çökmeler boyunca eksiksiz bir defter değildir. Commit’lenmiş nesneler doğruluk kaynağı olarak kalır.

  • finalCommittedOffset’te veya altındaki her kaynak ofseti tam olarak bir terminal sonuca ulaşmıştır: Committed, Skipped veya DeadLettered.
  • Öğeler kaynak-ofset sırasına göre sonlandırılır; bariyer sırası commit, checkpoint kaydı, idempotency-işareti boşaltma, sonra olay yaymadır.
  • Aynı runId ile bir çalıştırmayı yeniden çalıştırmak asla çift yayınlamaz: checkpoint’lenmiş ofsetler hızlı-ileri sarar ve bayt-eşdeğer yeniden commit’ler, idempotentReuse: true ile digest-karşılaştırmalı no-op’lardır.
  • Yeni bir nesne yalnızca atomik koşullu oluşturma aracılığıyla oluşturulur; dolu bir anahtarda overwrite olmadan farklı baytlar, asla bir üzerine yazma değil, deterministik bir SPEC-COMMIT-409 dead-letter’dır.
  • Belirsiz bir commit, sonlandırılmış öneki checkpoint’ler ve StreamProcessorException ile iptal eder; başarısız ofset ilerletilmez.
  • Bir crashSafe çalıştırması, herhangi bir girdiyi okumadan önce kalıcı-olmayan commit’çi, checkpoint, idempotency veya dead-letter iş ortaklarını reddeder.
  • Olay kimlikleri; çalıştırma, ofset, idempotency anahtarı ve durumun saf bir fonksiyonudur, dolayısıyla kalıcı bir outbox, olay başına en fazla bir satır tutar.

NextPDF Core, writer ve render-manifest sözleşmesi aracılığıyla her seferinde bir belge render eder — bkz. Writer. Core tek başına kalıcı iş akışlarına, checkpoint devamına, idempotency dedup’una, nesne-depolama commit’ine ve terminal-olay outbox’ına sahip değildir. NextPDF Pro, kalıcı, eşzamanlı render motorunu ve tek-host dosya sistemi depolarını ekler (Pro’da Stream). Host’lar arası yarı — bu işleyici, nesne-depolama commit’çisi ve kalıcı outbox — NextPDF Enterprise gerektirir.

Bu sayfa yalnızca dışarıdan gözlemlenebilir davranışı ve desteklenen genel API yüzeyini belgeler. Dahili ad alanı yolları, yardımcı sınıflar, mekanizma tabloları, runbook dosya adları ve bilet önekleri kapsam dışıdır.