콘텐츠로 이동

프로덕션 환경에서의 NextPDF Symfony

이 번들은 장기 실행 PHP 런타임을 염두에 두고 설계되었습니다. 문서는 공유되지 않으며, 글꼴 레지스트리는 워밍업 후 잠기고, 이미지 캐시는 요청 간에 재설정됩니다. 대용량 PDF는 스트리밍하고, 무거운 작업은 Messenger 워커로 오프로드하세요.

워커 안전성을 고려한 서비스 수명 주기

섹션 제목: “워커 안전성을 고려한 서비스 수명 주기”

장기 실행 런타임은 요청 사이에도 컨테이너를 유지하므로, 요청별 상태가 누출되어서는 안 됩니다. FrankenPHP, RoadRunner, Messenger 워커 모두 이 방식으로 동작합니다. 번들의 services.php는 아래 수명 주기를 정의하며, 해당 동작은 서비스 정의에서 검증되었습니다.

  • Document — 공유되지 않음. nextpdf.document(및 PdfDocumentInterface / Document 별칭)은 호출할 때마다 새 인스턴스로 반환됩니다. PSR-11에서는 컨테이너가 동일한 id에 대해 get()을 호출할 때마다 다른 값을 반환할 수 있도록 허용합니다(PSR-11 §1.1.2). 요청마다 문서를 새로 가져오세요. 요청 사이에 문서를 절대 보유하지 마세요.
  • FontRegistry — 공유되며 잠김. 이 레지스트리는 프로세스 수명 동안 유지되는 싱글턴입니다. warmup() 이후(preload_fonts가 비어 있지 않은 경우) 컴파일러 패스가 lock()을 호출합니다. 이 잠금은 런타임 변경을 막아 요청 간 글꼴 상태 오염을 방지합니다.
  • ImageRegistry — 공유되며 요청마다 재설정됨. 제한된 LRU(least-recently-used) 이미지 캐시는 공유되지만 kernel.reset 태그가 지정되고 메서드가 reset으로 설정되어 있으므로, Symfony는 kernel.reset을 준수하는 런타임에서 요청 간에 이 캐시를 비웁니다.
  • EInvoice 컨트랙트 — 공유되지 않음. Premium 구현이 있는 경우, embedder, validator, profile, schematron 서비스는 공유되지 않도록 등록됩니다. 각 호출의 파서 컨텍스트는 요청 사이에 절대 누출되지 않습니다.

구성을 담고 있지만 상태를 갖지 않는 공유 PdfFactory를 주입하고 요청마다 create()를 호출하세요.

public function __construct(private readonly PdfFactory $pdf) {}
public function action(): Response
{
$doc = $this->pdf->create(); // fresh, disposable
// ... build ...
return PdfResponse::inline($doc, 'document.pdf');
}

그 자체가 공유되고 요청 사이에 유지되는 서비스에는 Document 또는 nextpdf.document를 주입하지 마세요. 대신 요청 범위의 메서드 내부에서 이를 가져오세요.

PdfResponse::streamDownload()streamInline()StreamedResponse를 반환합니다. 이 콜백은 PDF 본문을 64 KB 청크 단위로 내보내고 각 청크 뒤에 플러시합니다. 이렇게 하면 대용량 문서에서 응답 버퍼를 제한할 수 있습니다. 아래 두 가지 절충 사항은 모두 PdfResponse에 대해 검증되었습니다.

  • 스트리밍 변형은 의도적으로 Content-Length를 생략합니다(응답 객체가 본문 크기를 사전에 알지 못하기 때문입니다). 다운로드 진행률 표시줄과 일부 프록시는 알려진 길이를 선호합니다. 문서가 메모리에 올릴 만큼 충분히 작고 콘텐츠 길이가 필요한 경우에는 스트리밍하지 않는 download() 또는 inline()을 사용하세요.
  • 스트리밍 변형은 버퍼링 변형과 동일한 보안 헤더와 동일한 Cache-Control: private, max-age=0, must-revalidate를 내보냅니다.

수 메가바이트 규모의 보고서와 일괄 내보내기에는 스트리밍을 선택하세요. 작고 지연 시간에 민감한 응답에는 버퍼링 변형을 선택하세요.

요청에 빠르게 응답해야 하거나 렌더링이 CPU를 많이 사용하는 경우, 생성 작업을 Messenger로 오프로드하세요.

  1. 각 문서 유형에 대해 PdfBuilderInterface를 구현하세요.
  2. 빌더를 container.service_locator에 등록한 뒤 이를 GeneratePdfHandler$builderLocator로 연결하세요.
  3. 메시지 GeneratePdfMessage를 영속적 전송 수단으로 라우팅하세요.
  4. 수명을 제한해 워커를 실행하세요.

