Amazon Web Services ブログ

Amazon DynamoDB のグローバルセカンダリインデックスにおけるマルチキーサポート

本記事は 2025 年 11 月 20 日に公開された “Multi-key support for Global Secondary Index in Amazon DynamoDB” を翻訳したものです。


Amazon DynamoDB は、グローバルセカンダリインデックス (GSI) の複合キーで最大 8 つの属性をサポートするようになりました。これで、GSIの一部としてアイテムを識別するために最大4つのパーティションキーと4つのソートキーを指定できるようになり、複数の次元に及ぶ大規模なデータにクエリできるようになります。

Amazon DynamoDB は、サーバーレスでフルマネージドの分散型 NoSQL データベースであり、あらゆるスケールで 1 桁ミリ秒のパフォーマンスを実現します。DynamoDB の主要な機能の 1 つは、スキーマの柔軟性です。プライマリキー以外はすべてオプションです。パーティションキー (PK) と、オプションでソートキー (SK) を持つシンプルなテーブルから始めることができます。どちらも文字列属性として定義すれば、すぐにデータモデルの構築を開始できます。

アプリケーションでは、カーディナリティを高めるためにパーティションキーに複数の属性を使用する必要がある場合があります。同様に、ソートキーでもデータの書き込みやクエリ時に、データのフィルタリング方法に柔軟性を持たせるために複数の属性が必要になることがあります。例えば、ステータスと日付でトランザクションを取得したり、読み取りタイプとタイムスタンプの両方でフィルタリングしたセンサーの読み取り値を取得するアクセスパターンが必要な場合、ソートキーに 2 つの属性を使用することでアプリケーションの効率が大幅に向上します。このような場合、これまでは創意工夫が必要で、アプリケーション側で属性を結合して 2 つ以上の属性の複合キーを作成する必要がありました。

この記事では、複合キーの追加属性サポートを備えたグローバルセカンダリインデックスを使用して、同様のデータモデルをより効率的に設計する方法を紹介し、複雑さを軽減した DynamoDB データモデルの例を提供します。

パーティションキーとソートキーの理解

パーティションキーはクエリにおける既知の要素を表し、基本的には user_idaccount_idsensor_idplayer_id などのデータ取得時に使用する識別子です。この情報がなければ、テーブル内のアイテムを効率的に検索することが難しくなります。ソートキーは、同じパーティションキーを共有するアイテムに関する質問に答えを提供します。例えば、「このアカウントの日付別トランザクションを検索する」や「このデバイスのセンサー読み取り値とアラームを時系列で表示する」、「この顧客のショッピングカートにはどのアイテムが入っているか?」といった質問です。

新しい GSI が最大 4 つのパーティションキーと 4 つのソートキーをサポートするようになったことで、多次元クエリへのアプローチが変わります。この機能を使用することで、開発者による回避策が不要になり、DynamoDB のパフォーマンスを維持しながら、柔軟性がさらに向上します。

主要な概念

DynamoDB テーブルは 2 つの概念を中心に構築されています:

  • パーティションキーは、データの格納場所を決定するメイン識別子です。これはアクセスパターンにおける「既知の要素」を表します。パーティションキーは、Query API などの効率的な API 操作では必ず指定する必要があります。
  • ソートキーは、関連するアイテムをまとめて格納し、範囲操作を使用して効率的にクエリするためのオプション属性です。ソートキーを使用すると、1 対多のリレーションシップが可能になり、アイテムコレクション(同じパーティションキーを持つアイテム)が生成されます。

