PDF に変換するために puppeteer を使うが、GROWI のコンテナ上でヘッドレスブラウザを立ち上げるのは、一つのコンテナが担う処理としては多すぎるため、別のコンテナ上で puppeteer を立ち上げる。その設計を以下に示す。別のコンテナで立ち上げるアプリ名は growi-pdf-converter とする。

エクスポートまでの全体フロー

  1. GROWI のコンテナと growi-pdf-converter のコンテナを、共有ディレクトリを設定して立ち上げる
    • GROWI.cloud: GCS FUSE でバケットを共有する
    • OSS: docker-compose で shared volume を設定する
  2. GROWI アプリで bulk export を開始する
  3. GROWI アプリから pdf 変換部分を growi-pdf-converter にリクエストし、growi-pdf-converter は変換した pdf ファイルを共有ディレクトリに出力する
  4. pdf の変換が完了したら、GROWI アプリはクラウドストレージにアップロードする

growi-pdf-converter

API

/pdf/html-to-pdf

パラメータ

  • htmlString: page の markdown body を html に変換した string
  • fileName: エクスポートするファイル名 (page の path)
  • jobId: PageBulkExportJob の id
    • 共有ディレクトリ上に job id のディレクトリを作り、そこにエクスポートするファイルを出力する

処理

  1. #{共有ディレクトリパス}/#{jobId} というディレクトリがない場合は作成する
  2. 渡された htmlString を puppeteer を使って pdf に変換し、#{共有ディレクトリパス}/#{jobId} に出力する

growi

  1. PDF の bulk export を POST /_api/v3/page-bulk-export 当てにリクエスト
  2. 各ページデータをストリームで読み込む
  3. 読み込んだページの body の PDF への変換と fs への書き込みを growi-pdf-converter にリクエストし、処理が終わるのを await する
    • 変換が timeout (120s) する場合は全体のエクスポートを失敗させる
    • 上記処理はストリームの中で行われるため、growi-pdf-converter の処理よりページデータの読み込みが早い場合は back pressure が生じ、ページデータの読み込みがストップする
  4. fs へのエクスポートが終了したら、共有ディレクトリから PDF データを読み込み、クラウドストレージにアップロードする

検証

実行時間

ランダムな30,000文字を記述したファイルを

  • 10,000 ページ (1ページツリー, 1並列) エクスポート: 28min
  • 50,000 ページ (5ページツリー, 5並列) エクスポート: 57min

各コンテナの使用リソース量

変換1並列

growi-pdf-converter

メモリ
  • 実行前: 265MB
  • 実行中: 350 - 450MB
CPU

00:12 からエクスポート実行
image.png

growi

メモリ
  • 実行前: 4.94GB
  • 実行中: 4.91 - 5.12GB
    • 最初すぐ max 値まで上がり、それ以降は実行前と同じ量を維持
CPU (大きな変化は無し)

00:12 からエクスポート実行
image.png

変換5並列 (max)

growi-pdf-converter

メモリ
  • 実行前: 289MB
  • 実行中: 400 - 650MB
CPU

00:12 からエクスポート実行
image.png

growi

メモリ
  • 実行前: 4.62MB
  • 実行中: 4.98 - 5.43GB
    • 最初すぐ max 値まで上がり、それ以降は実行前と同じ量を維持
CPU (大きな変化は無し)

00:12 からエクスポート実行
実行前の大きな揺れはコンパイルの影響  2024-09-16 at 0.26.18.jpg