콘텐츠로 이동
getnextpdf.com

Pro 에디션

Stream — 심층 참조

이 페이지는 개요 페이지를 넘어 NextPDF\Pro\Stream 서브시스템의 공개 계약, 클래스, 메서드, 그리고 실패 모드를 문서화합니다. 아래의 모든 타입은 문서화된 Pro 공개 표면의 일부입니다.

이 기능은 NextPDF Pro(nextpdf/pro)에 포함되며 Pro 등급 라이선스 봉투로 활성화됩니다. 그 자격이 없는 배포는 이 기능의 클래스를 로드하지 않습니다. 에디션 비교 및 라이선스 받기.

기능별 라이선스 플래그는 없습니다. 코드는 Pro 에디션에 포함되어 배포됩니다. 워커 수, 배치 크기, 재시도 예산, 그리고 저장소 백엔드는 런타임 매개변수입니다.

NextPDF\Pro\Stream\Engine\RenderEngineInterface는 처리량 엔진과 문서-작업 스트림 프로세서 사이의 계약입니다. 엔진이 이를 구현하고(동시성, 워커 풀 수명 주기, 백프레셔, 제한된 메모리를 소유), 스트림 프로세서가 이를 소비합니다(키 상태, 중복 제거, 재시도, 체크포인트, 그리고 정확히 한 번 커밋을 소유). 엔진은 바이트와 sha-256을 반환할 뿐, 커밋된 위치는 절대 반환하지 않습니다 — 그 부작용 없음이 바로 프로세서가 정확히 한 번 스테이징하고, 커밋하고, 체크포인트하게 해 줍니다.

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는 크기가 최대 maxBatchSize()list<RenderManifest>입니다. $variablesByJobId는 작업 id를 array<string, scalar> 템플릿 변수에 매핑합니다. 매니페스트당 실패는 항목별 Failed/Timeout 결과이며 배치를 절대 중단시키지 않습니다.

NextPDF\Pro\Stream\Engine\InProcessRenderEngine

섹션 제목: “NextPDF\Pro\Stream\Engine\InProcessRenderEngine”

동기식 단일 프로세스 기준선입니다. Core의 SingleDocumentRenderer를 통해 렌더링하기 전에 각 매니페스트를 RenderManifestValidator를 통해 실패-차단으로 검증합니다(16 MiB 인라인 페이로드 상한, 적합성/서명 허용 목록, sha-256 콘텐츠 해시 형식, BCP-47 로케일 문법). 차단성 검증 오류는 EngineRenderResult::failed(jobId, 'SPEC-MANIFEST-INVALID', ...)로 단락됩니다. 렌더 예외는 'SPEC-RENDER-EXCEPTION'이 됩니다. 생성자: __construct(SingleDocumentRenderer $renderer, int $maxBatchSize = 64, ?RenderManifestValidator $validator = null)maxBatchSize < 1InvalidArgumentException을 던집니다. isAvailable()은 항상 true입니다.

NextPDF\Pro\Stream\Engine\ConcurrentRenderEngine

섹션 제목: “NextPDF\Pro\Stream\Engine\ConcurrentRenderEngine”

final readonly, __construct(RenderUnitExecutorInterface $executor). 각 매니페스트를 인덱싱된 RenderUnit으로 감싸고, 익스큐터를 통해 실행한 다음, 완료를 인덱스로 다시 정렬하여 출력을 순차 렌더와 바이트 단위로 동일하게 만듭니다. [0, count) 밖의 완료 인덱스는 RenderEngineException::unknownUnit()을 던집니다. 반복된 인덱스는 duplicateResult()를 던집니다. 누락된 인덱스는 missingResult()를 던집니다. maxBatchSize()isAvailable()은 익스큐터에 위임합니다.

NextPDF\Pro\Stream\Engine\RenderUnitExecutorInterface

섹션 제목: “NextPDF\Pro\Stream\Engine\RenderUnitExecutorInterface”
public function execute(array $units): iterable; // iterable<CompletedRenderUnit>, any order
public function maxBatchSize(): int;
public function isAvailable(): bool;