ソートキーには、エンティティタイプステータス、ISO8601 形式のタイムスタンプなど、異なるエンティティタイプを組み合わせることができます。これらを # 文字で連結し、ソートキー条件を活用して特定のクエリを取得できます。顧客 C#1A2B3C から情報を取得する例を見てみましょう。

  • すべての注文を取得するには、ソートキー条件として BEGINS_WITH ORDER# を使用します。
  • PENDING の注文のみを取得したい場合は、探しているステータスを含めたソートキー条件として BEGINS_WITH ORDER#PENDING を使用することもできます。
  • 最後に、2 つの日付の間にある保留中の注文をすべて取得したい場合は、ソートキー条件として BETWEEN ORDER#PENDING#2025-11-01 AND ORDER#PENDING#2025-11-04 を使用します。より詳細な情報を提供するたびに、より小さなデータのサブセットを取得できることに注目してください。

データモデルを設計する際、パーティションキーは「何を探しているのか?」または「主要なものは何か?」という問いに答えます。言い換えれば、データを特定するために必ず知っておくべき情報です。ソートキーは「探しているものの、どの側面が欲しいのか?」または「絞り込むのに役立つ詳細は何か?」という問いに答えます。言い換えれば、データをどのように整理しフィルタリングするか?ということです。

例: 顧客 1A2B3C のショッピングカートでは、どのアイテムがアクティブですか?

必須情報顧客 1A2B3Cです。これが探している主要な情報です。どの顧客に属しているかがわからなければ、ショッピングカートのアイテムを見つけることはできません。

ここではショッピングカート内のアクティブなアイテムフィルタリングしてみましょう。ショッピングカート内のアイテムを ACTIVE ステータスで整理し、さらに一意性のために SKU を追加します。

次の表は、顧客 1A2B3C の 3 つのショッピングカートアイテムを含むサンプルデータモデルを示しています。

パーティションキー ソートキー status sku date
C#1A2B3C CART#S#ACTIVE#SKU#123ABC ACTIVE 123ABC 2025-11-04T10:00:00
C#1A2B3C CART#S#ACTIVE#SKU#234BCD ACTIVE 234BCD 2025-11-04T10:05:00
C#1A2B3C CART#S#SAVED#SKU#345CDE SAVED 345CDE 2025-11-04T10:08:00

ソートキーの複数の属性にまたがるクエリが必要な場合、一般的なパターンは結合し複合ソートキーにすることでした。例えば:


 PK: C#1A2B3C 
 SK: ORDER#PENDING#2024-11-01T10:30:00Z

グローバルセカンダリインデックスにおける拡張複合キーの紹介

グローバルセカンダリインデックスは複数属性キーをサポートしており、複数の属性からパーティションキーとソートキーを構成できます。各属性は独自のデータ型 (文字列、数値、バイナリ) を維持し、柔軟なクエリ機能を提供します。GSI はスパースであり、複合キーのいずれかのコンポーネントが欠落している場合、単一キーの GSI と同様にアイテムはインデックス化されません。

  • 複数のパーティションキー: 最大 4 つの属性をパーティションキーとして組み合わせることができます(例: テナント、顧客、部門)。
  • 複数のソートキー: 特定のクエリパターンに対応する最大 4 つのソートキー属性を定義できます。
  • ネイティブデータ型: 各属性は元の型を保持し、文字列への変換や連結は不要です。
  • 効率的なクエリ: データを再構築することなく、より具体的な属性の組み合わせでクエリを実行できます。
  • シンプルなシャーディング手法: 2 つ以上のパーティションキーを使用して、ホットパーティションのリスクを軽減します。インテリジェントなシャーディング手法を実装し、データモデル内の情報を活用してトラフィックを分散させることで解決できます。

以下のクエリパターンがサポートされています:

  • クエリには、すべてのパーティションキー属性に対する等価条件 (=) が必要です。
  • ソートキー条件はオプションであり、等価条件 (=) を使用して最大 4 つの属性を指定できます。
  • 範囲条件 (<><=>=BETWEENBEGINS_WITH) は、最後のソートキー属性でのみサポートされています。
  • クエリでソートキーをスキップすることはできません。例えば、すべてのパーティションキーと 1 番目と 3 番目のソートキーのみを指定するクエリはサポートされていません。
  • ソートキーは左から右へ部分的に指定できます。例えば、最初のソートキーのみ、または最初と 2 番目のソートキーのみを指定するクエリはサポートされています。
  • アプリケーションの複雑さを軽減しながら、DynamoDB と同等のパフォーマンスを維持できます。

