背景
PHP+CodeIgniter から TCPDF を利用して PDF 出力する処理を実装した。
少量のデータなら問題なく動作してたけど、大量の請求先を一括で生成しようとすると Allowed memory size exhausted エラーが発生し、メモリ不足に陥るケースがあった。
以下に、発生した問題の原因と、実際に採用した解決策についてまとめる。
症状
数百の PDF を一括で出力する処理では、次のようにループのたびに new Pdf() で TCPDF インスタンスを生成し、$pdf->Close() と unset() を呼び出していた。
foreach ($data as $billing_cd => $summary) {
// … 請求書ごとのデータ設定 …
$pdf = new Pdf(array('P','mm','A4',true,'UTF-8'));
// PDFに内容を書き込む
// …
$pdf->Output($filename,'F');
$pdf->Close();
unset($pdf);
gc_collect_cycles();
}
しかし、生成件数が多くなると PHP のプロセスが大量のメモリを消費し、メモリ上限に到達して処理が中断した。
原因
TCPDF はフォントや画像などをクラスの内部にキャッシュしていて、デフォルトではコンストラクタで register_shutdown_function() を登録していて、各インスタンスの デストラクタはスクリプト終了時に一度だけ実行される 仕様らしい。
ループの中で Close() を呼んでも PDF 出力を終了するだけで、内部のキャッシュやフォント情報が解放されない。
その結果、インスタンスを生成するたびにメモリが増え続ける ことになる。
FVue の記事ではこの挙動を検証しており、1,000 件の TCPDF オブジェクトを生成するだけでメモリが 120MB 以上に達することが報告されている。
また、SourceForge のフォーラムでも同様の相談があり、開発者から「同じインスタンスを使い回すか _destroy() を明示的に呼び出すように」というアドバイスが出ている。
解決策
1. デストラクタを明示的に呼び出す
TCPDF では __destruct()(内部では _destroy() を呼び出す)を手動で実行することでキャッシュを解放できる。
PDF を出力した後に以下のように記述する。
$pdf->Output($filename, 'F');
// キャッシュを解放
$pdf->__destruct();
unset($pdf);
gc_collect_cycles();
_destroy(true) を直接呼び出しても同様の効果がある。
これにより各ループ後にメモリ使用量がほぼ元に戻り、大量出力でもエラーが出なくなった。
2. インスタンスの再利用
出力ごとに新しい TCPDF オブジェクトを生成するのではなく、単一のインスタンスを使い回す 方法もある。
ページを追加するだけであれば AddPage() を呼び出せば済むので、フォントの読み込みなどの初期化処理が一度で済み、メモリ使用量も抑えられる。
3. その他の工夫
- ディスクキャッシュを利用する:
tcpdf_config.phpでK_PATH_CACHEを適切なディレクトリに設定し、コンストラクタの$diskcache引数をtrueにすると、フォントや画像をディスクにキャッシュできる(古いバージョン向けの設定だが一括出力時のメモリ対策になる)。 - フォント設定の最適化: 日本語を含まない帳票では
setFontSubsetting(false)を使用し、Unicode ではなくコアフォントや ISO‑8859‑1 等に切り替えるとメモリ使用量を減らせる。 - 処理の分割: 一度にすべてを生成せず、一定件数ごとに別プロセスとして呼び出す(CLI やバッチ処理)とプロセス終了時に完全にメモリが解放される。
まとめ
TCPDF で大量の PDF を連続生成する際に Close() と unset() だけではメモリが解放されず、プロセスがメモリ不足に陥ることがある。
原因はデストラクタが shutdown 時まで遅延実行されることにあり、手動で __destruct()(または _destroy())を呼び出すことでキャッシュを解放できる ことが分かった。
インスタンスを使い回す、ディスクキャッシュを有効にする、フォント設定を見直すといった工夫も合わせることで、大量出力処理が安定して動作するようになった。
今回の経験が、同じように TCPDF のメモリ消費に悩む人の参考になればうれしいです。
