#経緯
先日、「実行時の現在日付」を取得して、それをもとに処理をするみたいなLambda関数を書いてて、
少しはまったので共有。実行環境はnode.js 6.10。現在日付の取得にはnode.jsの日付処理ライブラリである
**Moment.js**を使用した。
...けど上記の環境じゃなくても起きるような事象だと思う。
#修正前
const moment = require('moment');
//現在日付を取得
const CURRENT_DATETIME = moment().format('YYYYMMDDHHmmssSSS');
exports.handler = function (event, context, callback) {
//CURRENT_DATETIME を使ってなんやかんやする。
...
}
##挙動
関数を何度かテスト実行しても現在日時を保持するCURRENT_DATETIMEの値が初回実行時の値から変化しない、
というものだった。
Lambdaに慣れた人にとっては、上記のコードは「こんなんちゃんと動かないに決まってんだろ」と思うような
モノかもしれないが、自分は最初このコードがおかしいということに気づくのに少し時間がかかってしまった。
というのもこのコード、ある程度短い間隔で連続実行されない限りはちゃんと動いてしまうためである。
##なぜ動かなかったか
多くの方がご存知の通り、AWS Lambdaは関数実行時にコンテナを生成し、そのコンテナ内で定義した関数を実行する。
というアーキテクチャになっている。また連続して呼び出された場合は、1度使用したコンテナを破棄せず、
次の関数呼び出し時にそのまま再利用することで、関数実行時のコンテナ生成のオーバーヘッドを減らすという
工夫が行われている。
ここで気にするべきなのが、この「定義した関数」というのはハンドラとして定義した関数(今回で言うとexports.handlerで
設定した無名関数)のことであるという点である。
上のコードを実際に動かした結果から想像するに、ハンドラ外に書かれたコード(requireとか定数定義とか)は、
コンテナ生成時に1度だけ処理され、以後同じコンテナ上では処理されないらしい...。
つまり今回のバグは、関数の実行に同じコンテナが再利用されることで、最初のコンテナ生成時に
初期化された現在日時の定数が使われ続けていた、というのが原因だったというわけである。
#修正後
const moment = require('moment');
exports.handler = function (event, context, callback) {
//現在日付を取得
const CURRENT_DATETIME = moment().format('YYYYMMDDHHmmssSSS');
//CURRENT_DATETIME を使ってなんやかんやする。
...
}
現在日時の初期化をハンドラ内に引っ越すだけでちゃんと動くようになった。
#まとめ
AWS Lambdaは非常に便利だが、最低限動作の原理を頭に入れていないと今回のようにバグを生んでしまうことになる。
今回の例以外にも、一時的なファイル入出力場所として使える/tmpディレクトリを利用する場合、
そこに前回実行時のゴミファイルが残っているみたいなことも想定できるので注意したい。