どーも!shihopowerです!
SAPの勉強をしていると、こんな問題に出会いました。
「Lambda 関数のイベントハンドラー外にデータベース接続を開くコードを移動する」
…ハンドラーの外って何?内側と外側って何が違うの?🤔
「Lambda を使う」というのはなんとなくわかっていても、ハンドラーの構造まで意識したことがなかったので、AWS公式ドキュメントをもとにしっかり調べてみました!
目次
1. Lambda 関数のイベントハンドラーとは?
定義
AWS公式ドキュメントによると、Lambda 関数のハンドラーは以下のように定義されています。
Lambda 関数のハンドラーとは、イベントを処理する関数コード内のメソッドです。関数が呼び出されると、Lambda はハンドラーメソッドを実行します。ハンドラーがレスポンスを返すか、終了するか、タイムアウトするまで関数は実行され続けます。
つまり、ハンドラーとは 「Lambda 関数が呼び出されたときに最初に実行されるエントリーポイント(入口)となるメソッド」 です。
ハンドラーが受け取る引数
ハンドラーは以下の2つの引数を受け取ります。
| 引数 | 内容 |
|---|---|
event |
関数に渡される入力データ。呼び出し元(API Gatewayなど)からのリクエスト情報が含まれる |
context |
呼び出し・関数・実行環境に関する情報が含まれる |
コードで表すと以下のようになります(Node.js の例)。
export const handler = async (event, context) => {
// ここに処理を書く
return 'Success';
};
ハンドラーの命名規則
ハンドラーを設定するときは、ファイル名と関数名をドットで繋いだ形式で指定します。
index.handler
↑ファイル名 ↑関数名
AWS コンソールでは index.handler がデフォルト設定になっています。
2. ハンドラーの「内側」と「外側」
ここが今回の記事の核心です!
Lambda 関数のコードは大きく 「ハンドラーの外側」 と 「ハンドラーの内側」 の2つに分かれます。
コードで見る構造
// ========================================
// ハンドラーの「外側」
// ========================================
import { S3Client } from '@aws-sdk/client-s3';
// SDKクライアントやDB接続はここで初期化する(外側)
const s3Client = new S3Client();
// ========================================
// ハンドラーの「内側」
// ========================================
export const handler = async (event, context) => {
// ↑ ここから「内側」
// 呼び出しのたびに毎回実行される処理
const bucketName = process.env.RECEIPT_BUCKET;
// ...メインの処理...
return 'Success';
// ↓ ここまで「内側」
};
内側と外側の実行タイミングの違い
| 実行タイミング | |
|---|---|
| 外側 | Lambda の初期化フェーズに1回だけ実行される |
| 内側 | 関数が呼び出されるたびに毎回実行される |
初期化フェーズって何?
Lambda 関数が最初に呼び出されるとき、Lambda はまず実行環境を準備します。このことを初期化フェーズといいます。
公式ドキュメントには以下のように記載されています。
Lambda は静的コードを、最初に関数を呼び出す前の初期化フェーズに実行します。初期化時に作成されたリソースはメモリ上に保持され、複数の呼び出しをまたいで再利用できます。
ここでいう「静的コード」が、ハンドラーの外側に書かれたコードのことです。
3. なぜ「外側」にコードを置くことが重要なのか?
実行環境の再利用という仕組み
Lambda は一度起動した実行環境を、次の呼び出しでも再利用することがあります。
公式ドキュメントでは以下のように推奨されています。
実行環境の再利用を活用してパフォーマンスを向上させましょう。SDK クライアントとデータベース接続をファンクションハンドラーの外側で初期化し、静的アセットを
/tmpディレクトリにキャッシュしてください。同じ関数のインスタンスで処理される後続の呼び出しはこれらのリソースを再利用でき、関数の実行時間が短縮されコストが削減されます。
DB接続を「内側」に書くと何が起きる?
DB接続コードをハンドラーの内側に書いてしまうと、関数が呼び出されるたびに毎回新しい接続を開くことになります。
export const handler = async (event) => {
// ❌ 毎回接続を開いてしまう(内側に書いた場合)
const connection = await openDatabaseConnection();
const result = await connection.query('SELECT * FROM products');
return result;
};
ホリデーシーズンのような高負荷時には Lambda が大量に並列実行されるため、その分だけ大量のDB接続が同時に開かれ、接続の遅延やエラーが発生します。
DB接続を「外側」に書くと何が変わる?
// ✅ 外側で一度だけ接続を初期化する
const connection = await openDatabaseConnection();
export const handler = async (event) => {
// 接続を再利用するだけ
const result = await connection.query('SELECT * FROM products');
return result;
};
実行環境が再利用されるとき、外側で初期化した接続もそのまま使い回されるため、毎回接続を開く必要がなくなります。
内側・外側の使い分けまとめ表
| コードの種類 | 置く場所 | 理由 |
|---|---|---|
| SDKクライアントの初期化 | 外側 | 再利用することでオーバーヘッドを削減 |
| DB接続 | 外側 | 毎回接続を開く遅延・エラーを防ぐ |
| 環境変数の参照 | 外側でも内側でも可 | 変わらない値は外側でキャッシュしても良い |
| リクエストごとに変わる処理 | 内側 | 呼び出しのたびに異なる入力を処理する必要がある |
| ユーザーデータの保存 | 内側(外側はNG) | 呼び出しをまたいでデータが漏洩するリスクがある |
⚠️ 注意点
公式ドキュメントでは、実行環境(外側)にユーザーデータやセキュリティ上重要な情報を保存しないよう注意が促されています。呼び出しをまたいでデータが漏洩する可能性があるためです。
4. まとめ
Lambda 関数のイベントハンドラーについて整理すると以下のようになります。
| 項目 | 内容 |
|---|---|
| ハンドラーとは | 関数が呼び出されたときに実行されるエントリーポイントのメソッド |
| 受け取る引数 |
event(入力データ)・context(実行環境情報) |
| 外側のコード | 初期化フェーズに1回だけ実行・複数の呼び出しをまたいで再利用される |
| 内側のコード | 呼び出しのたびに毎回実行される |
| DB接続を外側に置く理由 | 実行環境の再利用により接続を使い回せ、遅延・エラーを防げるため |
SAPの問題で「Lambda 関数のイベントハンドラー外にDB接続コードを移動する」という選択肢が出てきたとき、この記事の内容が頭に浮かんでくれたら嬉しいです😊
この記事が少しでも役に立てたら嬉しいです!引き続きSAP/SAA対策の記事を書いていくのでよろしくです〜!
参考:AWS公式ドキュメント