구현은 완료를 임의의 순서로 산출할 수 있습니다. ConcurrentRenderEngine이 인덱스로 순서를 복원합니다.

NextPDF\Pro\Stream\Engine\InlineRenderUnitExecutor

섹션 제목: “NextPDF\Pro\Stream\Engine\InlineRenderUnitExecutor”

final readonly, __construct(RenderEngineInterface $inner). 각 유닛을 내부 엔진을 통해 순서대로 렌더링합니다 — 병렬 익스큐터가 바이트 단위로 일치시켜야 하는 결정적 정확성 기준입니다. 시간, 프로세스, 스레드, 또는 무작위성이 없습니다.

NextPDF\Pro\Stream\Engine\ProcessPoolRenderUnitExecutor

섹션 제목: “NextPDF\Pro\Stream\Engine\ProcessPoolRenderUnitExecutor”

final readonly. 배치를 최대 maxWorkers개의 php 워커 하위 프로세스(각각 하나의 청크)에 분산하여 병렬로 렌더링합니다. 출력은 인라인 기준선과 바이트 단위로 동일합니다. 생성자:

__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
)

견고성 계약:

  • 교착 없음, Windows 안전. 유닛 페이로드와 결과는 파이프가 아니라 임시 파일을 통해 이동합니다. 부모는 proc_get_status()를 폴링하고 워커가 종료한 후에만 파이프를 EOF까지 비웁니다. 따라서 워커가 부모를 막을 수 없습니다.
  • 제한된 대기. timeoutSeconds는 전체 병렬 렌더를 제한합니다. 만료 시 여전히 실행 중인 모든 워커가 종료되고 RenderEngineException이 던져집니다.
  • 리소스 위생. finally가 파이프를 닫고, 살아남은 워커에 대해 제한된 종료-및-회수 시도를 수행하며(우아한 종료 → 강제 종료 → 회수. 제한된 유예 시간 내에 멈추는 것이 관찰되지 않은 자식은 무한 차단을 무릅쓰는 대신 포기됨), 모든 경로에서 모든 임시 파일을 언링크합니다.
  • 신뢰된 상관관계. 각 워커는 정확히 자신에게 할당된 인덱스 집합을 반환해야 합니다(누락, 중복, 또는 외래 인덱스 없음). 렌더링된 각 결과의 바이트는 다시 해싱되어 워커가 보고한 sha-256과 대조되며, rendered/failed 외의 어떤 상태든 하드 실패합니다. 매니페스트당 렌더 실패는 유닛별 Failed 결과입니다. 인프라적 결함(0이 아닌 종료, 읽을 수 없거나 손상된 출력, 타임아웃)만이 익스큐터를 하드 실패시킵니다.

isAvailable()은 autoload 파일과 워커 스크립트가 모두 존재하기를 요구합니다. 양수가 아닌 경계 또는 음수 타임아웃은 InvalidArgumentException을 던집니다.

final readonlyint<0, max> $index, RenderManifest $manifest, array<string, scalar> $variables. 상관관계는 작업 id가 아니라 index로 이루어집니다(작업 id는 배치 내에서 고유성이 보장되지 않습니다).

NextPDF\Pro\Stream\Engine\CompletedRenderUnit

섹션 제목: “NextPDF\Pro\Stream\Engine\CompletedRenderUnit”

final readonlyint $index(신뢰되지 않으며 엔진이 검증함), EngineRenderResult $result.

NextPDF\Pro\Stream\Engine\EngineRenderResult

섹션 제목: “NextPDF\Pro\Stream\Engine\EngineRenderResult”

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()는 상태를 보고합니다. 렌더링된 결과는 바이트와 다이제스트를 지니며, 커밋된 위치는 절대 지니지 않습니다.

NextPDF\Pro\Stream\Engine\EngineRenderStatus

