Lambda ではホットスタートが続くときグローバル変数が共有される
Lambda ではある程度の短い間隔でリクエストが続く場合「ホットスタート」といって同一のインスタンスが処理を担い続けます。Lambda でだけ言えば「ウォームスタート」の方が一般的な用語かもしれません。
この 1 つの Lambda の単位で CloudWatch のログストリームも 1 つ分として構成されるのが基本ルールのようですし、何かと意識するタイミングは多いです。
実行速度など主にパフォーマンスの観点で語られることが多い概念ですが、それに関連して同一プロセス内で動作するような処理になるためにグローバルで定義した変数が共有されてしまうということがあるようです。
以下のようなコードを書いてしまったときにたまたま気が付きました。
from datetime import datetime
now = datetime.now()
def lambda_handler(event, context):
# do something
AWS 側が用意するログのタイムスタンプと datetime.now()
した時刻が全くもって合わないので「もしや?」と思えました。ちょうどいま初めて同じ内容でググってみたのですが、他にも全く同じパターンで気づいた方が多いみたいです(やっぱり現在時刻を冒頭に脳死で初期化するということ自体に問題を感じた…)。
ちなみに「グローバル」と言っているのは lambda_handler()
の外かどうか、と同義です。
なので対策としては「ロジックで使うような変数は全てメイン関数の中で宣言しましょう」ということになります。
from datetime import datetime
def lambda_handler(event, context):
now = datetime.now()
# do something
ただこれを裏返して言うと、DB とのコネクションや AWS SDK のセッションなど、パフォーマンス的に積極的に使いまわしたいものはグローバルで宣言した方がいいということになりますね(実際、これは AWS が推奨するベストプラクティスの中でも特によく言われているパターンです)。
RDS を使うときのパフォーマンス測定でコネクションを共有するかどうかで実行時間の差をテストしたことがあるのですが、2回目以降は劇的に早くなります。当然コールドスタート時(ある程度前回からのリクエストから時間が空いたとき)だけは初動が遅くなってしまいますが、全体論で考えたらほとんどのユーザーの体感速度を上げられるのでプラスしかありません。
ちなみに RDS の例でいうとVPC 内にある Lambda の起動は驚くほど遅いのでどちらかというとそちらの問題をケアしたほうがよいです(昔より遥かに改善されましたがまだ差はあります)。
handler の中に入れるべきものとそうではないものをよく検討できるとよさそうです。