👻

【DynamoDB】GSIのマルチキーサポートは何が嬉しいのか考察してみた

に公開

はじめに

2025年11月19日、Amazon DynamoDB は Global Secondary Index(GSI)のマルチキーをサポートするというアップデートを発表しました。
以前はGSIのパーティションキーとソートキーがそれぞれ1属性に制限されていましたが、このアップデートによりパーティションキーに最大4属性、ソートキーに最大4属性(合計8属性)を使用できるようになりました。
https://aws.amazon.com/jp/about-aws/whats-new/2025/11/amazon-dynamodb-multi-attribute-composite-keys-global-secondary-indexes/
https://aws.amazon.com/jp/blogs/news/multi-key-support-for-global-secondary-index-in-amazon-dynamodb/

本記事では、工場内のデバイスから収集されるセンサーデータを題材に、このアップデートで具体的に何が嬉しいのかを考察していきたいと思います。

対象読者

  • DynamoDBの基本はある程度わかっている
  • GSIのマルチキーサポートのアップデートが気になっている

シナリオ:IoTセンサーログの検索

工場内の大量のデバイス(deviceId)から、ステータス(正常/エラー)や温度データを秒単位で収集しているシステムを想定します。管理画面では以下の3つの検索ニーズがあるとします。

  1. アラート監視: 「デバイスA」の「エラー(ERROR)」のデータだけを即座に見たい。
  2. 月次トラブル分析: 「デバイスA」の「エラー」の「2025年11月」のデータを見たい。
  3. 閾値超過の調査: 「デバイスA」の「エラー」の「2025年11月」のうち、さらに「温度が80度以上」の危険なデータだけを抽出したい。
  • データ構造のイメージ:
    {
      "deviceId": "DEV-001",
      "locationId": "LOC-A",
      "yearMonth": "2025-11",
      "status":"ERROR",
      "timestamp": "2025-11-20T10:00:00",
      "temperature": 85
    }
    

1. アプリケーション側での文字列結合が不要に

【Before】アプリケーション側で合成キーの作成が必要だった

以前はGSIのPKやSKに1属性しか指定できませんでした。
ニーズ3(特定のデバイス・ステータス・年月・温度)を満たすには、検索用の属性を文字列結合して作る必要がありました。

そのため、アプリケーション側で書き込み前に以下のような処理をしていました。

// 書き込み時:検索要件を満たすために、文字列結合
const item = {
  ...originalData,
  GSI_PK: deviceId, // "DEV-001"
  
  // 絞り込みたい属性を順番にすべて結合する。
  // 数値比較を壊さないよう、温度のゼロ埋め(パディング)も必須。
  GSI_SK: `${status}#${yearMonth}#${temperature.toString().padStart(5, '0')}`
  // 例: "ERROR#2025-11#00085"
};

辛かった点:

  • データ登録時に文字列結合のロジックが必要。
  • 後から「場所(locationId)も検索条件に加えたい」となった場合、既存の全データを更新(バックフィル)して合成キーを再生成する必要があった。

【After】マルチキーサポートによる直接指定

マルチキーサポートなら、文字列結合のロジックが不要になります。

// 各属性をそのまま指定してクエリを実行できる
const params = {
  IndexName: 'GSI2',
  KeyConditionExpression: 
    "deviceId = :d AND status = :s AND yearMonth = :ym AND temperature >= :t",
  ExpressionAttributeValues: {
    ":d": "DEV-001",
    ":s": "ERROR",
    ":ym": "2025-11",
    ":t": 80
  }
};

コンソール画面で見るとこんな感じ

嬉しいポイント:

  • アプリケーション側で文字列結合のコードを書く必要がない
  • 既存のテーブルにこのGSIを追加する場合でも、すでに属性が存在していればバックフィル(全件更新)なしでGSIを作成できる。

2. 数値型の維持(正確な範囲検索)

【Before】従来の手法

文字列結合による合成キーでは、数値の比較に問題がありました。
文字列の世界では "100" は "80" より小さいと判定されます(先頭の1と8を比較するため)。
これを防ぐには 00100 や 00080 のように、アプリ側で「ゼロパディング(桁埋め)」をする必要がありました。

辛かった点:

  • パディング処理をアプリ側でやらないといけないめんどくささ
  • そういった処理周りでバグが起きやすかった

【After】数値のまま扱える

マルチキーサポートでは、各属性のデータ型(String, Number, Binary)を維持したままインデックスを構成できます。
GSIのソートキー定義: yearMonth (String) + temperature (Number)

// 数値を数値として、そのまま範囲検索できる
const params = {
  TableName: 'IoTLogs',
  IndexName: 'GSI2',
  // ソートキー内で temperature を数値として比較
  KeyConditionExpression: "deviceId = :d AND status = :s AND yearMonth = :ym AND temperature >= :temp",
  ExpressionAttributeValues: {
    ":d": "DEV-001",
    ":s": "ERROR",
    ":ym": "2025-11",
    ":temp": 80 // 数値のまま渡せる
  }
};