섹션 제목: “NextPDF\Pro\Stream\Engine\EngineRenderStatus”

문자열 기반 열거형: Rendered, Failed, Timeout. isRetryable()Timeout에 대해서만 true이므로, 호출자는 오류를 다시 검사하지 않고도 타임아웃을 일시적인 것으로 분류합니다.

NextPDF\Pro\Stream\Commit\OutputCommitterInterface

섹션 제목: “NextPDF\Pro\Stream\Commit\OutputCommitterInterface”
public function commit(
string $jobId,
OutputObjectKey $target,
string $bytes,
string $sha256,
bool $overwrite = false,
): CommitReceipt;

정확히 한 번 발행: 원자적, 멱등적(바이트 단위로 동일한 재커밋은 쓰기를 수행하지 않고 idempotentReuse = trueCommitReceipt를 반환합니다 — 원본이 아니라 새 영수증입니다. 그 committedAt은 현재 시계입니다), 조용한 덮어쓰기 없음, 그리고 무결성 검사(커미터가 다이제스트를 다시 계산함). 실패 모드: CommitIntegrityException(선언된 sha-256이 바이트와 일치하지 않음), OutputCommitConflictException(overwrite = false로 점유된 키에 상이한 바이트), UnsupportedTargetException(지원되지 않는 대상 스킴).

NextPDF\Pro\Stream\Commit\LocalFilesystemCommitter

섹션 제목: “NextPDF\Pro\Stream\Commit\LocalFilesystemCommitter”

final readonly, OutputCommitterInterface, DurableCapability를 구현합니다. __construct(string $rootDirectory, ?AtomicFileWriter $writer = null, ?ClockInterface $clock = null). file 스킴만 서비스합니다. 모든 대상을 하나의 구성된 루트 아래에서 해석하고 원자적 writer를 통해 씁니다(O_EXCL 임시 → fsync → 동일 볼륨 이름 변경). 전체 임계 구역(부모 디렉터리 생성 포함)은 출력 키스페이스 밖에 유지되는 루트별 잠금 파일에 대한 배타적 flock 아래에서 실행되며, 잠금을 열거나 획득할 수 없으면 커밋은 실패-차단됩니다. 심볼릭 링크된 최종 구성 요소와 콜론을 포함하는 어떤 키든(NTFS 대체 데이터 스트림 벡터) 거부합니다. 동일한 키에 대한 호스트 간 동시 정확히 한 번은 내구성 있는 Enterprise 커미터를 요구합니다. 시스템 임시 디렉터리이거나 그것을 포함하는 루트는 InvalidArgumentException을 던집니다.

final readonlyjobId, OutputObjectKey $target, sha256, int<0, max> $bytesWritten, bool $idempotentReuse, DateTimeImmutable $committedAt. toArray() / fromArray()는 완전히 왕복 가능합니다(대상은 손실성 URI가 아니라 구조화되어 있습니다). fromArray()는 엄격하며 누락되거나 잘못된 필드에서 InvalidArgumentException을 던집니다.

NextPDF\Pro\Stream\Checkpoint\CheckpointStoreInterface

섹션 제목: “NextPDF\Pro\Stream\Checkpoint\CheckpointStoreInterface”

load(string $runId): ?RunCheckpointsave(RunCheckpoint $checkpoint): void(내구성 있고 원자적 — 리더는 절반만 쓰인 체크포인트를 절대 보지 않습니다).

NextPDF\Pro\Stream\Checkpoint\RunCheckpoint

섹션 제목: “NextPDF\Pro\Stream\Checkpoint\RunCheckpoint”

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()이 이를 직렬화합니다. fromArray()는 비어 있지 않은 실행 id와 유효한 updated_at을 요구하고, 호환되지 않는(1.x가 아닌) schema_version을 거부하며, 모든 깊이에서 JSON 직렬화 불가능한 값을 제거하여 키 상태를 정규화하므로 복구된 상태는 항상 재직렬화 가능합니다. 복구 시 프로세서는 committedOffset을 지나 빨리 감기를 수행하고 키 상태를 복원합니다. 마지막 배리어 이후 변경된 상태는 오류가 아니라 앞으로 다시 계산됩니다. 내구성 있는 정확히 한 번이 커미터의 다이제스트 중복 제거에서 비롯되기 때문입니다.

