Enterprise 에디션
미터링 — 심층 참조
한눈에 보기
섹션 제목: “한눈에 보기”NextPDF\Enterprise\Metering 네임스페이스는 청구 가시성과 감사를 위한 오케스트레이션 수준 사용량 미터링을 제공합니다. 공개 표면은 여섯 개의 심볼입니다: MeterCollector, MeterEntry, MeteringReporter, MeteringBackendInterface, PrometheusMeteringBackend, PrometheusPushgatewayException. 컬렉터는 불변 항목을 메모리에 버퍼링하고 배치 단위로 플러시합니다. 리포터는 각 배치를 백엔드별 재시도와 실패 격리를 갖추어 하나 이상의 백엔드로 팬아웃합니다. 미터링은 최선 노력이며 비치명적입니다. 미터링 백엔드 장애는 관측성을 저하시킬 뿐 결코 문서 처리를 저하시키지 않습니다. 이 스트림은 할당량 시행의 권위적 출처가 아닙니다. 워크플로 수준 가이드는 미터링을 참조하십시오.
가용성 및 라이선싱
섹션 제목: “가용성 및 라이선싱”이 기능은 NextPDF Enterprise(nextpdf/enterprise)에 포함되며 Enterprise 등급 라이선스 엔벨로프로 활성화됩니다. 해당 권한이 없는 배포는 이 기능의 클래스를 로드하지 않습니다. 에디션 비교 및 라이선스 받기.
미터링은 기본 Enterprise 기능으로, Enterprise 패키지가 설치되면 사용할 수 있습니다. 별도의 기능별 플래그가 없습니다. NextPDF Core(Apache-2.0)와 NextPDF Pro에는 컬렉터, 리포터, 백엔드 표면이 없습니다. 이 계약은 nextpdf/enterprise에만 포함됩니다.
공개 API 표면
섹션 제목: “공개 API 표면”| 심볼 | 매개변수 | 기본 동작 | 반환 | 던지거나 실패하는 경우 | 참고 |
|---|---|---|---|---|---|
MeterCollector::__construct | MeteringReporter $reporter, int $bufferSize = 100 | 빈 인메모리 버퍼를 가진 컬렉터를 생성합니다 | 새 MeterCollector | 던지지 않음 | $bufferSize는 문서상 positive-int입니다 |
MeterCollector::record | string $operation, int $count, string $tenantId, string $licenseId, int $pagesProcessed = 0, float $durationMs = 0.0, array $metadata = [] | 현재 시간이 찍힌 불변 MeterEntry 하나를 추가합니다. 버퍼가 $bufferSize에 도달하면 자동 플러시합니다 | void | 던지지 않음. 자동 플러시는 리포터에 위임되며, 리포터는 결코 던지지 않습니다 | 타임스탬프는 기록 시점에 취득됩니다 |
MeterCollector::flush | — | 버퍼링된 모든 항목을 리포터에 전달합니다. 빈 버퍼는 무연산입니다 | void | 던지지 않음. 백엔드 장애는 리포터가 흡수합니다 | 전달 전에 버퍼가 교체됩니다. 재진입 안전 |
MeterCollector::bufferCount | — | 버퍼링된 항목 수를 반환합니다 | int<0, max> | 던지지 않음 | 진단 및 배압(back-pressure) 결정용 |
MeterCollector::registerShutdownFlush | — | register_shutdown_function을 통해 flush()를 등록합니다 | void | 던지지 않음 | PHP-FPM 배포에서 부트스트랩 시 한 번 호출하십시오 |
MeterEntry::__construct | string $operation, int $count, DateTimeImmutable $timestamp, string $tenantId, string $licenseId, int $pagesProcessed = 0, float $durationMs = 0.0, array $metadata = [] | 제공된 값을 그대로 저장합니다 | 새 MeterEntry | 선언된 @throws 없음. strict_types 하에서 인수 타입이 불일치하면 PHP가 TypeError를 발생시킵니다 | final readonly. 승격된 여덟 개 프로퍼티는 모두 public입니다 |
MeteringReporter::__construct | list<MeteringBackendInterface> $backends, int $maxRetries = 2, LoggerInterface $logger = new NullLogger() | 백엔드 목록을 검증하고 저장합니다 | 새 MeteringReporter | $backends가 비어 있으면 InvalidArgumentException | $maxRetries는 백엔드별 총 전달 시도 횟수를 셉니다 |
MeteringReporter::report | list<MeterEntry> $entries | 배치를 백엔드별 재시도와 함께 모든 백엔드에 독립적으로 전달합니다 | void | 던지지 않음. 소진된 시도는 오류 수준으로 기록되고 그 백엔드의 배치를 드롭합니다 | 빈 목록은 무연산입니다 |
MeteringBackendInterface::report | list<MeterEntry> $entries | 배치를 백엔드에 전달합니다 | void | 백엔드에 도달할 수 없으면 RuntimeException | 구현은 반드시 멱등이어야 합니다(timestamp + operation + tenantId로 중복 제거) |
MeteringBackendInterface::isHealthy | — | 도달 가능성 프로브 | bool | 선언된 @throws 없음 | 진단 전용. 리포터는 이에 게이트하지 않습니다 |
MeteringBackendInterface::backendName | — | 진단용 백엔드 이름 | non-empty-string | 선언된 @throws 없음 | 예: "prometheus", "billing-api", "null" |
PrometheusMeteringBackend::__construct | ClientInterface $httpClient, RequestFactoryInterface $requestFactory, StreamFactoryInterface $streamFactory, string $pushgatewayUrl, string $jobName = 'nextpdf_metering' | Pushgateway 푸시 대상을 구성합니다 | 새 PrometheusMeteringBackend | 던지지 않음 | PSR-18 클라이언트와 PSR-17 팩토리가 주입됩니다 |
PrometheusMeteringBackend::report | list<MeterEntry> $entries | 배치를 연산 및 테넌트 시리즈별로 집계하고 노출 텍스트를 <pushgatewayUrl>/metrics/job/<jobName>에 POST합니다 | void | 2xx가 아닌 상태 또는 PSR-18 전송 실패 시 PrometheusPushgatewayException | 빈 목록은 무연산입니다 |
PrometheusMeteringBackend::isHealthy | — | Pushgateway 상태 엔드포인트를 프로브합니다. HTTP 200에서만 true | bool | 던지지 않음. 어떤 실패든 false를 반환합니다 | 읽기 전용 GET 프로브 |
PrometheusMeteringBackend::backendName | — | "prometheus"를 반환합니다 | non-empty-string | 던지지 않음 | 상수 |
PrometheusPushgatewayException | — | 실패한 Pushgateway 전달을 알립니다 | — | 던져지는 대상입니다 | final. RuntimeException을 확장합니다 |
public function __construct( private readonly MeteringReporter $reporter, private readonly int $bufferSize = 100,) {}
public function record( string $operation, int $count, string $tenantId, string $licenseId, int $pagesProcessed = 0, float $durationMs = 0.0, array $metadata = [],): void
public function flush(): void
public function bufferCount(): int
public function registerShutdownFlush(): voidpublic function __construct( public string $operation, public int $count, public DateTimeImmutable $timestamp, public string $tenantId, public string $licenseId, public int $pagesProcessed = 0, public float $durationMs = 0.0, public array $metadata = [],) {}public function report(array $entries): void;
public function isHealthy(): bool;
public function backendName(): string;public function __construct( array $backends, private readonly int $maxRetries = 2, private readonly LoggerInterface $logger = new NullLogger(),)
public function report(array $entries): voidpublic function __construct( private readonly ClientInterface $httpClient, private readonly RequestFactoryInterface $requestFactory, private readonly StreamFactoryInterface $streamFactory, private readonly string $pushgatewayUrl, private readonly string $jobName = self::DEFAULT_JOB_NAME,) {}final class PrometheusPushgatewayException extends RuntimeException {}MeterEntry public readonly 프로퍼티
| 프로퍼티 | 타입 | 의미 |
|---|---|---|
$operation | non-empty-string | 연산 유형, 예: "parse", "compress", "embed", "rag_query" |
$count | positive-int | 소비된 단위 수 |
$timestamp | DateTimeImmutable | 연산이 발생한 시점. 컬렉터가 기록 시점에 찍습니다 |
$tenantId | non-empty-string | 테넌트 식별자 |
$licenseId | non-empty-string | 라이선스 식별자 |
$pagesProcessed | int<0, max> | 처리된 PDF 페이지 수. PDF가 아닌 연산은 0 |
$durationMs | float | 밀리초 단위 연산 지속 시간 |
$metadata | array<string, mixed> | 자유 형식의 연산별 메타데이터 |
동작 계약
섹션 제목: “동작 계약”MeterCollector::record()는 불변MeterEntry하나를 생성하고 현재 시간을 찍어 인메모리 버퍼에 추가합니다. 버퍼가$bufferSize항목에 도달하면 컬렉터가 자동 플러시합니다.flush()는 멱등이며 재진입 안전합니다. 빈 버퍼는 무연산입니다. 배치가 리포터에 전달되기 전에 버퍼가 교체되므로, 재진입 플러시가 이중 전송할 수 없습니다.MeteringReporter는 빈 백엔드 목록으로의 생성을 거부합니다. 그InvalidArgumentException은 컬렉터/리포터 경로의 유일한 예외입니다.MeteringReporter::report()는 각 배치를 모든 백엔드에 독립적으로 전달합니다. 실패하는 백엔드가 다른 백엔드의 동일 배치 수신을 결코 막지 않습니다.$maxRetries는 백엔드별 총 전달 시도 횟수를 셉니다. 기본값2는 최초 시도 한 번에 재시도 한 번을 의미합니다. 모든 실패한 시도는 백엔드 이름, 시도 번호, 항목 수와 함께 경고를 기록합니다.- 백엔드에 대한 마지막 시도가 실패하면, 리포터는 드롭된 항목 수와 함께 오류 수준으로 추가 기록한 뒤 다음으로 넘어갑니다.
report()에서 결코 던지지 않으므로, 호출자는 정상 반환으로부터 전달을 추론해서는 안 됩니다. - 백엔드는 반드시 멱등이어야 합니다. 인터페이스 계약은 타임스탬프, 연산, 테넌트 식별자를 키로 하는 중복 제거를 요구합니다. 리포터 자체는 중복 제거하지 않습니다.
PrometheusMeteringBackend::report()는 배치를 연산별, 테넌트별 시리즈로 집계하고 Prometheus 텍스트 노출을 Content-Typetext/plain; version=0.0.4로<pushgatewayUrl>/metrics/job/<jobName>에 POST합니다. 기본 잡 이름은nextpdf_metering입니다.- 푸시된 페이로드는 세 개의 카운터 —
nextpdf_operations_total,nextpdf_pages_processed_total,nextpdf_operation_duration_ms_total— 를 담으며, 각각 연산과 테넌트로 레이블됩니다. - 이 미터링 스트림은 비권위적입니다. 할당량 시행과 권위적 컴퓨트 미터링은 이 버퍼가 아니라 배포의 별도 권위적 사용량 수치를 소비합니다. 오케스트레이션 미터링의 누락은 관측성 누락이지 청구 정확성 누락이 아닙니다.
엣지 케이스 및 실패 모드
섹션 제목: “엣지 케이스 및 실패 모드”- 중복되거나 재생된 배치. 백엔드 멱등성에 의해 흡수됩니다. 리포터는 중복 제거하지 않습니다. 정확히 한 번 전달에 의존하지 마십시오.
- 소진된 재시도. 그 백엔드의 배치는 드롭되고 오류 수준으로 기록됩니다.
report()또는flush()의 정상 반환은 결코 전달을 의미하지 않습니다. - 플러시 전 프로세스 종료. 버퍼는 메모리 전용입니다. 크래시, 또는 등록된 셧다운 핸들러 없는 종료는 버퍼링된 항목을 잃습니다.
- 워커 모델 불일치. PHP-FPM 배포는 부트스트랩에서
registerShutdownFlush()를 한 번 호출하여 나머지가 요청 종료 시 플러시되도록 합니다. 장시간 실행 워커(Octane, Symfony 워커, 큐 워커)는 대신 주기적 타이머로 플러시해야 합니다. 그렇지 않으면 워커 프로세스가 종료될 때까지 항목이 누적됩니다. 1미만의$bufferSize. 문서화된positive-int계약을 위반합니다. 관찰 가능한 결과는 매record()호출마다 플러시가 발생하는 것입니다.- 민감한 메타데이터.
$metadata는 자유 형식이며 민감한 연산 컨텍스트를 담을 수 있습니다. 저장, 보존, 접근 제어는 백엔드 운영자의 책임입니다. - Pushgateway 전달 실패. 2xx가 아닌 응답은 HTTP 상태와 응답 본문을 담은
PrometheusPushgatewayException을 발생시킵니다. PSR-18 전송 실패는 동일한 예외 유형으로 래핑됩니다. 리포터의 재시도-격리 루프가 둘 다 흡수합니다. - 상태 프로브.
PrometheusMeteringBackend::isHealthy()는<pushgatewayUrl>/-/healthy에 대해 GET을 발행하고 HTTP 200에서만true를 반환합니다. 어떤 전송 오류든false를 반환하며, 프로브는 결코 던지지 않습니다. - 적대적 레이블 값. 연산 또는 테넌트 값 내의 백슬래시, 큰따옴표, 줄바꿈 문자는 방출 시 이스케이프되므로, 레이블 값이 추가 노출 라인을 주입하거나 레이블 블록을 손상시킬 수 없습니다.
- FIPS 모드. 컬렉터와 리포터는 어떠한 암호화 연산도 수행하지 않으며 FIPS별 동작이 없습니다. 전송 중 서명하거나 암호화하는 백엔드는 그 호스트 암호 제공자의 FIPS 태세를 상속합니다.
적합성
섹션 제목: “적합성”어떤 외부 표준도 인프로세스 컬렉터, 리포터, 백엔드 계약을 관장하지 않습니다. 인용할 규범 사양이 없으므로 이 페이지는 설계상 RAG 인용을 담지 않습니다. Prometheus 백엔드는 Prometheus 텍스트 노출 형식을 방출하고 Content-Type text/plain; version=0.0.4로 푸시합니다. 그 형식은 ISO나 IETF 표준이 아니라 생태계 관례이며, 이 주장은 제품 소스에 근거합니다. NextPDF는 이 표면에 대해 어떠한 적합성 또는 인증 주장도 하지 않습니다.
개발 노트
섹션 제목: “개발 노트”- 모든 클래스는
strict_types=1을 선언하며final입니다.MeterEntry는 승격된 public 프로퍼티를 가진final readonly입니다. 인수 타입이 불일치하면 호출자에서 PHPTypeError가 발생합니다. - 모듈 클래스는 패키지
@since주석2.1.0을 담습니다.PrometheusPushgatewayException은@since3.2.0을 담습니다. - 리포터의 로거는 기본적으로 PSR-3
NullLogger입니다. 프로덕션에서는 실제 로거를 주입하십시오. 그렇지 않으면 드롭된 배치가 흔적을 남기지 않습니다. - 단위 테스트: 가짜
MeteringBackendInterface를 구현하고MeterEntry값을 직접 생성하십시오. Prometheus 백엔드는 PSR-18/PSR-17 추상화를 취하므로, 모의 HTTP 클라이언트가 오프라인에서 전체 푸시 경로를 수행합니다. - 권장 경계 테스트:
$bufferSize에서 정확히 버퍼, 재진입 플러시, 빈 버퍼 플러시, 한 백엔드가 실패하는 동안 두 번째가 성공, 그리고 재시도 소진 로깅. - 백엔드 구현자는 전달 실패 시
RuntimeException(또는 서브클래스)을 던지고, 리포터가 이를 흡수합니다. 상류에 추가 재시도를 더하기 전에 멱등성 요구사항을 준수하십시오.
공개 경계
섹션 제목: “공개 경계”이 페이지는 외부에서 관찰 가능한 동작과 지원되는 공개 API 표면만을 문서화합니다. 내부 네임스페이스 경로, 헬퍼 클래스, 메커니즘 표, 런북 파일명, 티켓 접두사는 범위 밖입니다.
함께 보기
섹션 제목: “함께 보기”- 미터링 — NextPDF Enterprise — 기능 페이지: 워크플로, 구성, 실제 배포 예제.
- 빌링 — 심층 참조 — 요금제 등급, 초과분 의미론, 경보 사다리.
- SaaS — 심층 참조 — 멀티테넌트 오케스트레이션 표면.
- 라이선싱 — 심층 참조 — Enterprise 기능을 활성화하는 라이선스 엔벨로프.