Lambda実行環境のTCPコネクション維持について調べてみた

CX事業本部の岩田です。

Lambdaのプログラミングモデルではデータベース接続等の処理はhandler外の初期化処理で実施することがベストプラクティスとされています。コストの高い処理は初期化処理内で実施することで、ウォームスタート時のパフォーマンスを向上させることができます。

一方で、handler内の処理が完了した後、次回のhandler呼び出しまでLambdaの実行環境は「フリーズ」することが知られています。

実際には、サービスは Lambda 関数の完了後実行コンテキストをフリーズさせ、再び Lambda 関数が呼び出された際に AWS Lambda がコンテキストを再利用する場合は、コンテキストを解凍して再利用します。

AWS Lambda 実行コンテキスト

初期化処理で確立したデータベースとの接続って、Lambda実行環境がフリーズしてる間はどうやって接続が維持されるんだろう??と疑問に思ったので、パケットキャプチャを行いながら検証してみました。

環境

今回利用した環境です

検証環境のざっくり構成

VPC内のEC2上にPostgresql10のDBサーバーを構築し、Lambdaの初期処理から接続しにいきます。 Lambda実行前からEC2の上ではtcpdumpによるパケットキャプチャを流しておき、後ほどWiresharkから分析します

ソースコード

これだけです。

import psycopg2
import time
import json

conn = psycopg2.connect(port=5432,
                        host='ip-172-31-41-239.ap-northeast-1.compute.internal',
                        database="lambda_db",
                        user="lambda_user",
                        password="lambda")

def lambda_handler(event, context):

    with conn.cursor() as cur:
        cur.execute('select now()')
        print(cur.fetchone())

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

Lambdaの初期化処理の中でEC2上のPostgresqlに接続し、明示的なクローズ処理は特に実装していません。EC2上で

tcpdump -vvv -i eth0  -s 0 -tttt port 5432 -w /home/ec2-user/lambda.cap

を実行しながら、

  1. Lambdaのテストイベント実行
  2. コンテナが破棄されるであろう時間放ったらかす

を繰り返します。

結果

こんな結果でした。

パケットキャプチャ結果1
  1. 9:58にLambdaからEC2(Postgresql)に接続
  2. 10:03にLambdaからEC2にKeep Aliveパケット送信 & EC2からACK
  3. 10:08にLambdaからEC2にFIN/ACK送信 & EC2もFIN/ACKで応答
  4. 10:08にLambdaからACKで応答

Lambda実行環境はフリーズしていると言いながら、実はTCPのKeepAliveパケットは送ってくれているようです。また、Lambda実行環境が破棄されるタイミングでも、マナー悪くブツ切りしている訳ではなくちゃんとFIN/ACKを送信してくれるようです。 接続が切れた後に再度Lambdaのテストイベントを実行し、X-Rayから確認したところInitializationが走っていたのでLambda実行環境のライフサイクルに合わせてTCPのコネクションを切断しているという理解で良さそうです。

検証結果の別パターンです。

パケットキャプチャ結果2
  1. 17:07にLambdaからEC2(Postgresql)に接続
  2. 17:12にLambdaからEC2にKeep Aliveパケット送信 & EC2からACK
  3. 17:17に再度LambdaからEC2にKeep Aliveパケット送信 & EC2からACK
  4. 17:17にLambdaからEC2にFIN/ACK送信 & EC2もFIN/ACKで応答
  5. 17:17にLambdaからACKで応答

最初に試した時とは違い、Keep Aliveパケットの往復が2回に増えています。 何度か試しましたが、いずれの場合もKeep Aliveパケットの往復が1回もしくは2回で接続が切れていました。

Postgresqlのパラメータtcp_keepalives_xxxxを変更した場合

さらにお試しでPostgresqlのパラメータを以下のように変更してみました

tcp_keepalives_idle = 30                # TCP_KEEPIDLE, in seconds;
tcp_keepalives_interval = 5             # TCP_KEEPINTVL, in seconds;
tcp_keepalives_count = 1                # TCP_KEEPCNT;

これでLambdaのテストイベント終了後、約30秒間隔でPostgresqlからLambda実行環境に対してKeep Aliveパケットが送信されることになります。Lambda実行環境はちゃんとACKを返してくれるのでしょうか??結果は以下のようになりました。

Postgressqlのパラメータtcp_keepalives_xxxxを変更した場合のパケットキャプチャ結果

ちゃんとACKを返してくれています!! 5分に1回一瞬だけフリーズを解除してKeep Aliveパケットを送っている訳ではなく、ちゃんとKeep Aliveパケットを受信できる体制も整っているようです。

まとめ

Lambdaの初期化処理内で確立された外部コンポーネントとのTCPコネクションはLambdaの実行基盤側が良い感じに接続を維持してくれるようです。おそらくWorkerもしくはWorker上のMicroVMが処理してくれているのでしょう!

また1つLambdaの裏側に詳しくなった気がします。