嬉しいポイント:

  • 数値比較が正確: temperature >= 80 のような条件が、アプリケーション側での加工なしに期待通りに動作する。

3. ストレージ容量と書き込みコスト(WCU)の削減

【Before】検索用の「余計な属性」を持たせる必要があった

以前の仕様でも、合成キーを使えば1つのGSIでニーズ1〜3を満たせました。
しかし、そのためにはベーステーブルに検索専用の「重複したデータ」を持たなければなりませんでした。

// 書き込み時:ベーステーブルに検索専用の属性を追加する
const item = {
  deviceId: "DEV-001",
  status: "ERROR",
  yearMonth: "2025-11",
  temperature: 85,
  timestamp: "2025-11-20...",
  // これが必要:他の属性を結合した「GSI検索用のデータ」
  GSI_SK: "ERROR#2025-11#00085" 
};

辛かった点:

  • ストレージ料金の増加: 全てのレコードに GSI_SK という長い文字列が追加されます。扱っているデータ量にもよりますが、ストレージ料金を増加させる1つの要因にはなっていました。
  • 書き込みコスト(WCU)の増加: DynamoDBのWCUは「書き込むデータのサイズ」で決まります。検索用属性(GSI_SK)が増えることで、1リクエストあたりのデータ量が増え、書き込みコストを増加させていたかもしれない。(データ量による話ではある)
    ※1 KB の項目の書き込みリクエストには、1 つの WRU が必要

【After】既存の属性をそのまま再利用できる

マルチキーサポートの恩恵は、「GSIを作るために、ベーステーブルに新しい属性を書き込まなくて良い」という点があると思います。

GSI定義

  • パーティションキー(PK): deviceId
  • ソートキー(SK): status (String) → yearMonth (String) → temperature (Number)

何が嬉しいのか?

  • ベーステーブルがスリムに: 検索用の合成キー(GSI_SK)をレコードに含める必要がなくなります。これにより、ストレージコストを最小限に抑えられます。
  • 書き込みコストの最適化: 無駄な属性を書き込まないため、ベーステーブルへの PutItem 時の消費WCUを低減できます。
  • 後付けの自由度: 合成キーの場合、後から「場所(locationId)」でも検索したくなったら全件更新(バックフィル)して文字列を作り直す必要があります。マルチキーなら、すでにテーブルにある属性を組み合わせて新しいGSIを作るだけで済み、バックフィル作業にかかるコストも時間も節約できます。

ちなみにニーズ1~3を以下のように、クエリの条件を変えるだけで対応可能です。

  1. アラート監視(「デバイスA」の「エラー」を見たい)
    特定のデバイスかつ、特定のステータスで絞り込みます。
  • PK指定: deviceId = :d
  • SK指定: status = :s
  1. 月次トラブル分析(エラー + 11月)
    ステータスに加えて、年月までを「完全一致」で固定して取得します。
  • PK指定: deviceId = :d
  • SK指定: status = :s AND yearMonth = :ym
  1. 閾値超過の調査(「デバイスA」の「11月のエラー」のうち「温度80度以上」を見たい)
    年月までを固定し、**最後の属性である温度だけを「範囲検索」**に指定します。
  • PK指定: deviceId = :d
  • SK指定: status = :s AND yearMonth = :ym AND temperature >= :t

まとめ

今回のアップデートによる、3つの嬉しいこと。

  1. 文字列結合コードの削除: アプリケーション側のロジックがシンプルに。
  2. 数値型の維持(正確な範囲検索): 数値を数値として扱える。
  3. コストと運用: 無駄な属性を排除してストレージ/WCUを節約し、バックフィルなしで検索パターンを拡張できる柔軟性。

感想と気づき

このアップデートを最初に見た際、
「GSIで8個の属性を指定できる」とだけ理解して、めっちゃ柔軟にクエリできるようになるのではと思いました。
ただ、以下部分が結構大事で、ここは理解しておいた方がいいなととても思いました。

マルチキーを使用したソートキー(SK)で範囲検索(> や >= など)を行う場合、以下のルールがあります。

  • 「前の属性」はすべて一致していなければならない
  • 「最後の属性」だけが範囲検索できる

コンソールから触ってみるとわかりやすいのですが、
GSIのSKでstatus + yearMonthで指定する際は、statusは完全一致のみ、yearMonthは範囲検索できる。

「新しい属性の追加」を押して status + yearMonth + temperatureで指定する際は、
temperatureは範囲検索できるが、statusとyearMonthは完全一致のみになる。

以前より対応できるアクセスパターン数が増えたのは真だと思いますが、めっちゃ柔軟にクエリできるようになったわけではなさそうです。
(具体的に数字で述べられたらいいのですが、そこまではまだ整理できてません)

NCDC テックブログ

Discussion