Lambdaタイムアウト時にX-Rayサブセグメントが記録されないことがあるので注意しよう

Lambdaがタイムアウトした際にX-Rayの想定していたセグメントが取得できていない時があったので、共有します。
2024.05.21

私が気づいたのはLambdaがタイムアウトした時でしたが、実際の条件は

「サブセグメントをクローズせずにLambdaが終了した場合」

です。

条件と注意点について、詳しく説明していきます。

今回のコード全体はこちらに載せています。

クローズを忘れてしまいサブセグメントが記録されないコード例

良い例と悪い例を紹介します。

良い例

export const handler = async (event: { sleepTime: number }) => {
  const segment = AWSXRay.getSegment();

  // 1000msのセグメントを取る
  const subSegment = segment?.addNewSubsegment("close");
  // ~~~何かしらの処理~~~ //
  subSegment?.close();

  return {
    statusCode: 200,
    body: JSON.stringify({
      message: "Hello from Lambda!",
    }),
  };
}

悪い例

export const handler = async (event: { sleepTime: number }) => {
  const segment = AWSXRay.getSegment();

  const notClosedSegment = segment?.addNewSubsegment("notClosed");

  return {
    statusCode: 200,
    body: JSON.stringify({
      message: "Hello from Lambda!",
    }),
  };
}

こちらのコードでは、segmentのcloseをしていないため、記録されません。

タイムアウトでサブセグメントが記録されないコード例

前例のコードのように、X-Rayを利用している方で close を書き忘れることはあまりないと思いますが、Lambdaのタイムアウトでも同じ事象が起こります。

以下のようなコードを書き、Lambdaをタイムアウトさせてみます。

Lambda自体のタイムアウトは10秒に設定しました。

import * as AWSXRay from "aws-xray-sdk-core";

export const handler = async (event: { sleepTime: number }) => {
  const segment = AWSXRay.getSegment();

  // 1000msのセグメントを取る
  const initialSegment = segment?.addNewSubsegment("init");
  await new Promise((resolve) => setTimeout(resolve, 1000));
  initialSegment?.close();

  // eventの指定秒数のセグメントを取る
  const sleepSegment = segment?.addNewSubsegment("sleep");
  if (event.sleepTime > 0)
    await new Promise((resolve) => setTimeout(resolve, event.sleepTime));
  sleepSegment?.close();

  return {
    statusCode: 200,
    body: JSON.stringify({
      message: "Hello from Lambda!",
    }),
  };
};

タイムアウトしない場合の結果

event

{
  "sleepTime": 2000
}

実行結果

xrayセグメントの画像1

タイムアウトした場合の結果

event

{
  "sleepTime": 11000
}

実行結果

xrayのセグメント

取ろうとしていたサブセグメントが取れていないことが確認できます。

起こりうるケース: 外部APIがタイムアウト

実際に起こりうるケースとして、外部APIへの接続でタイムアウトを設定しておらず、Lambdaのタイムアウトに陥ってしまいサブセグメントが取れないことがあります。

AWSXRay.captureHTTPsGlobal を利用すると、外部APIへの接続のセグメントを自動で取得してくれるようになります。

import * as AWSXRay from "aws-xray-sdk-core";
import * as http from "http";
import * as https from "https";
import axios from "axios";

export const handler = async (event: { sleepTime: number }) => {
  AWSXRay.captureHTTPsGlobal(http);
  AWSXRay.captureHTTPsGlobal(https);

  const reponse = await axios.get("https://ここがタイムアウトする.com");

  return {
    statusCode: 200,
    body: JSON.stringify({
      message: "Hello from Lambda!",
    }),
  };
};

この場合、axiosのタイムアウトを設定していないことが問題なので、適切にタイムアウトを設定しましょう。