프로덕션 환경에서의 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로 오프로드하세요.
- 각 문서 유형에 대해
PdfBuilderInterface를 구현하세요. - 빌더를
container.service_locator에 등록한 뒤 이를GeneratePdfHandler의$builderLocator로 연결하세요. - 메시지
GeneratePdfMessage를 영속적 전송 수단으로 라우팅하세요. - 수명을 제한해 워커를 실행하세요.
제한된 워커 수명
섹션 제목: “제한된 워커 수명”서드파티 종속성에서 누수된 할당이 한없이 늘어나지 않도록 워커를 재활용하세요.
php bin/console messenger:consume async \ --limit=200 \ --memory-limit=256M \ --time-limit=3600번들의 messenger.timeout 및 messenger.retries 구성 키는 의도한 메시지별 타임아웃과 재시도 예산을 기록합니다. Symfony의 재시도 전략과 워커 플래그를 통해 그에 맞는 동작을 적용하세요.
워커에서의 출력 경로 안전성
섹션 제목: “워커에서의 출력 경로 안전성”GeneratePdfMessage는 생성 시점에 출력 경로를 검증합니다. 그런 다음 GeneratePdfHandler는 디스크에 쓰기 전, 실행 시점에 이를 다시 검증합니다. 이 2단계 검사는 비동기 작업에서 중요합니다. 메시지는 디스패치와 소비 사이에 큐에 머물 수 있으므로, 핸들러는 큐에 들어간 경로를 무조건 신뢰하지 않습니다. 심층 방어 차원에서 워커의 파일 시스템 권한을 의도한 출력 디렉터리로 제한하세요.
관찰 가능성
섹션 제목: “관찰 가능성”해당 FontRegistry와 ImageRegistry 서비스는 선택적 Psr\Log\LoggerInterface를 받습니다(nullOnInvalid()로 바인딩됨). 애플리케이션이 로거를 제공하면 레지스트리는 이를 통해 진단 정보를 내보낼 수 있습니다. 로거는 PSR-3 로거 컨트랙트(PSR-3)에 따른 선택적이며 교체 가능한 협력자입니다. 요청 수준의 가시성을 확보하려면 애플리케이션 코드에서 PdfFactory::create()와 Messenger 핸들러 주변에 로그를 남기세요. 인시던트를 분류할 때는 messenger:consume -vv를 사용하세요.
배포 체크리스트
섹션 제목: “배포 체크리스트”- 애플리케이션
composer.json에서 단일nextpdf/core메이저 버전을 고정하세요 (번들은^3.0 || ^5.2를 허용합니다). - 배포된 PHP 이미지에서
ext-mbstring과ext-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-11 | psr_11_container#1.1.2.p3.b | 공유되지 않는 서비스: 확인마다 별개의 값 | |
| PSR-3 | psr_3_logger#x3.p17 | 선택적 로거 협력자 |
참고 항목
섹션 제목: “참고 항목”- /integrations/symfony/configuration/ — 서비스 수명 주기 및 매개변수.
- /integrations/symfony/security-and-operations/ — 응답 헤더, 경로 검증, 키 처리.
- /integrations/symfony/troubleshooting/ — 부팅 및 런타임 진단.
- /integrations/symfony/quickstart/ — 최소한의 비동기 설정.