トラブルシューティング:メモリとパフォーマンス
これらのエントリーは、負荷下で遭遇する 2 つの失敗のファミリーを扱います。レンダリング中に PHP がメモリを使い果たすことと、プロセスがウォームになったり飽和したりするとスループットが崖から落ちることです。各エントリーは、症状、もっともありそうな原因、 そして実際の NextPDF の API または標準の PHP-FPM コントロールを使う修正を名指しします。 背後のストリーミングモデルとワーカーのチュートリアルについては、ストリーミングとメモリ を読んでください。このページは、 その障害対応側の伴侶です。
まず計測します。レンダリングの前後で memory_get_peak_usage(true) をサンプリングし、
反復の間に memory_reset_peak_usage() を呼び出します。エンジンのベンチマークがレンダリングごとのコストを分離するのと同じやり方です。ベースラインなしのチューニングは、
崖を取り除くのではなく動かすだけです。
エントリー: 生成中の “Allowed memory size exhausted”
「エントリー: 生成中の “Allowed memory size exhausted”」という見出しのセクション- 症状。 レンダリングが、しばしば大きな、または画像の多いドキュメントで、PHP
ランタイムからの致命的な
Allowed memory size of <n> bytes exhaustedで中断します。 - ありそうな原因。 デフォルトの書き込みパスはドキュメント全体を構成してからシリアライズするため、ピークメモリは出力サイズの合計を追います。大きなドキュメント、
大きな埋め込み画像、または大きな埋め込みフォントフェイスが、リクエストを
memory_limitを超えて押し上げる可能性があります。 - 解決。
- 画像キャッシュを境界付ける。
NextPDF\Core\ConfigはimageCacheBytes(デフォルト52428800、つまり 50 MB)を公開します。インスタンスの wither$config->withImageCacheBytes($bytes)(シグネチャwithImageCacheBytes(int $bytes): self)でそれを下げて、多くの画像を埋め込むビルドが、スワップする代わりに既知の上限で速やかに失敗するようにします。これはメモリ内の画像キャッシュを上限で抑えます。画像自体を再サンプリングや再エンコードはしません。 - 埋め込む前に入力を縮小する。 Core は画像をダウンスケールや再エンコードしません。 過大なラスターアートを埋め込む 前に リサイズして再エンコードし、実際に使うフォントを埋め込んで、サブセット化が保持すべきグリフセットを小さく保ちます (PDF ファイルサイズを削減する を参照)。
- 圧縮を有効に保つ。 新しい
Configはcompressがtrueに設定されています。 通常のビルドではそのままにしてください。withCompress(false)はサイズ最適化ではありません(通常は出力が増えます)。パイプラインをデバッグまたはプロファイルするために手を伸ばしてください――それはメモリを減らすのではなく、CPU/メモリのトレードオフをシフトします(圧縮ステップをスキップする)。 memory_limitをワーカーごとに意図的に上げる。 これは NextPDF のキーではなく、標準の PHP 設定です。プール構成で、または CLI/キュープロセスに対してini_set('memory_limit', '256M')で設定し、推測ではなくプロファイルされたピークに合わせてサイズを決めてください。
- 画像キャッシュを境界付ける。
- 関連。 ストリーミングとメモリ。
エントリー: 非常に大きなドキュメントでメモリがページ数とともに増える
「エントリー: 非常に大きなドキュメントでメモリがページ数とともに増える」という見出しのセクション- 症状。 各ページが小さくても数千ページのドキュメントがメモリを使い果たし、ピークがページ数とおおよそ歩調を合わせて上昇します。
- ありそうな原因。 バッファリングするライターは、シリアライズされたドキュメント全体をヒープに保持します。非常に大きなドキュメントでは、それが支配的なコストです。
- 解決。
- ストリーミングの書き込みパスを優先します。ストリーミングとメモリ
に記述された、文書化されたストリーミング書き込みパスを使用してください。それは各ページを構成されるそばからシリアライズしてバッファを解放するため、ページバッファ/出力の増加を抑えます。小さなオブジェクトごとのメタデータ(オフセット、
ページツリー)は、依然としてページ/オブジェクト数とともにスケールしうります。
内部クラスをコピーするのではなく、文書化されたエントリーポイントに従ってください
――背後のストリーミングエンジンは
experimental階層で、そのシンボルは安定した公開面ではありません。 - ネイティブの
writeHtml()パーサーについては、入力側のメモリがネスト深さと要素数の両方のガードで境界付けられていることを覚えておいてください。ADR-001 はネストをMAX_NESTING_DEPTH = 100で上限を設け、MAX_ELEMENT_COUNT = 50000を超えるドキュメントを拒否します。要素の上限に達したドキュメントは、静かにメモリを使い果たすのではなく、明示的にそう告げられます。これらの ADR-001 の上限はネイティブパーサーのみを統制します。オプションの Chrome ブリッジ (writeHtmlChrome())はプロセス外でレンダリングし、これらの上限ではない独自の別個のメモリ/入力制限を持ちます。
- ストリーミングの書き込みパスを優先します。ストリーミングとメモリ
に記述された、文書化されたストリーミング書き込みパスを使用してください。それは各ページを構成されるそばからシリアライズしてバッファを解放するため、ページバッファ/出力の増加を抑えます。小さなオブジェクトごとのメタデータ(オフセット、
ページツリー)は、依然としてページ/オブジェクト数とともにスケールしうります。
内部クラスをコピーするのではなく、文書化されたエントリーポイントに従ってください
――背後のストリーミングエンジンは
- 関連。 ストリーミングとメモリ。
エントリー: 長命のワーカーが多くのジョブの後にメモリを使い果たす
「エントリー: 長命のワーカーが多くのジョブの後にメモリを使い果たす」という見出しのセクション- 症状。 単一のレンダリングは成功するが、多くの PDF を連続してレンダリングするキューワーカーが、数分または数時間後にメモリを使い果たす。
- ありそうな原因。 長命の PHP プロセスは、ジョブをまたいで割り当てを蓄積します。 1 つのリクエストでは見えない緩やかな増加が、数千にわたって積み重なります。
- 解決。
- レジストリを共有し、ドキュメントを再作成します。
FontRegistryとImageRegistryを起動時に一度構築してDocumentFactoryに渡し、ジョブごとに$factory->create($config)で新しいDocumentを作成します。フォントと画像の解析は、ジョブごとに一度ではなくプロセスごとに一度起こり、ジョブごとのドキュメントツリーはスコープを外れたときに回収されます。examples/14-worker-factory.phpに従ってください。 - 共有画像キャッシュを
new ImageRegistry(maxCacheBytes: ...)で境界付けて、 ジョブをまたいで際限なく増えないようにします。 - ワーカーをリサイクルする ――エンジンの保証ではなく、プロセス制御です。
PHP-FPM では、各子プロセスが固定数のリクエスト後に再生成されるよう
pm.max_requestsを設定します。Laravel キューではqueue:work --max-jobs/--max-time/--memoryを、Symfony Messenger ではmessenger:consume --limit/--time-limit/--memory-limitを使用します。
- レジストリを共有し、ドキュメントを再作成します。
- 関連。 ストリーミングとメモリ。
エントリー: コールドまたは不十分にウォームアップされたプロセスでのスループットの崖
「エントリー: コールドまたは不十分にウォームアップされたプロセスでのスループットの崖」という見出しのセクション- 症状。 新しいプロセスでの最初のレンダリングが遅い、またはすべてのリクエストが、 ウォームなリクエストが払うべきでない解析コストを払う。
- ありそうな原因。 2 つのコールドスタートコストが積み重なります。opcache のない
PHP はリクエストごとにすべてのファイルを再コンパイルし、ウォームアップされていない
FontRegistryは各フォントフェイスを最初に使われたときに解析します。 - 解決。
- opcache を有効にする(そして役立つ場合は JIT も)。
opcache.enable=1と寛大なopcache.memory_consumptionを設定し、本番ではopcache.validate_timestamps=0を設定して、キャッシュがリクエストごとに再チェックされないようにします。その設定は、すべてのリリースで PHP-FPM を再起動またはリロードする(あるいは別の方法で opcache をリセットする、例えばopcache_reset()/cachetool)デプロイプロセスを必要とします――そうでなければ opcache は古いバイトコードを提供し続け、デプロイ後に古いコードが走ります。これらは標準の PHP ini 設定で、NextPDF のキーではありません。 - 起動時にフォントレジストリをウォームアップしてロックする。
FontRegistryインスタンスで、$fontRegistry->warmup($fontFiles)は起動時にフェイスを一度解析し、$fontRegistry->lock()はレジストリを凍結してリクエスト時のコードが共有状態を変更できないようにします。$fontRegistry->isLocked()が状態を報告します。本物の長命のワーカーやアプリケーションサーバー――同じ PHP プロセスを多くのリクエストにわたって生かし続けるキューコンシューマーや RoadRunner/Swoole/Octane ワーカー――では、ウォームアップされロックされたレジストリは解析済みフェイスをオブジェクト状態に保持し、リクエストごとのフォント解析をプロセス起動時の一度きりのコストに変えます。標準の PHP-FPM リクエストモデルの下では、そのウォームなオブジェクト状態はリクエストをまたいで生き残り ません。opcache はコンパイル済みクラスとバイトコードをキャッシュするのであって、ウォームなユーザーランドのオブジェクト状態をキャッシュするのではないため、ウォームアップされたFontRegistryは リクエストごとに 再構築されます(子の起動時から各リクエストで再実行される)。子プロセス内でリクエストをまたいでウォームに保たれるのではありません。素の PHP-FPM では、opcache は主にバイトコードの再コンパイルコストを償却します。フォント解析が リクエストごとに 払われ、解消されはしないことを受け入れてください。リクエストをまたぐ償却――各フェイスをプロセスの寿命にわたって一度だけ解析する――は、RoadRunner/Swoole/Octane ワーカーや、 同じ PHP プロセスを多くのリクエストにわたって生かし続けるキューコンシューマーのような、 本物の長命のプロセスでのみ当てはまります。 - 同じテンプレートをリクエストごとに再解析しない。 フォントと再利用可能なリソースを、共有レジストリを通じて起動時に一度解決します。リクエストで作成すべきなのは、ジョブごとの
Documentだけです。
- opcache を有効にする(そして役立つ場合は JIT も)。
- 関連。 ストリーミングとメモリ。
エントリー: 並行下でサーバーが飽和してレイテンシが急増する
「エントリー: 並行下でサーバーが飽和してレイテンシが急増する」という見出しのセクション- 症状。 レンダリングごとのレイテンシは単独では問題ないが、負荷下でマシンがスワップしたり、CPU が飽和したり、リクエストがキューに溜まってタイムアウトしたりする。
- ありそうな原因。 利用可能な RAM に対して PHP-FPM ワーカーが多すぎて、ワーカーピークの合計が物理メモリを超えてホストがスワップする。または、ワーカーが少なすぎて、 リクエストが小さなプールの後ろで直列化する。
- 解決。
-
pm.max_childrenをプロファイルされたピークから決める。 標準の式を使用します。pm.max_children = (total RAM - OS/other overhead) / per-worker peak memory代表的なドキュメントでワーカーの実際のピークを計測し(範囲のプロファイリング注記を参照)、OS とあらゆる同居サービスのためのヘッドルームを確保して、除算します。マージンを残してください。RAM の 100% にサイズを合わせないでください。
-
予算で圧縮コストを見積もります。Flate 圧縮はストリームを書き込む際の大きな CPU コストになりえ、圧縮可能なストリームバイトの量とともにスケールするため、ページ数と埋め込みフォントの量がレンダリングごとの CPU に影響します。画像処理、フォントサブセット化、入力解析も支配的になりえます。代表的なドキュメントで計測し、ワーカー数と CPU を選ぶときに本当のドライバーを考慮してください。
-
pm.max_requestsをpm.max_childrenと並べて設定し、上のワーカーエントリーのように子プロセスがリサイクルしてあらゆる緩やかな増加を回収するようにします。
-
- 関連。 ストリーミングとメモリ。
エントリー: 大きな信頼できない入力が解析に遅い、または高コスト
「エントリー: 大きな信頼できない入力が解析に遅い、または高コスト」という見出しのセクション- 症状。 大きな、または深くネストされた入力――特に HTML やあなたが生成しなかったフォント――で、レンダリングが遅い、またはメモリを多く使う。
- ありそうな原因。 解析コストは入力のサイズと構造とともにスケールします。病的な入力(深いネスト、巨大な要素数、または不正なフォント)が予算を支配する可能性があります。
- 解決。
- エンジンの境界に頼ります。ネイティブの
writeHtml()HTML パーサーはMAX_NESTING_DEPTH = 100とMAX_ELEMENT_COUNT = 50000(ADR-001)を強制します。 それらの上限を超える入力は、プロセスを使い果たすことを許される代わりに拒否されます。(オプションの Chrome ブリッジ、writeHtmlChrome()は、これらの ADR-001 の上限の範囲外で、独自の別個のメモリ/入力制限を強制します。) - 呼び出し元が提供するフォントを信頼できないものとして扱います。不正なフォントは、
出力を破損させる代わりに
NextPDF\Exception\FontParsingExceptionを送出するため、 特定の例外をキャッチして、再試行する代わりに入力を拒否してください。 - 入力を境界で検証してサイズを決め、呼び出し元が影響するコンテンツにはドキュメントサイズにリクエストレベルの制限を適用してください。
- エンジンの境界に頼ります。ネイティブの
- 関連。 トラブルシューティング: フォントとタグ付け。
判断テーブル: 症状からレバーへ
「判断テーブル: 症状からレバーへ」という見出しのセクション| 症状 | もっともありそうなレバー |
|---|---|
単一レンダリングでの Allowed memory size … exhausted | $config->withImageCacheBytes() を下げる。埋め込み前に画像を縮小する。ワーカーごとの memory_limit を上げる |
| ピークメモリがページ数とともに上昇する | 文書化されたストリーミング書き込みパス を使う |
| ワーカーメモリが多くのジョブにわたって増える | DocumentFactory を介して FontRegistry/ImageRegistry を共有する。pm.max_requests / --max-jobs を設定する |
| 最初のリクエストが遅い、リクエストごとの解析コスト | opcache を有効にする。起動時に $fontRegistry->warmup() の後 ->lock() |
| 負荷下でホストがスワップ / レイテンシ急増 | pm.max_children = (RAM − overhead) / per-worker peak をサイズ決め |
| 大きな/信頼できない入力で遅い、または重い | ADR-001 の上限に頼る。FontParsingException で不正なフォントを拒否する |
エッジケースと落とし穴
「エッジケースと落とし穴」という見出しのセクションimageCacheBytesは メモリの上限であり、サイズのつまみではありません。 それを下げるとキャッシュが上限で抑えられてビルドが速やかに失敗します。埋め込む画像を再サンプリングや再エンコードすることは決してありません。Core には画質の制御はありません。withCompress(false)はファイルを 大きく し、デバッグ/プロファイリングの補助です。サイズ最適化ではありません。メモリを減らすのではなく、CPU/メモリのトレードオフをシフトします(圧縮ステップをスキップする)。- ストリーミングエンジンの正確なメモリプロファイルは
experimental階層のプロパティであり、マイナーリリース間でシフトする可能性があります。あらゆる単一の計測を、移植可能な定数ではなく観測として扱ってください。 memory_limit、opcache.*、pm.max_children、pm.max_requestsは標準の PHP / PHP-FPM 設定です。NextPDF はそれらのための独自のキーを公開しません。Configではなく、ランタイムで構成してください。
- ストリーミングとメモリ ―― ストリーミングモデル、ADR-001 の境界、そして完全なバッチワーカーのチュートリアル。
- PDF ファイルサイズを削減する ―― 圧縮とフォントサブセット化、2 つの本当のサイズ制御。
- トラブルシューティング: フォントとタグ付け ―― フォントの解決、解析、サブセット化の失敗。
- ナレッジベースインデックス
用語集: ストリーミングライター · フォントサブセット化