middy-profilerでNode.jsのLambda functionをプロファイルしてみた

middy-profilerでNode.jsのLambda functionをプロファイルしてみた

ローカル環境では確認し辛いボトルネックの分析に便利そうです
Clock Icon2022.03.12

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

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のコードを参考に自前実装するのも比較的容易だと思います。

参考

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.