複合キーの仕組み

GSI の拡張複合キーの動作を確認するために、完全な例を見ていきましょう。

シナリオ 1: 注文ダッシュボード

注文を追跡するシステムを構築しています。このシステムには以下の要件があります:

  • システムは注文 ID で注文を追跡し、ステータスとメタデータを更新します。
  • ユーザーは、ステータス (ACTIVE、PENDING、COMPLETED) と金額のしきい値を組み合わせて注文を検索できます。例えば、11 月 4 日の $100 を超える ACTIVE な注文などです。

ベーステーブルの設計

シナリオ 1 の 2 つのアクセスパターンのうち、ユーザーに関連しないものは 1 つだけです。注文追跡システムをベーステーブルとして使用します。注文が最も基本的な情報単位であるため、システムをスケールさせることができます。

必須情報order_id です。order_id がなければ、テーブル全体をスキャンする必要があります。他のアクセスパターンで注文を日付順に整理するために、生成時刻でソート可能な K-sortable unique identifier (KSUID) を使用します。

この例では注文 ID に対するクエリが含まれていないため、ソートキーは必要ありません。次の表は、顧客 1A2B3C の 3 つの注文を含むサンプルデータモデルを示しています。

パーティションキー
order_id customer_id order_date amount status acc_type org_id
KSUID1 C#1A2B3C 2025-11-04 200 ACTIVE A OMEGA
KSUID2 C#1A2B3C 2025-11-04 145 PENDING A OMEGA
KSUID3 C#1A2B3C 2025-11-04 110 PENDING B BRAVO
Base Table: Orders 
 Partition Key: order_id 
 Attributes: customer_id, status, order_date, amount, acc_type and org_id

複合キー GSI 設計

ダッシュボードクエリ用に、customer_id をパーティションキーとし、statusorder_dateamount で構成されるソートキーを持つ GSI を作成します。不等式はソートキー属性の最後に配置する必要があるため、amount を最後に配置することで、範囲クエリを使用して価格のしきい値でフィルタリングできます。

必須情報顧客 1A2B3Cです。どの顧客に属するかを知らなければ、注文を見つけることはできません。データをどのように整理してフィルタリングすればよいでしょうか?特定の日付のアクティブな注文で、$100 を超えるもので整理およびフィルタリングする必要があります。ステータスと日付を使用した複合キーを使用することで、この顧客の注文を取得できます。

以下の表は、顧客 1A2B3C の 3 つの注文を含むサンプルデータモデルを示しています:

GSI PK GSI SK
customer_id status order_date amount order_id acc_type org_id
C#1A2B3C ACTIVE 2025-11-04 200 KSUID1 A OMEGA
C#1A2B3C PENDING 2025-11-04 145 KSUID2 A OMEGA
C#1A2B3C PENDING 2025-11-04 110 KSUID3 B BRAVO
GSI: OrdersByStatusDateAmount 
 Partition Key: customer_id 
 Sort Key 1: status (equality condition)
 Sort Key 2: order_date (equality condition)
 Sort Key 3: amount (range condition)

以下の AWS CLI コマンドでは、顧客 1A2B3C の注文を取得します。