NextPDF\Pro\Stream\Checkpoint\FilesystemCheckpointStore

섹션 제목: “NextPDF\Pro\Stream\Checkpoint\FilesystemCheckpointStore”

final readonly, CheckpointStoreInterface, DurableCapability를 구현합니다. 실행당 하나의 JSON 파일을 원자적으로 씁니다. 실행 id는 [A-Za-z0-9._-]+와 일치해야 하며 ..를 포함하지 않아야 합니다. 존재하지 않는 디렉터리는 InvalidArgumentException을 던집니다.

NextPDF\Pro\Stream\Dedup\IdempotencyStoreInterface

섹션 제목: “NextPDF\Pro\Stream\Dedup\IdempotencyStoreInterface”

isCommitted(IdempotencyKey $key): bool, markCommitted(IdempotencyKey $key, CommitReceipt $receipt): void, receiptFor(IdempotencyKey $key): ?CommitReceipt. 재생을 렌더링하기 전에 단락하는 빠른 경로입니다. 커미터의 다이제스트 비교가 내구성 있는 보장으로 남아 있으므로, 레코드가 손실되더라도 최악의 경우 커미터가 중복 제거하는 재렌더링만 낭비합니다.

  • InMemoryIdempotencyStore — 단일 실행 / 테스트 범위(크래시 시 손실됨).
  • FilesystemIdempotencyStoreDurableCapability. 커밋된 키당 하나의 원자적 JSON 파일(직렬화된 영수증)이며, 키 값의 해시로 이름이 지정됩니다. 마킹은 멱등적입니다. 동시 재마킹은 하나의 파일에서 무해하게 경합합니다. 존재하지 않는 디렉터리는 InvalidArgumentException을 던집니다.

final readonlypositive-int $maxAttempts, positive-int $baseDelayMs, positive-int $maxDelayMs. __construct(int $maxAttempts = 3, int $baseDelayMs = 100, int $maxDelayMs = 30000)이며 불변식은 maxAttempts >= 11 <= baseDelayMs <= maxDelayMs <= 7 days입니다(그렇지 않으면 InvalidArgumentException). 팩토리 default()none()(단일 시도). shouldRetry(int $attempt): bool. delayMsForAttempt(int $attempt): int<0, max>maxDelayMs로 제한된 결정적 지수 백오프 baseDelayMs * 2^(attempt-1)입니다(내장 지터 없음. 호출 지점에서 적용하십시오).

NextPDF\Pro\Stream\Retry\DeadLetterStoreInterface

섹션 제목: “NextPDF\Pro\Stream\Retry\DeadLetterStoreInterface”

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이고, 그렇지 않으면 멱등성 키 값입니다. fromArray()failed_at을 ATOM으로 엄격하게 파싱하여(상대적 또는 ATOM이 아닌 표현을 거부함) 직렬화/역직렬화가 대칭으로 유지되도록 합니다.

  • InMemoryDeadLetterStore — 단일 실행 / 테스트 범위.
  • FilesystemDeadLetterStoreDurableCapability. 레코드당 하나의 원자적 JSON 파일이며, 중복 제거 키의 SHA-256 해시로 이름이 지정되므로(….dlq.json), 재개 시 동일한 항목을 다시 추가하는 것은 멱등적입니다. all()은 레코드를 결정적(정렬된) 순서로 읽고 손상된 레코드를 던져서 드러냅니다. count()는 유효성 검사가 아니라 저렴한 파일 카운트입니다.

NextPDF\Pro\Stream\State\KeyedStateStoreInterface

섹션 제목: “NextPDF\Pro\Stream\State\KeyedStateStoreInterface”

