Lambda でPython のデコレーターが Cold Start に含まれるのか確認してみた

# はじめに

Lambda で Python を動かす際に、Lambda Handler にデコレーターを付けることができます。デコレーターを利用する理由は、ソースコードをクリーンにするために、冗長的な記述を共通化したり、ロジックを分離できる点などがあげられます。

今回の記事では、デコレーターが Python のコールドスタート、ウォームスタート部分にどのような影響が有るか X-Ray で確認していきます。次の 2 点の疑問を解消するための検証です。

- デコレーターで定義した関数に時間が掛かった場合、X-Ray の画面ではコールドスタートに加算されるのか？ウォームスタートの加算されるのか？
- デコレーターを定義したファイルで、グローバルスコープで import を行った場合、コールドスタートに加算されるのか？ウォームスタートの加算されるのか？

 

といいつつ結論から書くと、次の通りの結果になりました。

- デコレーターで定義した関数に時間が掛かった場合、**デコレーター部分の実行時間はウォームスタートに加算される**。
- デコレーターを定義したファイルで、グローバルスコープで import を行った場合、**コールドスタートに加算される**。



冷静に考えてみれば当たり前の結果でした。具体的な検証内容を次に残しておきます。



# SAM Project 作成

手元の環境に SAM が入っているので、sam init コマンドで適当にプロジェクトを作成します。SAM 利用方法の詳細は Google などで検索してみてください。

```
sam init
```



# Python コード準備

SAM で作成されたプロジェクト内で、デコレーター用のファイルを新規作成します。

![image-20230304190356630.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/34c53847-1e45-9b2e-e2fc-32590d445b20.png)



デコレーター定義ファイルとして、`decorator.py` の中身を次の内容にします。X-Ray で実行時間を把握するときにわかりやすくするため、以下の工夫を入れています。

- グローバルスコープで、時間のかかる Import を実行
- デコレーターの関数内で、Sleep 3 秒 を入れる

```python
import boto3
import time
import json
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import scipy.ndimage
import scipy.signal


def decorator_test(func):
    def wrapper(*args, **kwargs):
        print('--start--')
        time.sleep(3)
        func(*args, **kwargs)
        print('--end--')
        return {
            "statusCode": 200,
            "body": json.dumps({
                "message": "hello world",
            }),
        }
    return wrapper
```



Lambda Handler 定義ファイルとして、`app.py` の中身を以下の内容に変更します。

- デコレーターの定義
- import に掛かる時間を計測するために、print 分を仕込む

```python
import time

start_time = time.perf_counter()
import json
end_time = time.perf_counter()
elapsed_time = end_time - start_time
print(f"import json: {elapsed_time:.4f}秒")

start_time = end_time
from decorator import decorator_test
end_time = time.perf_counter()
elapsed_time = end_time - start_time
print(f"import decorator: {elapsed_time:.4f}秒")


@decorator_test
def lambda_handler(event, context):
    print('Hello Decorator!')
```







# X-Ray 情報

Lambda 関数で X-Ray のトレーシングを有効にすると、コールドスタートとウォームスタートにどれくらい時間が掛かっているか確認ができます。下の X-Ray の画像にある、Initialization がコールドスタートの一部が計測されています。Invocation はウォームスタート部分が計測されています。

![image-20230304195019115.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/e54af91d-e723-41e4-af2e-056ad11d5a15.png)



この時、`app.py`に仕込んだ print は次のように確認できます。import そのものに、3.8 秒ほど掛かっている様子が見えます。

![image-20230304205143984.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/f44df256-485b-eed8-e790-7555b0a6221d.png)



# Python コード変更

デコレーター定義ファイルとして、`decorator.py` の中身を次の内容にします。あえて時間が掛かるようにしていた import 文をコメントアウトします。

```python
# import boto3
import time
import json
# import numpy as np
# import matplotlib.pyplot as plt
# import matplotlib.animation as animation
# import scipy.ndimage
# import scipy.signal


def decorator_test(func):
    def wrapper(*args, **kwargs):
        print('--start--')
        time.sleep(3)
        func(*args, **kwargs)
        print('--end--')
        return {
            "statusCode": 200,
            "body": json.dumps({
                "message": "hello world",
            }),
        }
    return wrapper
```



このときの X-Ray の画面です。Initialization の部分が、3.95 秒から、104 ミリ秒に減っています。デコレーター側の Global Scope Import は、コールドスタートに影響を与えていることがわかります。

![image-20230304194035594.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/21d071af-7a4a-f1a5-e8de-d9f3f5c181c9.png)



この時、`app.py`に仕込んだ print は次のように確認できます。import そのものの時間が削減されていることがわかります。

![image-20230304212119469.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/8cf8af01-361f-6b8a-5493-14ed0898bf8e.png)