AWS
CloudWatch
python3
DynamoDB
boto3

DynamoDBのCloudWatchメトリクス(キャパシティユニット)を盛大に誤解していた話

DynamoDBの監視をしたくて、CloudWatchメトリクスのキャパシティユニットを扱おうとしていたけど、公式ページを読んでもあまり理解できなく。。。

わかった気になって実装をしていたら、後から盛大に勘違いしていたことに気づいた話。

DynamoDB CloudWatchメトリクス(キャパシティユニット)

スクリーンショット 2018-06-08 8.51.35.png

これは読みに関する記述ですが、なんとなーくわかった気になれる書き方。

やりたかったこと

  • DynamoDBのキャパシティユニット使用量を確認し、プロビジョンドキャパシティユニットとして設定した値の閾値(例:80%)を超えたら通知を出す
  • 上述のために、キャパシティユニットの使用量を割合で出せるようにする。

実装のフロー

  1. 対象テーブルの読み/書きにおけるプロビジョンドキャパシティユニットをCloudWatchメトリクスから取得する。(ProvisionedWriteCapacityUnits, ProvisionedReadCapacityUnits)
  2. 実際のキャパシティユニットの使用量をCloudWatchメトリクスから取得する。(ConsumedWriteCapacityUnits, ConsumedReadCapacityUnits)
  3. No.1,2で取得した値を用いて、Metric Mathを利用してキャパシティユニットの使用量を算出する。

boto3を利用した実装方法

こんな感じで実装しようとしました。(仕様を誤解していたため、誤った実装です)
指定していた条件は以下。(書き込みキャパシティユニットの計算を想定)GetMetricData関数を使います。

  1. 1時間分のCloudWatchメトリクスを取得する
  2. 期間を5分で指定する
  3. 5分間の平均キャパシティユニットを取得する

(なんども書きますが、この実装は間違ってます)

response = cloudwatch_client.get_metric_data(
MetricDataQueries=[
  {
    'Id': 'm1',
    'MetricStat': {
        'Metric': {
            'Namespace': 'AWS/DynamoDB',
            'MetricName': 'ProvisionedWriteCapacityUnits',
            'Dimensions': [
                {
                    'Name': 'TableName',
                    'Value': TABLE_NAME
                },
            ]
        },
        'Period': 300,
        'Stat': 'Average',
        'Unit': 'Count'
    },
    'Label': 'ProvisionedWriteCapacityUnits'
  },
  {
    'Id': 'm2',
    'MetricStat': {
        'Metric': {
            'Namespace': 'AWS/DynamoDB',
            'MetricName': 'ConsumedWriteCapacityUnits',
            'Dimensions': [
                {
                    'Name': 'TableName',
                    'Value': TABLE_NAME
                },
            ]
        },
        'Period': 300,
        'Stat': 'Average',
        'Unit': 'Count'
    },
    'Label': 'ConsumedWriteCapacityUnits'
  },
  {
    'Id': 'calculationResult',
    'Expression': 'm2/m1'
    'Label': 'CalculationResult',
    'ReturnData': True
  }
],
StartTime=start_time,
EndTime=end_time
# StartTimeとEndTimeには1時間の開きを指定

間違いポイント

CloudWatchメトリクスのページに以下の記述があるのですが、この点を誤って理解していました。

  • Average – 消費されたリクエストごとの平均読み込みキャパシティー。

Averageは「消費されたリクエストごとの平均」を出すので、あくまでリクエスト毎の平均値を出すだけでした。
→5分間の間に10回のアクセスがあり、それぞれの使用ユニット量が「1, 2, 2, 3, 2, 2, 1, 1, 4, 3」ならAverageで取得した値は「2.1」になります(21 / 10)。この平均の計算に「期間」が利用されない、ということを理解していませんでした。

正しい算出方法

よくよく見れば「注記」に書かれていることをやればいいのですが、

(期間分のユニット使用量の合計値)/(期間[秒])

という計算をしてあげる必要があります。クエリで表すとこんな感じです。

response = cloudwatch_client.get_metric_data(
MetricDataQueries=[
  {
    'Id': 'm1_tmp',    # 変更点
    'MetricStat': {
        'Metric': {
            'Namespace': 'AWS/DynamoDB',
            'MetricName': 'ProvisionedWriteCapacityUnits',
            'Dimensions': [
                {
                    'Name': 'TableName',
                    'Value': TABLE_NAME
                },
            ]
        },
        'Period': 300,
        'Stat': 'Sum',   # 変更点
        'Unit': 'Count'
    },
    'Label': 'ProvisionedWriteCapacityUnits'
  },
  {
    'Id': 'm2',
    'MetricStat': {
        'Metric': {
            'Namespace': 'AWS/DynamoDB',
            'MetricName': 'ConsumedWriteCapacityUnits',
            'Dimensions': [
                {
                    'Name': 'TableName',
                    'Value': TABLE_NAME
                },
            ]
        },
        'Period': 300,
        'Stat': 'Sum',   # 変更点
        'Unit': 'Count'
    },
    'Label': 'ConsumedWriteCapacityUnits'
  },
# 下記のブロックを追加。
  {
    'Id': 'm1',
    'Expression': 'm1_tmp/300',
    'Label': 'm1_tmp',
  },
  {
    'Id': 'calculationResult',
    'Expression': 'm2/m1'
    'Label': 'CalculationResult',
    'ReturnData': True
  }
],
StartTime=start_time,
EndTime=end_time
# StartTimeとEndTimeには1時間の開きを指定