has, get, put, remove, clear, 그리고 체크포인트 경계를 위한 snapshot(): arrayrestore(array $snapshot): void. 값은 JSON 직렬화 가능해야 합니다. 기본 렌더-앤-커밋 워크로드의 경우 키 상태는 사용되지 않습니다. 이는 집계/윈도잉 확장을 위해 존재합니다. InMemoryKeyedStateStore는 단일 실행 구현입니다. 복구 시 이를 잃는 것은 기본 워크로드에 대해 시맨틱하게 무연산입니다. 정확히 한 번이 커미터의 다이제스트 중복 제거에서 비롯되기 때문입니다.

final readonly, __construct(string $tenantField = 'tenant_id', string $documentField = 'document_id'). keyFor(RenderManifest $manifest): non-empty-string은 매니페스트 메타데이터에서 파티션 키를 rawurlencode(tenant):rawurlencode(document)로 도출하며(이 인코딩은 ("a:b","c")("a","b:c")와 충돌하는 것을 막습니다), 두 필드 중 하나가 없으면 작업 id로 폴백합니다 — 따라서 모든 매니페스트는 안정적이고 비어 있지 않은 키로 해석됩니다.

NextPDF\Pro\Stream\DurableCapability는 프로세스 재시작에도 상태가 살아남는 모든 저장소/커미터를 위한 마커 인터페이스입니다. 크래시에 안전한 실행은 모든 협력자가 이를 구현하기를 요구하므로, 인메모리 저장소가 유지할 수 없는 정확히 한 번을 약속하는 대신 빠르게 실패합니다.

모든 서브시스템 예외는 NextPDF\Pro\Stream\Exception\StreamException(Throwable을 확장)을 구현하므로, 호출자는 catch (StreamException)을 일관되게 사용할 수 있습니다.

  • RenderEngineException(RuntimeException) — 익스큐터가 배치 계약을 위반함(알 수 없거나 중복되거나 누락된 유닛. 워커 결함. 타임아웃).
  • CommitIntegrityException(RuntimeException) — 선언된 sha-256이 페이로드와 일치하지 않음. 스펙 코드 SPEC-COMMIT-422.
  • OutputCommitConflictException(RuntimeException) — 덮어쓰기가 비활성화된 채로 점유된 키에 상이한 바이트. 스펙 코드 SPEC-COMMIT-409(specCode()를 통해 노출됨).
  • UnsupportedTargetException(InvalidArgumentException) — 커미터가 서비스할 수 없는 대상 스킴.

엔진은 Core 매니페스트 모델에 대해 매니페스트를 검증하고 결정적 바이트와 sha-256 다이제스트를 생성합니다. 커미터는 원자적이고 무결성 검사를 거친 정확히 한 번 쓰기를 강제합니다. 이 모듈은 sha-256 콘텐츠 다이제스트를 넘어서는 암호 연산을 수행하지 않으며 FIPS 특화 동작을 정의하지 않습니다.

  • renderBatch()는 매니페스트당 실패에서 절대 중단하지 않습니다. 각 EngineRenderResult를 검사하십시오.
  • ProcessPoolRenderUnitExecutor는 엄격하게 인덱스로 상관시키고 워커 바이트를 다시 해싱합니다. 버그가 있는 워커는 출력을 손상시키는 대신 하드 실패합니다.
  • LocalFilesystemCommitter는 단일 호스트입니다. 호스트 간 정확히 한 번은 내구성 있는 Enterprise 커미터가 필요합니다.
  • 크래시에 안전한 실행은 인메모리 변형이 아니라 전체에 걸쳐 DurableCapability(파일 시스템) 저장소를 사용해야 합니다.

이 페이지는 외부에서 관찰 가능한 동작과 지원되는 공개 API 표면만을 문서화합니다. 내부 네임스페이스 경로, 헬퍼 클래스, 메커니즘 테이블, 런북 파일명, 그리고 티켓 접두사는 범위를 벗어납니다.