1回の Lambda 実行で DynamoDB テーブルに複数回アクセスした際の AWS X-Ray のトレース結果を確認してみた

2023.11.01

こんにちは、CX 事業本部 Delivery 部の若槻です。

AWS X-Ray SDK for Node.js を使うと、AWS Lambda 関数などから AWS SDK を使用して AWS サービスにアクセスした際のトレースデータを AWS X-Ray へ送信することができます。

そして X-Ray では、コンソール上で トレースマップ により、サービス間のリクエスト経路や所要時間をグラフィカルに表示させることができます。

Using the service map - AWS X-Ray より

今回は、1 回の Lambda 実行で DynamoDB テーブルに複数回アクセスした際に、 AWS X-Ray のトレースマップの表示がどのようになるのか気になったので確認してみました。

確認してみた

次の 4 つのパターンで確認をしてみます。

  1. Func1: 1 つのテーブルへ put-item を 1 回行う場合
  2. Func2: 1 つのテーブルへ put-item を 2 回行う場合
  3. Func3: 2 つのテーブルへ put-item を 1 回ずつ行う場合
  4. Func4: 1 つのテーブルへ put-item と get-item を 1 回ずつ行う場合

CDK コード

検証に必要なリソースとなる 2 つの DynamoDB テーブルと 4 つの Lambda 関数を AWS CDK で作成します。

lib/cdk-sample-stack.ts

import {
  aws_lambda,
  aws_lambda_nodejs,
  aws_dynamodb,
  Stack,
  StackProps,
} from 'aws-cdk-lib';
import { Construct } from 'constructs';

export class CdkSampleStack extends Stack {
  constructor(scope: Construct, id: string, props: StackProps) {
    super(scope, id, props);

    const table1 = new aws_dynamodb.Table(this, 'Table1', {
      tableName: 'table1',
      partitionKey: {
        name: 'id',
        type: aws_dynamodb.AttributeType.STRING,
      },
      billingMode: aws_dynamodb.BillingMode.PAY_PER_REQUEST,
    });

    const table2 = new aws_dynamodb.Table(this, 'Table2', {
      tableName: 'table2',
      partitionKey: {
        name: 'id',
        type: aws_dynamodb.AttributeType.STRING,
      },
      billingMode: aws_dynamodb.BillingMode.PAY_PER_REQUEST,
    });

    // Func1: 1つのテーブルへ put-item を1回行う Lambda 関数
    const func1 = new aws_lambda_nodejs.NodejsFunction(this, 'Func1', {
      functionName: 'func1',
      architecture: aws_lambda.Architecture.ARM_64,
      environment: {
        TABLE_1_NAME: table1.tableName,
      },
      tracing: aws_lambda.Tracing.ACTIVE, // AWS X-Ray によるトレースを有効化
    });
    table1.grantWriteData(func1);

    // Func2: 1つのテーブルへ put-item を2回行う Lambda 関数
    const func2 = new aws_lambda_nodejs.NodejsFunction(this, 'Func2', {
      functionName: 'func2',
      architecture: aws_lambda.Architecture.ARM_64,
      environment: {
        TABLE_1_NAME: table1.tableName,
      },
      tracing: aws_lambda.Tracing.ACTIVE, // AWS X-Ray によるトレースを有効化
    });
    table1.grantWriteData(func2);

    // Func3: 2つのテーブルへ put-item を1回ずつ行う Lambda 関数
    const func3 = new aws_lambda_nodejs.NodejsFunction(this, 'Func3', {
      functionName: 'func3',
      architecture: aws_lambda.Architecture.ARM_64,
      environment: {
        TABLE_1_NAME: table1.tableName,
        TABLE_2_NAME: table2.tableName,
      },
      tracing: aws_lambda.Tracing.ACTIVE, // AWS X-Ray によるトレースを有効化
    });
    table1.grantWriteData(func3);
    table2.grantWriteData(func3);

    // Func4: 1つのテーブルへ put-item と get-item を1回ずつ行う Lambda 関数
    const func4 = new aws_lambda_nodejs.NodejsFunction(this, 'Func4', {
      functionName: 'func4',
      architecture: aws_lambda.Architecture.ARM_64,
      environment: {
        TABLE_1_NAME: table1.tableName,
      },
      tracing: aws_lambda.Tracing.ACTIVE, // AWS X-Ray によるトレースを有効化
    });
    table1.grantReadWriteData(func4);
  }
}

Func1: 1つのテーブルへ put-item を1回行う場合

1 つのテーブルへ put-item を 1 回行う場合です。

lib/cdk-sample-stack.Func1.ts

import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import { DynamoDBDocument } from '@aws-sdk/lib-dynamodb';
import { captureAWSv3Client } from 'aws-xray-sdk';

const TABLE_1_NAME = process.env.TABLE_1_NAME || '';