aws dynamodb query \ 
    --table-name orders-table \ 
    --index-name OrdersByStatusDateAmount \ 
    --key-condition-expression "customer_id = :cust" \ 
    --expression-attribute-values '{
        ":cust": {"S": "1A2B3C"}
    }'
{
    "Items": [ 
        {
            "org_id": {"S": "OMEGA"},
            "order_date": {"S": "2025-11-04"},
            "status": {"S": "ACTIVE"},
            "acc_type": {"S": "A"},
            "customer_id": {"S": "1A2B3C"},
            "amount": {"N": "200"},
            "order_id": {"S": "KSUID1"}
        },
        {
            "org_id": {"S": "BRAVO"},
            "order_date": {"S": "2025-11-04"},
            "status": {"S": "PENDING"},
            "acc_type": {"S": "B"},
            "customer_id": {"S": "1A2B3C"},
            "amount": {"N": "110"},
            "order_id": {"S": "KSUID3"}
        },
        {
            "org_id": {"S": "OMEGA"},
            "order_date": {"S": "2025-11-04"},
            "status": {"S": "PENDING"},
            "acc_type": {"S": "A"},
            "customer_id": {"S": "1A2B3C"},
            "amount": {"N": "145"},
            "order_id": {"S": "KSUID2"}
        }
    ],
    "Count": 3,
    "ScannedCount": 3,
    "ConsumedCapacity": null 
}

以下の AWS CLI コマンドは、顧客 1A2B3C の PENDING ステータスの注文をクエリします:

aws dynamodb query \ 
    --table-name orders-table \ 
    --index-name OrdersByStatusDateAmount \ 
    --key-condition-expression "customer_id = :cust AND #status = :status" \ 
    --expression-attribute-names '{"#status": "status"}' \ 
    --expression-attribute-values '{
        ":cust": {"S": "1A2B3C"},
        ":status": {"S": "PENDING"}
    }' 

以下の AWS CLI コマンドは、11 月 4 日に PENDING ステータスとなっている顧客 1A2B3C の注文を取得します:

aws dynamodb query \ 
    --table-name orders-table \ 
    --index-name OrdersByStatusDateAmount \ 
    --key-condition-expression "customer_id = :cust AND #status = :status AND order_date = :date" \ 
    --expression-attribute-names '{"#status": "status"}' \ 
    --expression-attribute-values '{
        ":cust": {"S": "1A2B3C"},
        ":status": {"S": "PENDING"},
        ":date": {"S": "2025-11-04"}
    }'

以下の AWS CLI コマンドは、顧客 1A2B3C の注文のうち、11 月 4 日に PENDING ステータスで金額が 100 ドルを超えるものをクエリします。

aws dynamodb query \ 
    --table-name orders-table \ 
    --index-name OrdersByStatusDateAmount \ 
    --key-condition-expression "customer_id = :cust AND #status = :status AND order_date = :date AND amount > :min_amount" \ 
    --expression-attribute-names '{"#status": "status"}' \ 
    --expression-attribute-values '{
        ":cust": {"S": "1A2B3C"},
        ":status": {"S": "PENDING"},
        ":date": {"S": "2025-11-04"},
        ":min_amount": {"N": "100"}
    }'

シナリオ 2: 進化 – トラフィックの増加

このソリューションが成功し、最大規模のエンタープライズ顧客が 1 秒あたり 500 回を超えるレートで注文ステータスをプッシュするようになったと想像してください。要件は変わらず、ユーザーは一定の金額しきい値内でステータスごとに注文をクエリする必要があります。

この大規模なエンタープライズ顧客の導入により、ホットパーティションが発生する可能性に直面しています。ベーステーブルで 1 つの注文ステータスを NEW から ACTIVE に更新すると、1 WCU を消費する単一の書き込み操作が使用されます。GSI では、NEW ステータスを削除するために 1 つ、ACTIVE に設定するためにもう 1 つ、合計 2 WCU を消費し、仮想パーティションあたり 1000 WCU の制限に近づきます。

幸いなことに、status 属性をシャーディングキーとして使用できます。5 つの異なるステータスがあると仮定すると、スループットを最大 5 倍に増加させることができます。パーティションキーを customer_id と status、ソートキーを order_date と amount とする新しい GSI を作成します。