서드파티 종속성에서 누수된 할당이 한없이 늘어나지 않도록 워커를 재활용하세요.

Terminal window
php bin/console messenger:consume async \
--limit=200 \
--memory-limit=256M \
--time-limit=3600

번들의 messenger.timeoutmessenger.retries 구성 키는 의도한 메시지별 타임아웃과 재시도 예산을 기록합니다. Symfony의 재시도 전략과 워커 플래그를 통해 그에 맞는 동작을 적용하세요.

GeneratePdfMessage는 생성 시점에 출력 경로를 검증합니다. 그런 다음 GeneratePdfHandler는 디스크에 쓰기 전, 실행 시점에 이를 다시 검증합니다. 이 2단계 검사는 비동기 작업에서 중요합니다. 메시지는 디스패치와 소비 사이에 큐에 머물 수 있으므로, 핸들러는 큐에 들어간 경로를 무조건 신뢰하지 않습니다. 심층 방어 차원에서 워커의 파일 시스템 권한을 의도한 출력 디렉터리로 제한하세요.

해당 FontRegistryImageRegistry 서비스는 선택적 Psr\Log\LoggerInterface를 받습니다(nullOnInvalid()로 바인딩됨). 애플리케이션이 로거를 제공하면 레지스트리는 이를 통해 진단 정보를 내보낼 수 있습니다. 로거는 PSR-3 로거 컨트랙트(PSR-3)에 따른 선택적이며 교체 가능한 협력자입니다. 요청 수준의 가시성을 확보하려면 애플리케이션 코드에서 PdfFactory::create()와 Messenger 핸들러 주변에 로그를 남기세요. 인시던트를 분류할 때는 messenger:consume -vv를 사용하세요.

  • 애플리케이션 composer.json에서 단일 nextpdf/core 메이저 버전을 고정하세요 (번들은 ^3.0 || ^5.2를 허용합니다).
  • 배포된 PHP 이미지에서 ext-mbstringext-zlib이 활성화되어 있는지 확인하세요(그렇지 않으면 번들이 부팅 시 즉시 실패합니다).
  • 레지스트리가 첫 요청 때가 아니라 부팅 시점에 워밍업되고 잠기도록, 문서에서 사용하는 글꼴에 대해 preload_fonts를 미리 채워 두세요.
  • 배포 간에 캐시된 아티팩트에 의존한다면, cache_path를 쓰기 가능하고 영속적인 위치로 지정하세요. 그렇지 않으면 기본값인 %kernel.cache_dir%로 충분합니다.
  • 컴파일된 컨테이너(선택적 확장 프로브 포함)가 트래픽을 받기 전에 빌드되도록, 배포 중에 php bin/console cache:warmup를 실행하세요.
  • 프로덕션 비동기 작업에는 영속적인 Messenger 전송 수단(sync 아님)을 사용하고, --limit / --memory-limit / --time-limit로 워커를 재활용하세요.
  • 버퍼링 프록시 뒤의 스트리밍 응답 — 전체 본문을 버퍼링하는 프록시는 메모리 이점을 무효화합니다. 프록시가 PDF 응답을 스트리밍하도록 구성하거나, 해당 환경에서는 버퍼링 응답을 사용하세요.
  • kernel.reset가 준수되지 않음kernel.reset을 호출하지 않는 런타임에서는 이미지 캐시가 image_cache_mb로 제한되지만 요청 사이에 비워지지는 않습니다. 그에 맞게 상한값을 설정하세요.
  • 요청 간에 문서 보유 — 이전 요청에서 캡처한 Document에는 오래된 상태가 남아 있습니다. 항상 PdfFactory를 통해 요청마다 새로 가져오세요.

각 행은 이 페이지에서 제시하는 규범적 주장을 나타내며, 게이트된 SDO 코퍼스의 전체 64자리 16진수 reference_id에 고정되어 있습니다. 출처, 즉 코퍼스 매니페스트와 검색 전송 수단은 _sidecars/rag-citations.yaml에 있습니다.

사양조항reference_id 식별자주장
PSR-11psr_11_container#1.1.2.p3.b공유되지 않는 서비스: 확인마다 별개의 값
PSR-3psr_3_logger#x3.p17선택적 로거 협력자
  • /integrations/symfony/configuration/ — 서비스 수명 주기 및 매개변수.
  • /integrations/symfony/security-and-operations/ — 응답 헤더, 경로 검증, 키 처리.
  • /integrations/symfony/troubleshooting/ — 부팅 및 런타임 진단.
  • /integrations/symfony/quickstart/ — 최소한의 비동기 설정.