const dynamodbClient = captureAWSv3Client(
  new DynamoDBClient({
    region: 'ap-northeast-1',
    apiVersion: '2012-08-10',
  })
);
const doc = DynamoDBDocument.from(dynamodbClient);

export const handler = async (): Promise<void> => {
  await doc.put({
    TableName: TABLE_1_NAME,
    Item: {
      id: '1',
      name: 'name1',
    },
  });
};

関数の実行結果を X-Ray で確認すると、トレースマップには DynamoDB テーブルを表すノードが** 1 つのみ**表示されています。

Func2: 1つのテーブルへ put-item を2回行う場合

続いて、1 つのテーブルへ put-item を 2 回行う場合です。

import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import { DynamoDBDocument } from '@aws-sdk/lib-dynamodb';
import { captureAWSv3Client } from 'aws-xray-sdk';

const TABLE_1_NAME = process.env.TABLE_1_NAME || '';

const dynamodbClient = captureAWSv3Client(
  new DynamoDBClient({
    region: 'ap-northeast-1',
    apiVersion: '2012-08-10',
  })
);
const doc = DynamoDBDocument.from(dynamodbClient);

export const handler = async (): Promise<void> => {
  await doc.put({
    TableName: TABLE_1_NAME,
    Item: {
      id: '1',
      name: 'name1',
    },
  });

  await doc.put({
    TableName: TABLE_1_NAME,
    Item: {
      id: '1',
      name: 'name1',
    },
  });
};

関数の実行結果を X-Ray で確認すると、トレースマップには DynamoDB テーブルを表すノードが** 1 つのみ**表示されています。リクエストの回数だけノードが表示されるわけでは無いようです。

一方でセグメントタイムラインには 2 回分の put-item の履歴が表示されています。

Func3: 2つのテーブルへ put-item を1回ずつ行う場合

続いて、2 つのテーブルへ put-item を 1 回ずつ行う場合です。

lib/cdk-sample-stack.Func3.ts

import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import { DynamoDBDocument } from '@aws-sdk/lib-dynamodb';
import { captureAWSv3Client } from 'aws-xray-sdk';

const TABLE_1_NAME = process.env.TABLE_1_NAME || '';
const TABLE_2_NAME = process.env.TABLE_2_NAME || '';

const dynamodbClient = captureAWSv3Client(
  new DynamoDBClient({
    region: 'ap-northeast-1',
    apiVersion: '2012-08-10',
  })
);
const doc = DynamoDBDocument.from(dynamodbClient);

export const handler = async (): Promise<void> => {
  await doc.put({
    TableName: TABLE_1_NAME,
    Item: {
      id: '1',
      name: 'name1',
    },
  });

  await doc.put({
    TableName: TABLE_2_NAME,
    Item: {
      id: '1',
      name: 'name1',
    },
  });
};

関数の実行結果を X-Ray で確認すると、トレースマップには DynamoDB テーブルを表すノードが** 2 つ**表示されています。リクエスト先のテーブルリソースの数だけノードが表示されるようです。

Func4: 1つのテーブルへ put-item と get-item を1回ずつ行う場合

最後に、1 つのテーブルへ put-item と get-item を 1 回ずつ行う場合です。

lib/cdk-sample-stack.Func4.ts

import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import { DynamoDBDocument } from '@aws-sdk/lib-dynamodb';
import { captureAWSv3Client } from 'aws-xray-sdk';

const TABLE_1_NAME = process.env.TABLE_1_NAME || '';

const dynamodbClient = captureAWSv3Client(
  new DynamoDBClient({
    region: 'ap-northeast-1',
    apiVersion: '2012-08-10',
  })
);
const doc = DynamoDBDocument.from(dynamodbClient);

export const handler = async (): Promise<void> => {
  await doc.put({
    TableName: TABLE_1_NAME,
    Item: {
      id: '1',
      name: 'name1',
    },
  });

  await doc.get({
    TableName: TABLE_1_NAME,
    Key: {
      id: '1',
    },
  });
};

Func2 の場合と同様に、関数の実行結果を X-Ray で確認すると、トレースマップには DynamoDB テーブルを表すノードが** 1 つのみ**表示されています。リクエストの種類が異なる場合でも、リクエスト先のテーブルリソースに対応するノードのみが表示されるようです。

一方でセグメントタイムラインには put-item と get-item のいずれの履歴も表示されています。

まとめ

1 回の Lambda 実行で DynamoDB テーブルに複数回アクセスした場合のトレースマップでのテーブルリソースのノードの表示は以下のようになる。

  • リクエスト対象のテーブルリソースの数だけノードが表示される
  • リクエストの回数や種類が複数の場合でも、同一のテーブルリソースに対応するノードが複数表示されることはない

トレースマップにより Lambda からの AWS サービス/リソースへのアクセスがすべてグラフィカルに可視化されると思いましたが、飽くまでセグメントタイムラインの補助として見るべきもののようです。

以上