Lambdaで使用中のファイルディスクリプタを確認する

2023.06.09

Lambda関数のクォータの1つにファイルディスクリプタ:1024があります。今回、Lambdaで使用中のファイルディスクリプタ数を調査する機会がありましたので、その手順を紹介します。

背景

Lambda(Node.js)のInvoke ErrorとしてError: connect EMFILEとなる事象が発生しました。EMFILEエラーの原因として「/tmpディレクトリの容量」「ファイルディスクリプタ」の制限にかかっていることが考えられるとのことでしたので、使用中のファイルディスクリプタ数を確認することとなりました。

使用中のファイルディスクリプタ数を確認する

Lambdaで使用中のファイルディスクリプタ数はLambda Insightsを利用することでCloudWatch Logsに出力することが可能です。

Lambda Insightsを有効化

Lambdaの管理コンソールから対象となる関数を選択し、[設定]タブより[モニタリングおよび運用ツール]を開き、右上の[編集]を開きます。

モニタリングツールの編集ウィンドウが開きますので、[CloudWatch Lambda インサイト]の[拡張モニタリング]を有効化し、[保存]します。

CloudWatch Lambda インサイトを有効化するとLambdaレイヤーにLambdaInsightsExtensionというレイヤーが自動的に追加されます。

これで準備は完了です。

Lambda Insightsの出力を確認

Lambda Insightsの出力を確認するため、対象の関数を実行します。その後、CloudWatch管理コンソールの左ペインより、[インサイト]-[Lambda Insights]を開きます。マルチファンクションモードの表示になっている場合、[関数の概要]欄に先程Lambda Insightを有効化した関数のサマリーが表示されています。(出力に少々時間が掛かることがあります)

対象の関数を選択し、[パフォーマンスログを表示]を開きます。

CloudWatch Logs Insightsの画面が開きますので、デフォルトで表示されている以下のクエリを実行します。Lambda Insightsのログはロググループ/aws/lambda-insightsに出力されます。

fields @timestamp, @message, @logStream
| filter function_name = "function-name"
| sort @timestamp desc
| limit 20

使用中のファイルディスクリプタ数のメトリクスはfd_useになりますので、以下の例ではファイルディスクリプタを20使用していることが判ります。

出力例

[
    {
        "@timestamp": "2023-06-08 13:13:12.905",
        "@message": {
            "tx_bytes": 20007,
            "cold_start": false,
            "agent_version": "1.0.229.0",
            "cpu_user_time": 70,
            "agent_memory_max": 9,
            "billed_duration": 1703,
            "fd_use": 20,
            "threads_max": 10,
            "total_network": 68168,
            "cpu_total_time": 90,
            "billed_mb_ms": 217984,
            "tmp_free": 538324992,
            "total_memory": 128,
            "rx_bytes": 48161,
            "tmp_max": 550461440,
            "tmp_used": 12136448,
            "function_name": "function-name",
            "event_type": "performance",
            "cpu_system_time": 20,
            "_aws": {
                "CloudWatchMetrics": [
                    {
                        "Namespace": "LambdaInsights",
                        "Dimensions": [
                            [
                                "function_name"
                            ]
                        ],
                        "Metrics": [
                            {
                                "Name": "memory_utilization",
                                "Unit": "Percent"
                            },
                            {
                                "Name": "total_memory",
                                "Unit": "Megabytes"
                            },
                            {
                                "Name": "used_memory_max",
                                "Unit": "Megabytes"
                            },
                            {
                                "Name": "cpu_total_time",
                                "Unit": "Milliseconds"
                            },
                            {
                                "Name": "tx_bytes",
                                "Unit": "Bytes"
                            },
                            {
                                "Name": "rx_bytes",
                                "Unit": "Bytes"
                            },
                            {
                                "Name": "total_network",
                                "Unit": "Bytes"
                            },
                            {
                                "Name": "tmp_used",
                                "Unit": "Bytes"
                            }
                        ]
                    }
                ],
                "Timestamp": 1686229655885,
                "LambdaInsights": {
                    "ShareTelemetry": true
                }
            },
            "used_memory_max": 65,
            "request_id": "ca90dbb7-d533-4920-9890-fe5ca9b48423",
            "fd_max": 1024,
            "duration": 1702,
            "agent_memory_avg": 9,
            "memory_utilization": 50
        },
        "@logStream": "function-name/fZpF5gCwBoxnyMJCpCUtC0gAl"
    }
]

使用中のファイルディスクリプタ数を可視化

Lambda Insightsで取得されるメトリクスの内、CloudWatch メトリクスとして時系列集計データとして可視化されるのはLambdaInsightsネームスペースに登録される以下の8つのみです。

メトリクス名 ディメンション 説明
cpu_total_time function_name
function_name、バージョン
cpu_system_time と cpu_user_time の合計
単位: ミリ秒
init_duration function_name
function_name、バージョン
Lambda 実行環境のライフサイクルの init フェーズで費やされた時間
単位: ミリ秒
memory_utilization function_name
function_name、バージョン
最大メモリは、関数に割り当てられたメモリのパーセンテージとして測定されます
単位: パーセント
rx_bytes function_name
function_name、バージョン
関数の受信バイト数
単位: バイト
tx_bytes function_name
function_name、バージョン
関数の送信バイト数
単位: バイト
total_memory function_name
function_name、バージョン
Lambda 関数に割り当てられたメモリの量。これは関数のメモリサイズと同じです
単位: メガバイト
total_network function_name
function_name、バージョン
rx_bytes と tx_bytes の合計。I/O タスクを実行しない関数の場合でも、Lambda ランタイムによって行われるネットワーク呼び出しのため、この値は通常 0 より大きくなります
単位: バイト
used_memory_max function_name
function_name、バージョン
関数サンドボックスの測定されたメモリ
単位: メガバイト

残念ながらfd_useLambdaInsightsネームスペースに登録されておらず、そのままでは可視化されません。可視化するには以下のように、CloudWatch Insightsで[可視化]タブに切り替え、クエリを実行します。

今回はLambdaの実行中にファイルディスクリプタ上限に達していたかどうかを確認したいため、1分毎のログフィールドfd_useの最大値を集約関数を利用して表示したいので、以下のようなクエリを実行します。

stats max(fd_use) by bin(1m)

他の統計集計関数等についてはドキュメントを参照ください。

以下は検証環境の例ですが、ファイルディスクリプタ数の最大値が可視化されています。もし、この値が1024に達しているようであれば、EMFILEの原因はファイルディスクリプタ上限に刺さったことだと特定できますので、次に、なぜファイルディクリプタ上限に刺さってしまったのか、、とトラブルシューティングを進めることができます。

その他のメトリクス

fd_useの他にもLambdaInsightsネームスペースに登録されないメトリクスは以下のとおりとなっています。Lambda Insightsを利用の際にはCloudWatchメトリクスで表示されていないメトリクスもある、ということを覚えておくと良いでしょう。

メトリクス名 説明
cpu_system_time CPU がカーネルコードの実行に費やした時間
単位: ミリ秒
cpu_user_time CPU がユーザーコードの実行に費やした時間
単位: ミリ秒
fd_max 使用可能なファイル記述子の最大数
単位: 個
fd_use 使用中のファイル記述子の最大数
単位: 個
threads_max 関数プロセスで使用中のスレッドの数。関数の作成者は、ランタイムによって作成されたスレッドの初期数を制御しません
単位: 個
tmp_max /tmpディレクトリで使用可能な領域の量
単位: バイト
tmp_used /tmpディレクトリ内で使用される領域の量
単位: バイト

以上、誰かのお役にたてば幸いです。