database
A
信頼度ランク
| S | 公式ソース確認済み |
| A | 成功実績多数・失敗例少数 |
| B | 賛否両論 |
| C | 動作未確認・セキュリティリスク高 |
| Z | 個人所感 |
DynamoDB GSI vs LSI — インデックスの仕組み・制約・設計パターン
DynamoDBのGSI(グローバルセカンダリインデックス)とLSI(ローカルセカンダリインデックス)の違い、作成タイミング、一貫性、スループット設定、クエリパターンを解説。
一言結論
GSIはテーブル作成後も追加でき柔軟性が高い一方でWCUが独立しているためボトルネックになりやすく、LSIは強整合性読み取りが可能だがテーブル作成時のみ追加でき1パーティション10GBの制限があるため、現代設計ではGSI中心のSingle Table Designが推奨される。
DynamoDBのインデックス
DynamoDBはキーバリュー型のNoSQLだが、セカンダリインデックスを使うことでパーティションキー以外の属性でもクエリできる。
| 特徴 | GSI | LSI |
|---|---|---|
| 作成タイミング | テーブル作成後でも追加可能 | テーブル作成時のみ |
| PKの制約 | プライマリテーブルと異なるPKを設定可能 | プライマリテーブルと同じPKが必須 |
| SKの制約 | 任意 | 必須(異なる属性) |
| スループット | 独自のRCU/WCUを設定 | テーブルと共有 |
| 強整合性読み取り | 不可(結果整合性のみ) | 可能 |
| パーティションサイズ | 無制限 | 10GBの制限あり |
| 最大数 | 20個/テーブル | 5個/テーブル |
LSI(ローカルセカンダリインデックス)
同じパーティション内でソートキーを変えて検索するためのインデックス。
# テーブル定義(LSIあり)
import boto3
dynamodb = boto3.client('dynamodb')
dynamodb.create_table(
TableName='Orders',
KeySchema=[
{'AttributeName': 'userId', 'KeyType': 'HASH'}, # PK
{'AttributeName': 'orderId', 'KeyType': 'RANGE'} # SK
],
AttributeDefinitions=[
{'AttributeName': 'userId', 'AttributeType': 'S'},
{'AttributeName': 'orderId', 'AttributeType': 'S'},
{'AttributeName': 'orderDate', 'AttributeType': 'S'}, # LSI用
{'AttributeName': 'totalAmount', 'AttributeType': 'N'} # LSI用
],
LocalSecondaryIndexes=[
{
'IndexName': 'userId-orderDate-index', # 日付でソートするLSI
'KeySchema': [
{'AttributeName': 'userId', 'KeyType': 'HASH'}, # テーブルと同じPK
{'AttributeName': 'orderDate', 'KeyType': 'RANGE'} # 異なるSK
],
'Projection': {'ProjectionType': 'ALL'}
}
],
BillingMode='PAY_PER_REQUEST'
)
# LSIを使ったクエリ(特定ユーザーの注文を日付順に取得)
from boto3.dynamodb.conditions import Key
table = dynamodb_resource.Table('Orders')
response = table.query(
IndexName='userId-orderDate-index',
KeyConditionExpression=Key('userId').eq('user123'),
ScanIndexForward=False # 降順(新しい順)
)
GSI(グローバルセカンダリインデックス)
テーブルの任意の属性をPKとして設定できる。テーブルとは完全に異なるアクセスパターンをサポートする。
# GSIの追加(テーブル作成後に追加可能)
dynamodb.update_table(
TableName='Orders',
AttributeDefinitions=[
{'AttributeName': 'status', 'AttributeType': 'S'},
{'AttributeName': 'orderDate', 'AttributeType': 'S'}
],
GlobalSecondaryIndexUpdates=[
{
'Create': {
'IndexName': 'status-orderDate-index',
'KeySchema': [
{'AttributeName': 'status', 'KeyType': 'HASH'}, # テーブルと異なるPK
{'AttributeName': 'orderDate', 'KeyType': 'RANGE'}
],
'Projection': {'ProjectionType': 'INCLUDE',
'NonKeyAttributes': ['userId', 'totalAmount']},
'ProvisionedThroughput': {
'ReadCapacityUnits': 5,
'WriteCapacityUnits': 5
}
}
}
]
)
# GSIを使ったクエリ(ステータスが"processing"の注文を日付順に取得)
response = table.query(
IndexName='status-orderDate-index',
KeyConditionExpression=Key('status').eq('processing') &
Key('orderDate').begins_with('2026-04')
)
Sparse Index(スパースインデックス)パターン
GSIは属性が存在するアイテムのみインデックスに含まれる。これを利用して「特定の属性を持つアイテムのみ」を効率的に検索できる。
# unprocessedというフラグがあるアイテムのみGSIに含める
# フラグが削除されたらGSIから自動的に除外される
# 未処理の注文のみを高速に取得
{
"orderId": "order-123",
"status": "processing",
"unprocessed": "YES" ← この属性があるアイテムのみGSIに含まれる
}
# 処理完了後にunprocessedを削除 → GSIから自動除外
Projection(射影)の設計
ProjectionType:
ALL: テーブルの全属性をインデックスにコピー(ストレージ多め)
KEYS_ONLY: プライマリキーとインデックスキーのみ
INCLUDE: 指定した属性のみ
設計のポイント:
- クエリで必要な属性をPROJECTIONに含める
- ALLは汎用的だがストレージコストが高い
- 不足した属性はベーステーブルを再度読む必要がある(追加コスト)
GSIのスロットリング
GSIはプライマリテーブルへの書き込み時に同時に更新される。GSIのWCUが不足するとテーブル全体の書き込みがスロットリングされる。
テーブルのWCU: 100
GSIのWCU: 10 ← ボトルネック
→ テーブルへの書き込みが100WCU以内でもGSIが10WCUを超えると
テーブル全体の書き込みがスロットリングされる
On-demand モードではGSIも自動スケールするため、このリスクが軽減される。
試験頻出ポイント
| シナリオ | 回答 |
|---|---|
| テーブル作成後にインデックスを追加したい | GSI(LSIはテーブル作成時のみ) |
| 強整合性読み取りが必要 | LSI(GSIは結果整合性のみ) |
| テーブルと異なるPKでクエリしたい | GSI |
| 同じPKで異なるSKでのクエリ | LSI |
| パーティション内の10GB制限がある | LSI(1パーティション = 1PKの値で10GB上限) |
| GSIの書き込みボトルネック | GSIのWCUを適切に設定、またはOn-demand使用 |
まとめ
GSIは柔軟性が高くテーブル作成後も追加可能で、異なるアクセスパターンに対応できる。LSIはテーブル作成時のみ設定可能で制約が多いが、強整合性読み取りが可能な点が特徴だ。現代の設計ではGSIを中心に設計し、Single Table Design パターンで1テーブルに多様なアクセスパターンを収めることが推奨される。