GSI PK GSI SK
customer_id status order_date amount order_id acc_type org_id
C#1A2B3C ACTIVE 2025-11-04 200 KSUID1 A OMEGA
C#1A2B3C PENDING 2025-11-04 145 KSUID2 A OMEGA
C#1A2B3C PENDING 2025-11-04 110 KSUID3 B BRAVO
GSI: OrdersByOrgAccountStatus 
 Partition Key 1: customer_id 
 Partition Key 2: status 
 Sort Key 2: order_date (equality condition)
 Sort Key 3: amount (range condition)

複数属性の複合キーを使用する際のベストプラクティス

まずクエリパターンを設計してください。GSI を作成する前に、最も頻繁に使用される上位 3 〜 5 つのクエリパターンを特定し、その頻度とパフォーマンス要件を把握します。ベーステーブルの構造は一意性を確保するように設計し、GSI を使用して複合キーパターンを最適化することで、最も一般的なクエリを効率的に処理できるようにします。複合キー GSI を使用すれば、データモデル全体を再構築するのではなく、異なるキーの組み合わせでインデックスを追加することで、新しい要件に対応できます。

キーの順序は慎重に選択してください。GSI の属性の順序は、実行できるクエリに直接影響します。4 つのパーティションキーと 4 つのソートキーを使用する必要はありません。3 つのパーティションキーと 2 つのソートキー、1 つのパーティションキーと 3 つのソートキー、その他の構成など、アクセスパターンに合った組み合わせを選択してください。

複合キー GSI の最も強力な側面の 1 つは、ネイティブデータ型のサポートです。タイムスタンプ、数量、数値比較には Number 型を使用して、適切なソートと数学演算を可能にします。アクティブ/非アクティブや有効/無効などのバイナリ状態には Boolean フラグを使用します。型固有の操作のメリットが失われるため、必要でない限り値を文字列に変換することは避けてください。

最初からスケールを考慮して計画してください。コストを削減するために、指定したキー属性に値を持つ項目のみをインデックス化するスパースインデックスを可能な限り設計してください。プロジェクションタイプは戦略的に選択してください。柔軟性を重視する場合は ALL を、ストレージコストを削減する場合は KEYS_ONLY を、必要な属性のみを射影する場合は INCLUDE を使用します。ベーステーブルに Time to Live (TTL) 戦略を実装して、レコードサイズを長期的に管理し、無制限な増加を防止してください。

まとめと次のステップ

この記事では、複合キー GSI の使い方を学びました。この新しい DynamoDB の機能により、属性を連結するような回避策を使用することなく、異なるデータ型や属性をクエリできます。この機能は、標準の GSI ストレージ、スループット、機能以外の追加コストなしで、すべての DynamoDB テーブルで利用可能です。DynamoDB のデータモデルをシンプルにする準備はできましたか?以下の手順に従ってください:

  1. クエリパターンを特定する: 現在連結されたソートキーを使用しているクエリを確認します
  2. GSI を設計する: データ階層に基づいて、属性をパーティションキーとソートキーの位置にマッピングします。
  3. 徹底的にテストする: テストテーブルを作成し、想定される負荷の下でクエリパターンが期待どおりに動作することを検証します。
  4. 本番環境にデプロイする: 本番テーブルに新しい GSI を追加し、アプリケーションコードを更新します
  5. パフォーマンスを監視する: CloudWatch で GSI のメトリクスを追跡し、必要に応じて最適化します
  6. クリーンアップする: GSI で使用されているレガシーの複合属性を削除します。

詳細な実装ガイダンスについては、DynamoDB でグローバルセカンダリインデックスを使用する方法データモデリングのベストプラクティスを参照してください。

この記事の翻訳は Solutions Architect の堤 勇人が担当しました。

著者について

Esteban Serna

Esteban Serna

Esteban は、AWSのプリンシパルDynamoDBスペシャリストソリューションアーキテクトで、16年の経験を持つデータベース愛好家です。コンタクトセンターインフラの展開からNoSQLに魅了されるまで、Estebanの歩みは分散コンピューティングの専門化へと導きました。仕事に情熱を持つEstebanは、自身の知識を他者と共有することほど好きなことはありません。