💭

Java Runtime の Lambda での PDF 作成プロトタイプとそこで得られた知見

2023/12/24に公開

背景とやりたいこと

Hello! 李です。この記事は株式会社エス・エム・エス Advent Calendar 2023の24日目の記事です。

私のチームは介護業務で使う一部の帳票を PDF 方式で作成する必要があります。PDF を作成する時にいろんなライブラリがありますが、プログラミング言語やパフォーマンスなどの要素を考慮して、Apache PDFBox というライブラリを採用しました。また動作環境について、今後のスケーラビリティや料金を考えると、Lambda を使えばトラフィックが増えてもさばけるし、利用時しか料金が発生しなくてコストを削減できそうなので、Lambdaで PDF 処理のプロトタイプを作成してみました。
以降ではプロトタイプのアーキテクチャとその実装の中で得られた知見について見ていきます。

アーキテクチャの説明

プロトタイプのアーキテクチャは以下になります:

処理の流れを説明すると以下のようになります。

  • 1.PDF 作成リクエストを投げる:Frontend から Web Application へ PDF 作成リクエストを投げます
  • 2.PDF 生成用データをアップロード:PDF 作成に必要なデータをWeb ApplicationからS3へ投げます。その後2−1〜2−4の以下の処理が非同期で実施されます
    • 2−1.S3 が PDF 生成用データを受けた後に、PUT イベントによって PDF 作成用の Lambda Function を実行させます
    • 2−2.PDF 作成用の Lambda Function は S3 から PDF 生成用データを取得します
    • 2−3.PDF 作成用の Lambda Function で PDF ファイルを作成します
    • 2−4.作成した PDF ファイルを S3 へアップロードします
  • 3.Presigned URL を作成:S3 へアップロードした PDF ファイルを Presigned URL 方式で公開します。この処理は2の非同期処理の完了は待たずに実行されます
  • 4.Presigned URL を返す:Frontend へPresigned URL を返します。
  • 5.PDF ファイルをポーリング:Frontend は Presigned URL を使って S3 に対して PDF ファイルをポーリングして取得する

知見

前項で紹介したアーキテクチャに基づいてプロトタイプを実装しました。その際に得られた技術的な知見について3点、以下で紹介します。

知見1 : S3を介すことでLambda request のペイロード制約を回避できる

プロトタイプでは当初、Web Application から Lambda Function を実行する(ステップ2&ステップ2−1)処理で S3 を経由せずに Lambda を直接起動する方式を試してみたのですが、Lambda request のペイロードが大きくなると失敗することがありました。調べたところ、Lambda request のペイロードは制限があり、非同期で Lambda request を実行する場合に上限値が 256 KB になっています。対策を2つ考えましたが、Lambda request のペイロードを考慮しないために S3 を経由して実施する対策にしました(アーキテクチャの通り)。

  • 対策1:Lambda request を同期で実行します。同期で実行する場合はペイロードは制限が緩くなって、上限値が 6 MB になります。
  • 対策2:Lambda request を直接呼び出さず、S3 を経由して S3 のイベントによって Lambda を実行します。S3 は1 つの PUT にアップロード可能なオブジェクトの最大サイズは 5 GB ですので基本は大丈夫なはずです。ただ、S3 を経由するため、Lambda を直接呼び出すよりは多少オーバヘッドが発生します。

知見2: Presigned URLの取り回しには注意が必要

