middy-profilerでNode.jsのLambda functionをプロファイルしてみた
MAD事業部@大阪の岩田です。
先日NODE_OPTIONS=–enable-source-maps
を有効化したLambdaのパフォーマンスが遅くなる現象に遭遇しました。
この事象について調べた際以下のツイートが検索にヒットし、そこからNode.jsのLambda functionをプロファイルするのに使えるmiddy-profilerというライブラリを知りました
Spent most of the day hunting a performance issue with Node.js on AWS lambda, found it, and fixed it.
Execution time. Before: 1s, after: 150ms
To track and optimize such performance issues, read this quick debugging-horror-story-with-happy-ending thread ?
— Vincent Voyer (@vvoyer) February 28, 2022
今回は試しにこのライブラリを利用してみます。
環境
今回検証に利用した環境です
- Lambdaのランタイム: Node.js 14.x
- TypeScript: 4.4
- バニラのJavaScriptでも構わないのですが、今回は既存のTypeScript PJをベースに利用しました
- @middy/core: 2.5.7
- middy-profiler: 1.0.0
middy-profilerとは?
Node.js製のLambda functionを簡潔に記述するためのミドルウェアエンジンMiddyのミドルウェアとして提供されているライブラリです。このミドルウェアを利用するとLambda functionの実行結果をプロファイルし、プロファイル結果を所定のS3バケットにアップロードできます。プロファイル結果はデフォルトで
<Lambda function名>/<AWSリクエストID>/cpu_profile.cpuprofile
というオブジェクトキーでS3にアップロードされます。
このアップロードされたプロファイル結果をローカルPCにダウンロードし、Chromeの開発者ツール等で分析するといったことが可能です。
やってみる
それでは早速middy-profilerを使ってLambda functionをプロファイルしてみましょう。
ライブラリの導入
middy-profilerは@middy/coreに依存しています。まずは@middy/coreとmiddy-profilerをインストールします。手元のプロジェクトではyarnを利用しているのでyarnでインストールします。
$ yarn add @middy/core middy-profiler
npmの場合はこちら
$ npm install @middy/core middy-profiler
Lambda functionのコード修正
続いてmiddy-profilerを利用するようにLambda functionのコードを修正します。まずLambda functionの先頭に以下のコードを追加します。
import middy from '@middy/core' const profiler = require('middy-profiler')
middy-profilerの型定義が提供されていなかったので、importではなくrequireしています。
続いてhandlerを以下のように書き換えます。
(修正前)
export const handler = async ( event: APIGatewayProxyEventBase<APIGatewayEventDefaultAuthorizerContext> ): Promise<APIGatewayProxyResult> => { ...略 }
(修正後)
export const handler = middy(async ( event: APIGatewayProxyEventBase<APIGatewayEventDefaultAuthorizerContext> ): Promise<APIGatewayProxyResult> => { ...略 }).use(profiler())
S3バケットの準備とLambda functionの環境変数設定
コードの修正ができたので、middy-profilerからプロファイル結果をアップロードするためのS3バケットを作成し、Lambda functionの環境変数MIDDY_PROFILER_S3_BUCKET_NAME
にバケット名を設定します。
Lambda functionを実行する
ここまでで準備は完了です。Lambda functionを実行し、S3バケットの中身を確認してみましょう
こんな感じでプロファイル結果がアップロードされていればOKです。
Chromeの開発者ツールで色々確認する
S3にアップロードされたプロファイル結果をダウンロードしてChromeの開発者ツールを使って分析してみましょう。
まずChromeのアドレスバーにchrome://inspect/
と入力します
続いてOpen dedicated DevTools for Node
をクリックし、DevTools - Node.jsを開きます。
Loadボタンを押し、先程ダウンロードしたプロファイル結果を読み込みましょう。
読み込みが完了したら画面左のCPU PROFILES
に読み込んだファイルが表示されるので選択します。デフォルトの表示形式はTree(Top Down)です。
Self Time
列上部の▽アイコンをクリックすると表示形式を変更できます。Heavy (Bottom Up)
に変更すると「重たい」処理を特定するのが容易になります。
Chart
の表示はこんな感じです。時系列に処理を追いかけながらコールスタックや各処理の実行時間を追いかけるのに便利ですね。
まとめ
middy-profilerを使ってNode.jsのLambda functionをプロファイルしてみました。導入のハードルも低く、パフォーマンス問題を解析するには非常に有用なライブラリだと感じました。1点注意したいのはmiddyの思想的にhandlerをラップするような形でミドルウェアを組み込んでいくことになるので、プロファイル対象はhandlerとして指定された関数になることです。コールドスタート時の初期化処理をプロファイルしたいといった要件だと、また別の方法を検討する必要があります。幸いmiddy-profilerはかなり「薄い」ライブラリなので、middy-profilerのコードを参考に自前実装するのも比較的容易だと思います。