【DynamoDB】GSIのマルチキーサポートは何が嬉しいのか考察してみた
はじめに
2025年11月19日、Amazon DynamoDB は Global Secondary Index(GSI)のマルチキーをサポートするというアップデートを発表しました。
以前はGSIのパーティションキーとソートキーがそれぞれ1属性に制限されていましたが、このアップデートによりパーティションキーに最大4属性、ソートキーに最大4属性(合計8属性)を使用できるようになりました。
本記事では、工場内のデバイスから収集されるセンサーデータを題材に、このアップデートで具体的に何が嬉しいのかを考察していきたいと思います。
対象読者
- DynamoDBの基本はある程度わかっている
- GSIのマルチキーサポートのアップデートが気になっている
シナリオ:IoTセンサーログの検索
工場内の大量のデバイス(deviceId)から、ステータス(正常/エラー)や温度データを秒単位で収集しているシステムを想定します。管理画面では以下の3つの検索ニーズがあるとします。
- アラート監視: 「デバイスA」の「エラー(ERROR)」のデータだけを即座に見たい。
- 月次トラブル分析: 「デバイスA」の「エラー」の「2025年11月」のデータを見たい。
- 閾値超過の調査: 「デバイス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を以下のように、クエリの条件を変えるだけで対応可能です。
- アラート監視(「デバイスA」の「エラー」を見たい)
特定のデバイスかつ、特定のステータスで絞り込みます。
- PK指定: deviceId = :d
- SK指定: status = :s
- 月次トラブル分析(エラー + 11月)
ステータスに加えて、年月までを「完全一致」で固定して取得します。
- PK指定: deviceId = :d
- SK指定: status = :s AND yearMonth = :ym
- 閾値超過の調査(「デバイスA」の「11月のエラー」のうち「温度80度以上」を見たい)
年月までを固定し、**最後の属性である温度だけを「範囲検索」**に指定します。
- PK指定: deviceId = :d
- SK指定: status = :s AND yearMonth = :ym AND temperature >= :t
まとめ
今回のアップデートによる、3つの嬉しいこと。
- 文字列結合コードの削除: アプリケーション側のロジックがシンプルに。
- 数値型の維持(正確な範囲検索): 数値を数値として扱える。
- コストと運用: 無駄な属性を排除してストレージ/WCUを節約し、バックフィルなしで検索パターンを拡張できる柔軟性。
感想と気づき
このアップデートを最初に見た際、
「GSIで8個の属性を指定できる」とだけ理解して、めっちゃ柔軟にクエリできるようになるのではと思いました。
ただ、以下部分が結構大事で、ここは理解しておいた方がいいなととても思いました。
マルチキーを使用したソートキー(SK)で範囲検索(> や >= など)を行う場合、以下のルールがあります。
- 「前の属性」はすべて一致していなければならない
- 「最後の属性」だけが範囲検索できる
コンソールから触ってみるとわかりやすいのですが、
GSIのSKでstatus + yearMonthで指定する際は、statusは完全一致のみ、yearMonthは範囲検索できる。

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

以前より対応できるアクセスパターン数が増えたのは真だと思いますが、めっちゃ柔軟にクエリできるようになったわけではなさそうです。
(具体的に数字で述べられたらいいのですが、そこまではまだ整理できてません)
NCDC株式会社( ncdc.co.jp/ )のテックブログです。 主にエンジニアチームのメンバーが投稿します。 募集中のエンジニアのポジションや、採用している技術スタックの紹介などはこちら( github.com/ncdcdev/recruitment )をご覧ください!
Discussion