Lambda で作成した PDF ファイルを S3 に保存しますが、S3 に保存する PDF ファイルをどう公開するかについては調べたところ基本的に Presigned URL 方式で公開することが分かりました。
当初、S3 に PDF ファイルをアップロードする時に Presigned URL も必要だと思いましたが、実際には不要でした。S3 に PDF ファイルをアップロードする処理を実行するのが Lambda なので、Lambda に S3 へデータをアップロードする権限を与えれば OK でした。実際に必要なのは Front が S3 から PDF ファイルを取得する時に使う Presigned URL です。
また、Front が S3 から PDF ファイルを取得する時に使う Presigned URL を作成する処理をコーディングする時に、 S3 への接続と S3 に該当するオブジェクトの存在が必要と想定しましたが、実際に S3 への接続が無くても作れますし、S3 にオブジェクトが存在しなくても作れることが分かりました(もちろんダウンロードする時に Presigned URL で指定したオブジェクトが存在しないとダウンロードできないです)。
更に、 Presigned URL を一旦公開したらを削除できないです!(詳細は下記のリンクをご参考ください)

最後、Presigned URL を作る時に下記のように AWS コンソールでも設定できて色々手軽に試せました。
<img width="500" alt="image.png (146.4 kB)" src="https://files.esa.io/uploads/production/attachments/6551/2023/12/14/78686/9f292022-3c8a-4a6a-8ffe-7b7f5f4d0407.png">

知見3: cold start と warm up された場合パフォーマンスが随分変わる

Java Runtime の Lambda を使う場合に cold start の状態の実行は warm up された状態の実行より時間がかかることが分かりましたが、どれぐらい時間がかかるか測ってみましたところ、自分の場合 2秒~3秒ぐらい伸びることが分かりました。
cold start 時のパフォーマンス改善 について AWS 公式で推奨された snapstart 対策を試していましたが、Lambda の設定を変更するだけではあまり効かないことが分かりました。Lambda で snapstart を有効にするには下記をご参考ください

snapstart を有効にして測定した結果ですが、snapstart では改善対象が初期化処理の Init Duration であり、Init Duration 自体が長くなければ改善効果が出ないです。測定した処理だと Init Duration が 0.4s ぐらいです。改善策によってそれ自体はなくなりましたが、代わりにRestore Duration(0.3sぐらい) が増えてほぼ相殺してあまり変わらなかったです。逆に、snapstart を有効にしたあとに初期化後の リクエストハンドリング処理となる Duration が長くなってトータルで改善前よりもパフォーマンスが悪くなってしまいました。
snapstart のメリットをより享受できるために Lambda での処理をできるだけ init 処理に寄せて、init 処理後の処理時間を短くする必要があります。他にJava の コンパイル方法も工夫すれば改善できそうです。

軽くパフォーマンスを測定しました

実装したプロトタイプに対して、どの程度のパフォーマンスが出るのかをトラヒックが少ない場合と多い場合のそれぞれざっくり測定してみました。

  • トラヒックが少ない場合に、仮でLambda の同時実行数が1で、1件のPDF ファイル(500 ページ、仮のコンテンツ)を生成する時間を測ってみると下記の結果を得られました:

    • warm up された状態:1秒〜1.7秒
    • cold start の状態:上記の結果 + (2秒〜3秒)
  • トラフィックが多い場合を模擬して100件のPDFファイル(ファイルのコンテンツは上記と同じ)を作成するリクエストを連続で実行させて下記の結果を得られました( warm up された状態)

    • Lambda の同時実行数:7
    • 全件の作成時間:平均61 秒(←生成時間が1件の PDF ファイルの場合の 100 倍より小さい数字になっていることが分かります )

まとめ

プロトタイプを作ってみたところ下記のことが分かりました。

  • Lambda request のペイロードを予測するのが難しい場合は S3 経由で実施すると無難です。ただ、 S3 経由することによってある程度のオーバヘッドが発生します
  • S3 から PDF ファイルを取得する時に Presigned URL を使えば良いです。Lambda から S3 へ PDF をアップロードする場合に Presigned URL が不要です
  • Java Runtime の Lambda を使う場合、code start が発生するとパフォーマンスが悪くなりますので改善策が必要です。Lambda の設定で snapstart を有効に変更するだけでパフォーマンスが改善されない場合があります。
  • トラフィックが多い場合、Lambda の同時実行数が増えると順次実行よりトータル処理時間が短くなります。

参考資料

Discussion