AWS Lambda (Python) のクラス変数の挙動メモ

Lambda Pythonが楽しい季節になって参りました。

クラスを書いていると変わった挙動に出会ったので、軽く調べてメモしておきます。



クラス変数の挙動試験

キッカケ

最初は、Pythonにおけるクラス変数とインスタンス変数の関係的なものを知らず、変数の初期化の役割をクラス変数にやらせていました。

それが、どうもLambdaで実行するとクラス変数の値の変更がキャッシュされてしまうことがあるようで、そうでないこともあるようで、色々と不審に思って調べ始めました。

テストコード

内容はこんな感じ。
  • 複数種類のクラス変数を定義
  • ホスト名を確認
  • 現在のクラス変数値とメモリアドレスを確認
  • クラス変数を更新
  • これを何度も実行する
  • 通常のLinuxでの実行結果

    お手元のテスト環境で lambda_handler() を1つ書いてを数回pythonコマンドで実行すると、出力内容はこんな感じ。

    当然、クラス変数は毎回初期化されます。

    Lambdaでの実行結果

    これがLambdaで実行すると、こうなります。

    これからわかることは、このような感じ。

  • 複数のホストで実行される。体感ではほぼランダム。今回は指定メモリも処理も小さいため最小の2台といったところ
  • 数値とタプルは毎回初期化される
  • リストと辞書は前回の値が残ったままになり、初期化されない
  • 1枚のコードでも zip でも挙動は同じ
  • ホストごとにプロセスが残っていると推測

  • コード更新後の挙動

    コードを更新すると、結果はこうなります。

    プロセスを作りなおしたっぽいのがわかります。

    原因と対策

    ここまできて、ようやく公式で見つけたのが、

    Q: AWS Lambda は関数インスタンスを再利用しますか?

    パフォーマンス向上のため、AWS Lambda は新しく関数のインスタンスを作成するのではなく、関数のインスタンスを保持してその後のリクエストに対応することがあります。ただし、常にインスタンスを再利用するわけではありません。


    ということで、内部ではプロセスを保持したまま lambda_handler() を何度も実行しているだけで、そのため残る仕様のものは残っている、というだけのことでした。新規に関数インスタンスを作成するのは、コード更新時は確実で、他にも時間経過とかあるかもしれませんが不明です。

    ちなみに、お手元Linuxで1処理の中で複数回 lambda_handler() を実行しても、以下のようにリストと辞書のクラス変数は値が残ったので、この仕様はPython独特のものなのでしょう。(Python初心者なのでこれ以上は突っ込まないようにしますが……)

    Pythonではクラス変数は不変のものとして扱い、変数の初期化は __init__() のインスタンス変数でやるのが基本のようなことをどこかで見たので、そのように書いていけば特に問題は出ないかと思います。

    データキャッシュ

    この挙動を突いて、外部からもってきたデータとかキャッシュできるやん☆って一瞬考えましたが、そんな黒魔術はやめたほうがいいですよね~ってことで、ついでによくある質問にあった /tmp について。

    Q: AWS Lambda 関数のためにディスクにスクラッチスペースが必要な場合はどうすればよいですか?

    各 Lambda 関数では /tmp ディレクトリに 500 MB の容量を割り当てることができます。


    500MBも使えるので、おとなしくここでmtimeでの読み書きキャッシュすることにしましょうかね、と。複数ホストで実行されるため、台数分はキャッシュを作ることになりますが、オンデマンド価格JSONなど、作り直しのタイミングがさほど重要じゃないものには十分でしょう。


    まとめると、『よくある質問』はちゃんと全部読みましょうってことです、ハイ。