AWS Lambdaのhandler内外でのexitの挙動について

こんにちは。 エキサイト株式会社の三浦です。

AWSには、サーバーレスで処理を実行できる「AWS Lambda」というサービス(以降Lambda)があります。 Lambdaでは、実行の起点となるメソッド(handler)を指定することで、Lambdaの実行イベントが走ったときにそのhandlerメソッドが実行される、という流れになっています。

そのためコードの実装は基本的にはそのhandlerメソッドに書くことになるのですが、実は一部の状況ではあえてhandlerメソッドの外にコードを書くことがあります。

今回は、そういった状況で、handlerメソッドの内外で終了処理( exit )を実行したときの挙動の違いについて説明していきます。

Lambdaとは

LambdaはAWSのサービスの1つで、公式ページでは以下のように説明されています。

AWS Lambda は、サーバーレスでイベント駆動型のコンピューティングサービスであり、サーバーのプロビジョニングや管理をすることなく、事実上あらゆるタイプのアプリケーションやバックエンドサービスのコードを実行することができます。

Lambdaでは様々な言語でコードを書くことができるのですが、今回はPythonで試してみます。

言語をPythonで設定すると、以下のようなコードが自動生成されます。

import json

def lambda_handler(event, context):
    # TODO implement
    return {
        'statusCode': 200,
        'body': json.dumps('Hello from Lambda!')
    }

handlerメソッドは lambda_handler になっており、これをテスト実行してみると、以下のようなLogが出力されます。

Function Logs
START RequestId: xxxx Version: $LATEST
END RequestId: xxxx
REPORT RequestId: xxxx  Duration: 1.28 ms   Billed Duration: 2 ms   Memory Size: 128 MB Max Memory Used: 39 MB  Init Duration: 160.50 ms

handlerメソッドが lambda_handler であるため、このLambdaは実行のたびに lambda_handler が実行されることになります。

そのため、実装したいコードはその lambda_handler の中に書いていけば良いのですが、実は場合によっては lambda_handler の外に処理を書いたほうがいい場合もあります。

handlerメソッドの外に処理を書いたほうがいい場合

DBとの接続のコードのサンプルでは、以下のように書かれています。

ハンドラの外部で pymysql.connect() を実行すると、関数がデータベース接続を再利用できるようになるため、パフォーマンスが向上します。

実はhandlerメソッドの外で定義したコードは、handlerメソッド内に定義したコードと異なり、Lambda実行の度に毎回実行されることはありません。

代わりに、Lambdaのコンテナ(Lambdaの実態はコンテナです)が立ち上がった時の最初の一回しか実行されず、以降はそのコンテナが終了するまで結果を保持し続けるため、例えばDBとの接続のような一回実行すれば問題ないような処理を書くにはもってこいの場所になっています。

実際にサンプルコードで試してみます。

import json

print('external printing')

def lambda_handler(event, context):
    print('internal printing')

    return {
        'statusCode': 200,
        'body': json.dumps('Hello from Lambda!')
    }

このコードを実行してみると、初回実行時は以下のようなログが出力されます。

START RequestId: xxxx Version: $LATEST
external printing
internal printing
END RequestId: xxxx
REPORT RequestId: xxxx  Duration: 1.67 ms   Billed Duration: 2 ms   Memory Size: 128 MB Max Memory Used: 39 MB  Init Duration: 139.88 ms

二回目以降は以下のようになります。

Function Logs
START RequestId: xxxx Version: $LATEST
internal printing
END RequestId: xxxx
REPORT RequestId: xxxx  Duration: 1.21 ms  Billed Duration: 2 ms  Memory Size: 128 MB    Max Memory Used: 39 MB

初回にあった、handlerメソッドの外で定義している external printing の出力が、二回目以降は消えているのがわかります。

handlerメソッド内外でのexitの挙動

さて、突然ですが、コードを書く上で exit をしたい状況はたまに存在します。

例えば、本来なってほしくない結果が得られてしまったときなど、それ以降の処理を続行せずにそこで終わらせたいときなどです。

Lambdaでももちろん exit をすることは可能なのですが、上記の通りLambdaでのコードは、handlerメソッド内外で異なる挙動をします。 では、 exit実行時にはどのような違いがあるのでしょうか?

handlerメソッド内部での exit 実行

handlerメソッド内部で、以下のコードで exit を実行してみます。

import json
import sys

print('external printing')

def lambda_handler(event, context):
    print('internal printing')
    sys.exit(0)
    
    return {
        'statusCode': 200,
        'body': json.dumps('Hello from Lambda!')
    }

初回・二回目以降のログは以下のようになります。

初回

START RequestId: xxxx Version: $LATEST
external printing
internal printing
END RequestId: xxxx
REPORT RequestId: xxxx  Duration: 144.18 ms Billed Duration: 145 ms Memory Size: 128 MB Max Memory Used: 39 MB  Init Duration: 215.69 ms    
RequestId: xxxx Error: Runtime exited without providing a reason
Runtime.ExitError

二回目以降

START RequestId: xxxx Version: $LATEST
internal printing
END RequestId: xxxx
REPORT RequestId:xxxx   Duration: 104.91 ms Billed Duration: 105 ms Memory Size: 128 MB Max Memory Used: 11 MB  
RequestId: xxxx Error: Runtime exited without providing a reason
Runtime.ExitError

exit しない時と同様、二回目以降はhandlerメソッド外部の処理は呼ばれていないことがわかります。

handlerメソッド外部での exit 実行

handlerメソッド外部で、以下のコードで exit を実行してみます。

import json
import sys

print('external printing')
sys.exit(0)

def lambda_handler(event, context):
    print('internal printing')
    
    return {
        'statusCode': 200,
        'body': json.dumps('Hello from Lambda!')
    }

初回・二回目以降のログは以下のようになります。

初回

START RequestId: xxxx Version: $LATEST
external printing
external printing
END RequestId: xxxx
REPORT RequestId: xxxx  Duration: 1369.88 ms    Billed Duration: 1370 ms    Memory Size: 128 MB Max Memory Used: 11 MB  
RequestId: 19a1b5ac-519a-4d6e-bbaa-afee39439c57 Error: Runtime exited without providing a reason
Runtime.ExitError

二回目以降

START RequestId: xxxx Version: $LATEST
external printing
END RequestId: xxxx
REPORT RequestId: xxxx  Duration: 1328.81 ms    Billed Duration: 1329 ms    Memory Size: 128 MB Max Memory Used: 11 MB  
RequestId: xxxx Error: Runtime exited without providing a reason
Runtime.ExitError

初回に external printing が二回出てしまっているのは謎ですが、少なくとも二回目以降でも external printing が出ている、すなわちhandlerメソッド外部の処理が実行されているのがわかります。

上記の結果から、 exit をhandlerメソッド内部で実行するときと異なり、外部で実行する時は全体的に終了されていることがわかります。

DBとの接続設定など、handler外部での処理ごと終了させたい場合はhandler外部で exit を実行し、そうではなく毎回実行したい処理だけ終了したい場合はhandler内部で exit を実行すると良いでしょう。

最後に

Lambdaは便利ですが、上記のように細かい設定も存在します。 活用していけばよりパフォーマンスを改善したりすることもできるので、ぜひ気にかけていきましょう。