1日に数百件のPDFを1つのバックエンドLambdaや小さなKubernetes podで生成している段階では、アーキテクチャはあまり重要ではありません。どの方法でも動きます。どの方法でも十分速いです。
規模が大きくなると状況は変わります。1日に数万件の文書を出力するようになると、つまり、ある程度の利用があるECプラットフォーム、物流会社、BNPLサービス、給与計算サービス、請求書発行プラットフォームでは、次の3つの数字が痛み始めます。
- コールドスタートのレイテンシ。どこかの何かは常にコールド状態だからです。
- 地域間レイテンシ。顧客はオリジンの隣にいるわけではないからです。
- 1回のPDF生成にかかる計算コスト。1日に数万回、そのコストを払うからです。
この記事では、1日あたり約10,000回のPDF生成を超えるとこれらがどう変わるのか、そしてgPdfのようにエッジへ配置された生成エンジンが「同じものを少し速くしたもの」ではなく、実質的に別カテゴリの解決策になる理由を見ていきます。
1. コールドスタート税は並行性と一緒に積み上がる
コールドスタートは単なる不便ではありません。並行処理数の曲線に連動します。よくある流れはこうです。
- 平均トラフィックに合わせて、N=10のウォーム状態のコンテナを用意する。
- 3倍のトラフィックスパイクが来る(Black Friday、給与日、四半期末など)。
- スパイクを吸収するために、20個の新しいコンテナがコールドスタートする。それぞれChromium / Prince / 自社の実行環境の起動に1.5〜2.5秒かかる。
- その30秒間、新しいコンテナはp99 = 2秒で応答し、全体のp99を引き上げる。
- 下流のタイムアウト予算(おそらく注文パイプライン全体で5〜10秒)は、PDF生成だけで食われ始める。
トラフィックが平坦なら問題ありません。しかしスパイクがあると厳しくなります。そしてPDFトラフィックは常にスパイクします。請求書は請求サイクルの境目に、ラベルは配送会社の集荷タイミングに、明細は月末に集中します。
エッジ配置の代替案: Cloudflare Worker isolateのコールドスタートは、1.5〜2.5秒ではなく 5〜20 ms です。コンテナを立ち上げず、JVM/V8を初期化せず、ブラウザを起動しません。WASMモジュールは、すでに動いているプロセスに読み込まれます。コールドスタートは、アーキテクチャで回避策を組む対象ではなくなります。
gPdfについて言えば、ベンチマークで観測した最悪のコールドスタートは約12 msです。それも、新しく割り当てられたisolateへの最初のリクエストだけです。同じisolate上の後続リクエストでは、それすら省かれます。
2. 「速い」リクエストでも地域間レイテンシは本物
Sydneyから us-east-1 オリジンへの往復は、コードが何かをする前に200 msかかります。São Pauloから eu-west-1 は約190 ms。MumbaiからUS Eastは約220 msです。
しかも、これは片方向ごとです。Sydneyの顧客から見て、中央集約型PDF APIがサーバ側生成に300 msかかる場合はこうなります。
client → us-east : 200 ms
us-east render : 300 ms
us-east → client : 200 ms
total wall-clock : 700 ms
インタラクティブな操作(「送信前に請求書をプレビューする」)では痛みます。大量のサーバ側ジョブでは目立ちません。
エッジ配置の代替案: Cloudflareは300以上の都市で動いています。Sydneyの顧客に最も近いcoloは、おおよそ5 msの距離です。同じPDF生成はこうなります。
client → SYD colo : 5 ms
SYD render : 4 ms
SYD → client : 5 ms
total wall-clock : 14 ms
インタラクティブな操作では 50倍の改善です。サーバ側ジョブでは差し引きで同程度になることもありますが、インタラクティブなPDFプレビュー(「送る前に見せて」)は、ぎこちない処理ではなく、ほぼ無料の操作になります。
隠れた二次効果もあります。PDF APIがエッジで動くなら、隣接する処理もエッジへ移せます。チェックアウト時のPDFプレビュー、レート制限、認証チェックなどです。ホットパスからエッジへ移した部品ごとに、往復通信が1つ消えます。
3. 1回のPDF生成にかかる計算コストは静かに積み上がる請求です
1日100,000回のPDF生成に対するLambdaの価格計算を見てみます。
- Puppeteer: 約600 msの実時間、1024 MBメモリ。egressの前に、計算処理だけで月約$240。
- DocRaptor:
89/100,000 page tier。100,000/day(= 3M/month)で月約2,670。 - gPdf:
5/100,000 page tier。100,000/dayで月約150。ちょうど100,000/monthなら月約$5です。
規模が上がっても、コスト差は縮まりません。広がります。1日1,000,000回のPDF生成では次のようになります。
- Puppeteerインフラ: 月約$2,400 + 運用 + オンコール
- DocRaptor: 月約$26,700
- gPdf: 月$1,500の定額(公開価格グリッド上でボリュームを交渉する前提で、100K/day tierの5倍)
よくある反応は、「実際には節約幅はもっと小さいはず。何か隠れているはずだ」 です。私たちの経験では、そうではありません。PDF生成のコスト要因は、生成エンジンの計算負荷です。600 MBのChromiumプロセスを4 MBのWASMモジュールに置き換えると、1回のPDF生成コストはおおむね100倍下がり、請求もそれに従います。
これで私たちが赤字にならない理由は、土台となるCloudflare Workers Bundledの価格が約$0.50/million requestsだからです。gPdfの生成エンジンは1回あたり約1.5 msのCPUしか使わないため、1回の生成原価は本当に1セント未満です。持続可能な事業にするために控えめに上乗せしても、利用者には18倍の差が残ります。
「エッジ配置」が実際に買ってくれるもの
マーケティングスライドではなく、実際には3つです。
どんな負荷でも予測しやすいレイテンシ
リクエストごとの起動コストがないため、p50とp99が近く保たれます。トラフィックスパイクの最中でも、p99はp50の3倍以内に収まることが多いです。一方、Puppeteerではコールドスタートが連鎖している最中に、p99がp50の10倍まで伸びることがあります。
どこでも同じ 1 つのデプロイ可能な成果物
.wasmモジュールは、すべてのCloudflare coloに同じ形でデプロイされます。「Sydneyのプールはウォーム状態か?」という問いはありません。すべてのisolateが数ミリ秒でモジュールを起動し、同じように提供します。リージョンごとのLambda同時実行予約を維持するより、運用上本当にシンプルです。
組み込みへの道
将来、顧客の境界内(VPC、隔離されたクラスター、ネットワーク分離されたイントラネット)でgPdfを動かしたくなった場合も、同じWASMモジュールが動きます。これは「ホスト型SaaS」と「どこでも動く技術を出荷した」の違いです。
どこで崩れるか
エッジは無料の魔法ではありません。中央集約型が勝つ用途もあります。
- 数十秒かかるPDF生成。 1つのPDFに30秒かかる場合(巨大な財務諸表、科学レポートなど)、エッジのCPU上限と戦うより、永続状態を持つ長時間実行コンテナの方が向いています。
- ほかのデータベースが必要なPDF生成。 生成前に3つのOLAPテーブルをJOINする必要があるなら、生成エンジンはエッジではなくデータベースの近くに置くべきです。(解決策: JOINを済ませてから、実際のPDF生成用JSONをエッジへ送る。)
- 後処理が必要な出力。 透かし、署名、アーカイブのように、PDF生成後の処理が複数ステップで状態を持つなら、エッジ生成の「ステートレス」性は機能ではなく負担になることがあります。
それ以外、つまりB2Bの請求書/ラベル/領収書トラフィックの大部分では、重要な軸すべてでエッジが勝ちます。
今の構成を許容するのをやめるタイミング
シンプルなチェックリストです。3 つにチェックが入るなら、移行の計算はもう傾いています。
- 月間PDFインフラコストが $300 を超えている。
- 通常トラフィック中のPDF p99レイテンシが 800 ms を超えている。
- 顧客に影響したコールドスタート障害がある。
- CJK / RTL / emoji glyphsの欠落デバッグに4時間以上使った。
- インタラクティブな操作(プレビュー、画面上のダウンロード)でPDFを生成している。
- 複数の地理リージョンで運用している。
最初の3つが揃うなら、あなたは支払いながら痛みも受けている状態です。後ろの3つは、中央集約型の生成エンジンが、本来できたはずのプロダクト判断を積極的に制限していることを意味します。
心当たりがあるなら、Playground はサンプル請求書をブラウザ内で5 ms未満で生成します。結果に語らせてください。