はじめに
AWS Lambdaを使用していた際に、/tmp
ディレクトリに置いたファイルが、次の実行に継承されていることに気がついたので、その仕組みをまとめる。
参考文献
AWS Lambdaの実行について
前提としてLambdaは、Lambda関数に対してリクエストが行われたとき、リクエストごとにインスタンス(実行環境)が生成され、そのインスタンスで処理が実行される。
このインスタンスの作成・実行の流れをもう少し詳しく分割すると以下のようになる。
1.実行環境の作成
2.デプロイパッケージのロード
3.デプロイパッケージの展開
4.ランタイム起動・初期化
5.関数/メソッドの実行
6.コンテナの破棄
この流れのうち、関数/メソッドの実行までの部分は、Lambda関数の設定を変更したり、コード内容を変えたりしない限りは毎回同じことを繰り返すことになり、非常に無駄の多い処理となってしまう。
このような無駄な処理を排除し、既に作成した実行環境を次回以降のリクエストでも使用できるようにしているのが、いわゆる「ウォームスタート」と呼ばれるものである。
ちなみに、上記のプロセスを全て実行するものは「コールドスタート」と呼ばれている。
AWS Lambdaでは、次回以降のリクエストが比較的短い時間内に行われ、かつ、Lambda関数の実行環境が変更されなかった場合、ウォームスタートで関数を実行する仕様になっているようだ。
ウォームスタート時の/tmpディレクトリについての検証
Lambdaには一時的に利用可能な/tmp
ディレクトリが用意されている。
今回は、ウォームスタート時にこの/tmp
ディレクトリ内のファイルがどうなるのかを検証する。
検証用コード
import glob
import os
def lambda_handler(event, context):
print(glob.glob('/tmp/*.txt', recursive=True)) # 実行時にあるファイルを表示
num = event['num']
with open(f'/tmp/{num}.txt', "w", encoding='UTF-8') as f:
f.write('testtest')
print(glob.glob('/tmp/*.txt', recursive=True)) # ファイル出力後にあるファイルを表示
上記のように、インプットに与えられたnum
に依存するファイル名を持つファイルを新規作成するというlambda関数で今回の検証を行う。
コールドスタートかどうかの判別方法
コードスタートかどうかはCloudWatchに出力されるログを見るとわかる。
REPORT RequestId: XXXXXXX Duration: 1.93 ms Billed Duration: 2 ms Memory Size: 1024 MB Max Memory Used: 52 MB Init Duration: 314.08 ms
ログの最後に、上記のようなInit Duration
という記述があればコールドスタートである。
検証
まず、num='1'
として実行してみると実行結果は以下のようになる。
START RequestId: XXXX Version: $LATEST
[]
['/tmp/1.txt']
END RequestId: XXXX
REPORT RequestId: XXXX Duration: 1.71 ms Billed Duration: 2 ms Memory Size: 1024 MB Max Memory Used: 36 MB Init Duration: 110.84 ms
次に、num='2'
として実行してみると実行結果は以下のようになる。
START RequestId: XXXX Version: $LATEST
['/tmp/1.txt']
['/tmp/2.txt', '/tmp/1.txt']
END RequestId: XXXX
REPORT RequestId: XXXX Duration: 2.09 ms Billed Duration: 3 ms Memory Size: 1024 MB Max Memory Used: 37 MB
実行結果から、ウォームスタート時は、/tmp
ディレクトリ内のファイルは継承されていることがわかる。
/tmpディレクトリ以下のファイルを継承させない方法
Lambda関数を実行したときに、/tmp
ディレクトリ内に意図しないファイルが残る可能性があるので、これの回避方法を考える。
例えば以下のように
import glob
import os
def lambda_handler(event, context):
print(glob.glob('/tmp/*.txt', recursive=True))
num = event['num']
with open(f'/tmp/{num}.txt', "w", encoding='UTF-8') as f:
f.write('testtest')
print(glob.glob('/tmp/*.txt', recursive=True))
os.remove(f'/tmp/{num}.txt')
print(glob.glob('/tmp/*.txt', recursive=True))
コードの最後に出力したファイルを削除するような処理を追加してあげればよい。(コードの最初に追加してもよい)
num='1'
のとき
START RequestId: XXXX Version: $LATEST
[]
['/tmp/1.txt']
[]
END RequestId: XXXX
REPORT RequestId: XXXX Duration: 1.69 ms Billed Duration: 2 ms Memory Size: 1024 MB Max Memory Used: 36 MB Init Duration: 102.89 ms
num='2'
のとき
START RequestId: XXXX Version: $LATEST
[]
['/tmp/2.txt']
[]
END RequestId: XXXX
REPORT RequestId: XXXX Duration: 2.71 ms Billed Duration: 3 ms Memory Size: 1024 MB Max Memory Used: 37 MB
このように、前回の実行時のファイルを継承しないことが可能となる。
まとめ
Lambdaのウォームスタート時は、前回実行時の/tmp
ディレクトリ内をそのまま継承することが明らかになった。この場合、意図しないファイルが次回実行に継承されてしまう恐れがあるので、そこを考慮してコーディングするのが良さそうだ。