DynamoDBインデックス戦略の本質:後付けできるGSI、できないLSI
DynamoDBの設計で最初に理解すべき重要な事実があります。それは「GSI(Global Secondary Index)」は既存テーブルに後から追加できるのに対し、「LSI(Local Secondary Index)」はテーブル作成時にしか定義できないという根本的な違いです。これは単なる仕様の違いではなく、設計思想そのものに関わる重大な制約なのです。
GSIとLSIの根本的な違いを理解する
GSIは柔軟に後付け可能、でも注意点がある
AWSの公式ドキュメントによると、GSIはUpdateTable
APIを使用して既存のテーブルに追加可能です。ただし、ここに重要な制約があります。1回のUpdateTable
で作成・削除できるGSIは1つのみという点です。複数のGSIを追加したい場合は、順次実行する必要があるため、計画的な実装が求められます。
GSI作成時の処理は「リソース割当」と「バックフィル」の2段階で進行します。バックフィル中はテーブル自体は利用可能ですが、新しいGSIがACTIVE
になるまではクエリできません。さらに興味深いことに、バックフィル中はテーブル読み取りの課金が発生せず、GSIへの書き込み課金のみが発生するという特徴があります。
実際のプロジェクトでこの仕様を活用した例をご紹介します。あるECサイトのプロジェクトで、当初は商品IDベースの検索のみを想定していたのですが、運用開始後にカテゴリー別検索の要求が発生しました。GSIを後付けできる特性のおかげで、サービスを停止することなく新たな検索パターンに対応できたのです。
LSIは設計時の一発勝負
一方、LSIはテーブル作成時のみ定義可能で、後からの追加・削除・変更は一切できません。この制約は、DynamoDBの内部アーキテクチャに起因するものです。LSIはベーステーブルと同じパーティション内に存在し、パーティションキーを共有するため、後から構造を変更することが技術的に困難なのです。
LSIには「同一パーティションキー値あたり10GB」という容量制限も存在します。これはベーステーブルとLSIの合計サイズに適用される制限であり、大量のデータが特定のパーティションキーに集中するようなケースでは致命的な問題となりえます。
最新の制約事項と運用上の落とし穴
インデックス数の上限と回避策
2025年現在の制約事項を整理すると、以下のような上限が設定されています。
表 DynamoDBインデックスの主要制約一覧
項目 | 上限値 | 備考 |
---|---|---|
GSI数/テーブル | 20(デフォルト) | サービスクォータで増枠可能な場合あり |
LSI数/テーブル | 5 | ハード制約、増枠不可 |
アイテム最大サイズ | 400KB | 属性名も含む |
投影属性の合計 | 100属性 | INCLUDE指定の総計、全インデックス合算 |
Query結果の最大サイズ | 1MB | ページング必須 |
これらの制約に対処するため、後述する「GSI Overloading」という設計パターンが重要になってきます。20個のGSI制限があっても、実質的に20以上のフィールドに対する検索を実現できる優れた手法です。
整合性モデルの違いを理解する
GSIとLSIのもう一つの重要な違いは、読み取り整合性です。GSIは結果整合性のみをサポートし、テーブルやLSIのような強い整合性読み取りは利用できません。これは金融取引のような厳密な整合性が求められるケースでは、設計上の重要な考慮点となります。
バックフィル処理の実態と対処法
GSIを後から追加する際の最大の課題は、バックフィル処理の管理です。大規模なテーブルでは、この処理に数時間から数日かかることもあります。
バックフィル処理を監視するためのCloudWatchメトリクスが提供されています。
OnlineIndexPercentageProgress
:進捗率の監視OnlineIndexConsumedWriteCapacity
:消費された書き込み容量OnlineIndexThrottleEvents
:スロットリングイベントの発生状況
実運用では、バックフィル中にGSIの書き込み容量が不足するとテーブル書き込みがスロットルされる可能性があります。公式ドキュメントでは、一時的にGSIのWrite容量を1.5倍から2倍に引き上げることが推奨されています。
FilterExpressionの誤解と正しい使い方
DynamoDBにおける「FilterExpression」は、最も誤解されやすい機能の一つです。多くの開発者が「フィルターを使えば効率的に検索できる」と考えがちですが、実際はそうではありません。
FilterExpressionは「後段フィルタ」である
FilterExpressionは、Query完了後に適用される後段フィルタです。つまり、データベースから取得する段階では、フィルターの有無に関わらず同じ量のデータを読み込み、同じ量の読み込み容量(RCU)を消費します。その後、取得したデータに対してフィルタリングを行い、条件に合致しないアイテムを除外してからクライアントに返却するのです。
この仕様を理解していないと、大きなパフォーマンス問題に直面します。例えば、100万件のアイテムから10件だけを抽出したい場合、FilterExpressionを使うと100万件分の読み込みコストが発生し、その後に10件に絞り込まれます。これは明らかに非効率です。
ProjectionExpressionも容量消費には影響しない
同様に誤解されやすいのが「ProjectionExpression」です。ProjectionExpressionは返却属性を縮小するだけで、プロビジョンドスループット消費には影響しません。つまり、特定の属性だけを取得したいからといって、読み込みコストが削減されるわけではないのです。
正しいアプローチ:キー条件式とインデックス設計
効率的な検索を実現するには、「キー条件式(KeyConditionExpression)」と適切なインデックス設計が不可欠です。キー条件式では、パーティションキーの等価指定と、ソートキーの範囲演算(=, <, <=, >, >=, BETWEEN, begins_with
)が利用可能です。
実際のプロジェクトで遭遇した例を紹介します。ユーザーの行動ログを記録するテーブルで、当初は全データを取得してからアプリケーション側でフィルタリングしていました。データ量が増えるにつれてレスポンスが悪化し、調査の結果、FilterExpressionの誤用が原因と判明しました。キー設計を見直し、適切なGSIを追加することで、レスポンス時間を90%以上削減できました。
Composite Key(複合ソートキー)パターンの実践
「Composite Key」パターンは、複数の属性を組み合わせてソートキーを構成する設計手法です。これにより、単一のインデックスで複数の検索パターンに対応できる優れた手法となっています。
実装例:ユーザーアクティビティの管理
ECサイトのユーザー行動分析システムを例に、Composite Keyパターンの実装を見ていきます。要件として、「特定ユーザーの完了済みタスクを新しい順に取得」と「特定期間のタスクを抽出」の両方に対応する必要があったとします。
従来の設計では、以下のような単純な構造を採用しがちです。
表 従来型のテーブル設計
パーティションキー | ソートキー | その他の属性 |
---|---|---|
UserId | Date | Status, TaskId, Description |
この設計では、ステータスで絞り込むためにFilterExpressionを使用する必要があり、前述の通り非効率です。
Composite Keyパターンを適用すると、以下のような設計になります。
表 Composite Keyパターンを適用した設計
パーティションキー | ソートキー | その他の属性 |
---|---|---|
UserId | Status#YYYY-MM-DD | TaskId, Description |
ソートキーをStatus#YYYY-MM-DD
(例:DONE#2025-01-15
)とすることで、begins_with
演算子を使った効率的なプレフィックスクエリが可能になります。
クエリ例:
- 完了済みタスクの取得:
UserId = :uid AND begins_with(StatusDate, 'DONE#')
- 特定月の完了タスク:
UserId = :uid AND begins_with(StatusDate, 'DONE#2025-01')
階層型キー設計の応用
Composite Keyパターンをさらに発展させると、階層型のキー設計が可能です。例えば、組織構造を表現する場合、Department#Team#UserId
のような階層的なキーを構築できます。
実際のプロジェクトで、大規模な組織の権限管理システムを構築した際、この階層型キー設計が威力を発揮しました。部署単位、チーム単位、個人単位での権限検索が、すべて単一のGSIで実現でき、インデックス数の節約にもつながりました。
設計上の注意点
Composite Keyパターンを採用する際の注意点を整理します。
Composite Key設計における主要な考慮事項は以下の通りです。
- キーの順序は検索パターンの優先度に基づいて決定する
- 区切り文字(#など)は、データに含まれない文字を選択する
- キーが長くなりすぎないよう、必要最小限の情報に絞る
- 将来の拡張性を考慮し、柔軟な命名規則を採用する
Sparse Index(スパースインデックス)の威力
「Sparse Index」は、AWSが公式にベストプラクティスとして推奨する設計パターンです。インデックスキー属性を持つアイテムだけがGSIに現れるという特性を活用し、テーブル全体の一部だけを効率的にクエリできる手法です。
Sparse Indexの仕組みと利点
DynamoDBのGSIは、デフォルトでスパースです。つまり、インデックスのパーティションキーまたはソートキーに値が設定されていないアイテムは、そのインデックスには含まれません。この特性を意図的に利用することで、以下のような利点が得られます。
Sparse Indexの主要な利点を以下に示します。
- インデックスのサイズが小さくなり、ストレージコストが削減される
- クエリ対象のアイテム数が減るため、読み込みコストが削減される
- インデックスの書き込みスループットをテーブルより低く設定できる
- 特定の条件を満たすアイテムのみを高速に検索できる
実装例:ランキングシステムの構築
ゲームアプリケーションのランキングシステムを例に、Sparse Indexの実装を見ていきます。全ユーザーのスコアを記録しつつ、上位100位までのランキングを高速に取得する要件があったとします。
表 Sparse Indexを活用したランキングテーブル設計
UserId (PK) | GameId (SK) | Score | UpdatedAt | Rank |
---|---|---|---|---|
user-001 | game-A | 95000 | 2025-01-15 | 3 |
user-002 | game-A | 98000 | 2025-01-15 | 1 |
user-003 | game-A | 96000 | 2025-01-15 | 2 |
user-004 | game-A | 85000 | 2025-01-15 | (null) |
user-005 | game-A | 80000 | 2025-01-15 | (null) |
GSIの設計
- パーティションキー:GameId
- ソートキー:Rank
この設計では、Rank属性を持つアイテム(上位100位まで)のみがGSIに含まれます。全体で100万人のユーザーがいても、GSIには100件しか存在しないため、ランキングの取得が極めて高速になります。
未処理タスクの管理への応用
別の実用例として、タスク管理システムにおける未処理タスクの抽出があります。処理済みのタスクにはProcessedAt
属性を付与し、未処理のタスクにはこの属性を設定しないことで、未処理タスクのみを含むSparse Indexを構築できます。
あるプロジェクトで、毎日数百万件のタスクが生成されるシステムを構築した際、この手法により未処理タスクの検索時間を99%削減できました。従来は全タスクをスキャンしてステータスでフィルタリングしていたのですが、Sparse Indexの導入により、必要なアイテムのみを直接取得できるようになったのです。
GSI Overloading(多重定義):制約を超える設計
「GSI Overloading」は、AWSが公式ドキュメントで紹介している高度な設計パターンです。単一のGSIで複数の検索要件に対応することで、GSIの20個という制限を実質的に回避できる手法です。
GSI Overloadingの基本概念
通常、各検索要件に対して個別のGSIを作成しますが、GSI Overloadingでは、汎用的なキー名(例:GSI_PK
、GSI_SK
)を使用し、その中に異なる種類のデータを格納します。DynamoDBのスキーマレスな特性を最大限に活用した設計パターンといえるでしょう。
実装例として、従業員管理システムを考えてみます。氏名、部署、入社日、役職など、様々な属性での検索が必要な場合、通常なら各属性に対してGSIを作成する必要があります。
表 GSI Overloadingを適用した従業員テーブル
EmployeeId (PK) | GSI_PK | GSI_SK | Name | Department | JoinDate | Position |
---|---|---|---|---|---|---|
emp-001 | NAME#山田太郎 | emp-001 | 山田太郎 | 営業部 | 2020-04-01 | 課長 |
emp-001 | DEPT#営業部 | emp-001 | 山田太郎 | 営業部 | 2020-04-01 | 課長 |
emp-001 | JOIN#2020-04 | emp-001 | 山田太郎 | 営業部 | 2020-04-01 | 課長 |
emp-002 | NAME#鈴木花子 | emp-002 | 鈴木花子 | 開発部 | 2021-10-01 | 主任 |
emp-002 | DEPT#開発部 | emp-002 | 鈴木花子 | 開発部 | 2021-10-01 | 主任 |
emp-002 | JOIN#2021-10 | emp-002 | 鈴木花子 | 開発部 | 2021-10-01 | 主任 |
この設計により、単一のGSIで以下のような多様な検索が可能になります。
検索パターンの例:
- 氏名での検索:
GSI_PK = 'NAME#山田太郎'
- 部署での検索:
GSI_PK = 'DEPT#営業部'
- 入社月での検索:
GSI_PK begins_with 'JOIN#2021-10'
実装上の注意点と運用の工夫
GSI Overloadingは強力な手法ですが、適切に実装しないと保守性が損なわれます。私が実際のプロジェクトで培った運用上の工夫を共有します。
命名規則の統一
キープレフィックス(NAME#、DEPT#など)の命名規則を厳密に定義し、ドキュメント化することが重要です。チーム全体で共有し、新規メンバーにも理解しやすい規則を採用しましょう。
データ重複の管理
GSI Overloadingでは、同一のデータを複数のアイテムとして格納するため、データの重複が発生します。更新時は関連するすべてのアイテムを更新する必要があり、トランザクション処理やDynamoDB Streamsを活用した整合性の維持が重要になります。
投影属性の最適化
GSIの投影(Projection)設定は、コストとパフォーマンスのトレードオフです。ALL
を選択すると全属性が投影されますが、ストレージコストが増大します。KEYS_ONLY
では最小限のコストですが、追加の読み込みが必要になる場合があります。アクセスパターンを分析し、最適な設定を選択することが重要です。
設計時の落とし穴と対策
DynamoDBの設計において、経験豊富なエンジニアでも陥りがちな落とし穴がいくつか存在します。ここでは、実際のプロジェクトで遭遇した問題とその対策を共有します。
Hot Partition問題への対処
特定のパーティションキーにアクセスが集中する「Hot Partition」問題は、DynamoDBの性能問題の代表例です。例えば、タイムスタンプをパーティションキーにした場合、現在時刻付近のパーティションにアクセスが集中します。
対策として、以下のような手法が有効です。
Hot Partition回避の主要な手法:
- パーティションキーにランダムサフィックスを追加する(シャーディング)
- Write Sharding パターンを採用し、書き込みを複数のパーティションに分散する
- 時系列データの場合、時間バケット(日付、時間帯など)でパーティションを分割する
10GB制限の回避策
LSIを持つテーブルでは、同一パーティションキー値あたり10GBという制限があります。この制限に達すると、そのパーティションキーへの書き込みができなくなるという深刻な問題が発生します。
実際に遭遇した例では、ユーザーIDをパーティションキーとしたログテーブルで、特定のヘビーユーザーのデータが10GBを超えてしまい、サービス障害につながりました。対策として、以下のような設計変更を行いました。
10GB制限への対処法:
- パーティションキーに時間要素を組み込む(例:
UserId#YYYY-MM
) - データのアーカイブ戦略を実装し、古いデータを別テーブルに移動する
- LSIではなくGSIを使用し、制限を回避する
型不一致によるインデックス欠落
既存データの型が不一致だったり、サイズが制限を超えていたりすると、そのアイテムはインデックスに含まれません。この問題は、開発環境では発見しづらく、本番環境で初めて発覚することが多い厄介な問題です。
AWSは「Violation Detector」という機能を提供しており、型違反やサイズ超過を検出できます。GSIを追加する前に、必ずこのツールで既存データをチェックすることを強く推奨します。
パフォーマンス最適化の実践テクニック
適応型キャパシティの活用
DynamoDBの「適応型キャパシティ」は、不均等なワークロードに自動的に対応する機能です。Hot Partitionが検出されると、そのパーティションに追加のキャパシティを自動的に割り当てます。
ただし、適応型キャパシティには反応時間があるため、急激なトラフィック増加には対応できない場合があります。予測可能なトラフィックパターンがある場合は、事前にキャパシティを調整することが重要です。
オンデマンドモードの戦略的活用
オンデマンドモードでは、テーブルとGSIごとに最大スループットを設定可能です。これにより、予期しないトラフィック増加によるコスト暴走を防げます。
あるスタートアップのプロジェクトで、マーケティングキャンペーンによる突発的なトラフィック増加に対応するため、オンデマンドモードを採用しました。結果として、サービスの安定性を保ちつつ、平常時のコストを最小限に抑えることができました。
DynamoDB Accelerator(DAX)の効果的な利用
DAXは、DynamoDBの前段に配置するインメモリキャッシュです。マイクロ秒レベルのレイテンシーを実現し、読み込み負荷を大幅に削減できます。
DAXの導入が効果的なケース:
- 同じデータへの繰り返しアクセスが多い
- レイテンシー要件が厳しい(1ms以下)
- 読み込みコストの削減が優先事項である
ただし、DAXは結果整合性の読み込みのみをサポートし、強い整合性読み込みはサポートしていない点に注意が必要です。
運用監視とトラブルシューティング
CloudWatchメトリクスの活用
DynamoDBの健全性を監視するために、以下のメトリクスを重点的に監視することを推奨します。
表 重要なCloudWatchメトリクス一覧
メトリクス名 | 説明 | アラート閾値の目安 |
---|---|---|
ConsumedReadCapacityUnits | 消費された読み込み容量 | プロビジョンド容量の80% |
ConsumedWriteCapacityUnits | 消費された書き込み容量 | プロビジョンド容量の80% |
UserErrors | クライアントエラーの数 | 通常時の5倍 |
SystemErrors | システムエラーの数 | 1分間に5回以上 |
ThrottledRequests | スロットリングされたリクエスト数 | 1分間に10回以上 |
これらのメトリクスを継続的に監視し、異常を早期に検出することで、サービス障害を未然に防げます。
Contributor Insightsの活用
Contributor Insightsは、最もアクセス頻度の高いアイテムやキーを特定できる機能です。Hot Partitionの検出や、アクセスパターンの分析に非常に有効です。
実際のプロジェクトで、特定の時間帯にレスポンスが悪化する問題が発生した際、Contributor Insightsを使用して原因を特定しました。特定の人気商品へのアクセスが集中していることが判明し、キャッシュ戦略の見直しで問題を解決できました。
コスト最適化戦略
適切な課金モードの選択
プロビジョンドモードとオンデマンドモードの選択は、コストに大きな影響を与えます。一般的な指針として、以下のような基準で選択することを推奨します。
課金モード選択の基準:
- 予測可能で安定したトラフィック → プロビジョンドモード
- 予測困難で変動が激しいトラフィック → オンデマンドモード
- 開発・検証環境 → オンデマンドモード
- 本番環境で24時間安定稼働 → プロビジョンドモード + Auto Scaling
S3へのエクスポートとアーカイブ戦略
DynamoDBからS3への継続的なエクスポート機能を活用することで、古いデータをコスト効率的にアーカイブできます。PITRウィンドウ内からの非同期エクスポートはRCUを消費しないため、本番環境への影響を最小限に抑えられます。
あるログ分析システムでは、30日以上経過したデータをS3にエクスポートし、Athenaで分析する構成を採用しました。これにより、DynamoDBのストレージコストを70%削減しつつ、過去データへのアクセスも維持できました。
まとめ:DynamoDB設計の極意
DynamoDBの設計は、単純なKey-Value Storeと思われがちですが、実際には奥深く、様々な設計パターンと最適化手法が存在します。本記事で解説した内容を振り返ると、以下のポイントが特に重要です。
DynamoDB設計の成功要因:
- アクセスパターンを事前に徹底的に分析し、適切なキー設計を行う
- GSIとLSIの特性を理解し、要件に応じて使い分ける
- FilterExpressionに頼らず、キー条件式とインデックスで効率的な検索を実現する
- Composite Key、Sparse Index、GSI Overloadingなどの設計パターンを活用する
- 制約事項を理解し、事前に回避策を組み込む
- 継続的な監視とチューニングを行う
DynamoDBは、適切に設計・運用すれば、スケーラブルで高性能なデータベースとして機能します。しかし、設計を誤ると、パフォーマンス問題やコスト増大に直面することになります。本記事で紹介した知見が、皆様のDynamoDB活用の一助となれば幸いです。
最後に、技術は常に進化しています。AWSの公式ドキュメントを定期的に確認し、新機能や最新のベストプラクティスをキャッチアップすることを忘れないでください。DynamoDBの可能性は無限大です。その力を最大限に引き出し、ビジネス価値の創出につなげていきましょう。