- AWS
DynamoDBのGSIとLSIの違い|作成・クエリ・射影をコードで解説
GSI(グローバルセカンダリインデックス)は「テーブルとは別のパーティションキーで全体を引くための索引」、LSI(ローカルセカンダリインデックス)は「同じパーティションキーのまま、別のソートキーで並べ替えるための索引」です。 GSI はあとから追加・削除でき、LSI はテーブル作成時にだけ定義できます。
この記事では、注文テーブル 1 つに LSI と GSI の両方を作り、IndexName を指定した Query を boto3 と AWS CLI で実行して、返ってくるアイテムをそのまま載せます。射影(ProjectionType)の 3 種類で返る属性がどう変わるか、GSI に強い整合性の読み込みを指定したときのエラー、キー属性を持たないアイテムが索引に載らないスパースインデックスの動きも、出力で確かめます。
確認環境:Ubuntu 24.04、Python 3.11.15、boto3 1.43.103、AWS CLI 1.46.1。AWS の API をローカルで再現するツール(moto 5.2.3)で実行しました。掲載するコマンドとコードは、実際の AWS でそのまま使える形(接続先の指定なし)にしてあります。
GSIとLSIの違い 早見表
| 比べる点 | GSI(グローバル) | LSI(ローカル) |
|---|---|---|
| パーティションキー | テーブルと別の属性を選べる | テーブルと同じ属性 |
| ソートキー | 任意(無しでもよい) | 必須(テーブルと別の属性) |
| 作れるタイミング | テーブル作成時、または作成後に追加・削除 | テーブル作成時だけ(あとから追加・削除はできない) |
| 検索できる範囲 | テーブル全体(全パーティション) | 指定した 1 つのパーティションキー値の中 |
| 読み込みの整合性 | 結果整合性のみ | 結果整合性と強い整合性を選べる |
| ConsistentRead=True | ValidationException になる | 使える |
| 射影していない属性 | 取得できない(テーブルへ取りに行かない) | 要求すればテーブルから自動で取得される |
| キャパシティ | 索引ごとに独立して消費・設定 | テーブル本体の分を消費 |
| サイズの制約 | なし | 同じパーティションキー値のアイテム合計が 10 GB まで |
| 1 テーブルあたりの数 | 20 個(既定のクォータ) | 5 個 |
| キーの一意性 | 不要(同じキー値のアイテムを複数持てる) | 不要 |
| キー属性が無いアイテム | 索引に載らない(スパース) | 索引に載らない(スパース) |
出典は AWS 公式の開発者ガイド(末尾の「参考・出典」)です。迷ったら「別のパーティションキーで引きたいなら GSI、同じパーティションキーで並び順だけ変えたいなら LSI」と覚えておくと判断しやすくなります。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「DynamoDBのGSIとLSIの違い|作成・クエリ・射影をコードで解説」
題材のテーブルと、作るインデックス
注文を入れる Orders テーブルを使います。テーブル本体のキーは、パーティションキー customer_id(顧客)とソートキー order_id(注文番号)です。
| 名前 | 種類 | パーティションキー | ソートキー | 射影 | できるようになること |
|---|---|---|---|---|---|
| (テーブル本体) | ― | customer_id | order_id | ― | 顧客ごとの注文を注文番号順に取る |
| lsi_created | LSI | customer_id | created_at | INCLUDE(total) | 顧客ごとの注文を日付順・期間指定で取る |
| gsi_status | GSI | status | created_at | ALL | 顧客をまたいで、状態ごとの注文を日付順に取る |
| gsi_status_keys | GSI | status | created_at | KEYS_ONLY | 射影の比較用 |
| gsi_status_inc | GSI | status | created_at | INCLUDE(total) | 射影の比較用 |
| gsi_tracking | GSI | tracking_no | なし | KEYS_ONLY | 追跡番号から注文を引く(スパースインデックス) |
LSIつきのテーブルを作る(create-table)
LSI はテーブル作成時にしか定義できないので、create-table の --local-secondary-indexes に書きます。索引のキーに使う属性(ここでは created_at)は、--attribute-definitions にも型を書くのがポイントです。
aws dynamodb create-table --table-name Orders \
--attribute-definitions \
AttributeName=customer_id,AttributeType=S \
AttributeName=order_id,AttributeType=S \
AttributeName=created_at,AttributeType=S \
--key-schema AttributeName=customer_id,KeyType=HASH AttributeName=order_id,KeyType=RANGE \
--local-secondary-indexes 'IndexName=lsi_created,KeySchema=[{AttributeName=customer_id,KeyType=HASH},{AttributeName=created_at,KeyType=RANGE}],Projection={ProjectionType=INCLUDE,NonKeyAttributes=[total]}' \
--billing-mode PAY_PER_REQUEST \
--query 'TableDescription.{Table:TableName,LSI:LocalSecondaryIndexes[].IndexName}'
{
"Table": "Orders",
"LSI": [
"lsi_created"
]
}
LSI の KeySchema では、HASH にテーブルと同じ customer_id を書きます。ここを別の属性にしたい場合は、LSI ではなく GSI を選びます。
GSIをあとから追加する(update-tableのGlobalSecondaryIndexUpdates)
GSI は稼働中のテーブルに追加できます。update-table の --global-secondary-index-updates に、Create という操作を JSON で渡します。
aws dynamodb update-table --table-name Orders \
--attribute-definitions \
AttributeName=status,AttributeType=S \
AttributeName=created_at,AttributeType=S \
--global-secondary-index-updates '[{"Create":{"IndexName":"gsi_status","KeySchema":[{"AttributeName":"status","KeyType":"HASH"},{"AttributeName":"created_at","KeyType":"RANGE"}],"Projection":{"ProjectionType":"ALL"}}}]'
できあがりは describe-table で確認します。
aws dynamodb describe-table --table-name Orders \
--query 'Table.{LSI:LocalSecondaryIndexes[].[IndexName,Projection.ProjectionType],GSI:GlobalSecondaryIndexes[].[IndexName,IndexStatus,Projection.ProjectionType]}'
{
"LSI": [
[
"lsi_created",
"INCLUDE"
]
],
"GSI": [
[
"gsi_status",
"ACTIVE",
"ALL"
]
]
}
追加するときの決まりを、公式ドキュメントの記述から表にまとめます。
| 項目 | 内容 |
|---|---|
| 1 回の UpdateTable で作成・削除できる GSI | 1 つ |
| 追加直後の IndexStatus | CREATING(既存アイテムを索引へ書き込むバックフィルが進む) |
| Query・Scan に使えるようになる時点 | IndexStatus が ACTIVE になってから |
| 作成中のテーブル本体 | そのまま読み書きできる |
| 削除 | 同じ引数に Delete を渡す。テーブル本体の読み書きには影響しない |
削除は次のコマンドです(比較用に作った索引を消す例)。
aws dynamodb update-table --table-name Orders \
--global-secondary-index-updates '[{"Delete":{"IndexName":"gsi_status_keys"}}]'
boto3でインデックスを作り、データを入れてQueryする
ここからは Python(boto3)です。次の 1 ファイルで、比較用の GSI を 3 つ追加し、アイテムを 6 件入れて、テーブル本体・LSI・GSI に Query します。上の 2 つのコマンドを実行したあとに、そのまま動かせます。
# gsi_demo.py
import time
import boto3
from boto3.dynamodb.conditions import Key
from botocore.exceptions import ClientError
ddb = boto3.resource("dynamodb")
client = ddb.meta.client
table = ddb.Table("Orders")
def add_gsi(name, keys, projection):
"""GSI を 1 つ足し、ACTIVE になるまで待つ(1 回の UpdateTable で作れる GSI は 1 つ)"""
client.update_table(
TableName="Orders",
AttributeDefinitions=[{"AttributeName": k, "AttributeType": "S"} for k in keys],
GlobalSecondaryIndexUpdates=[{"Create": {
"IndexName": name,
"KeySchema": [{"AttributeName": k, "KeyType": t}
for k, t in zip(keys, ["HASH", "RANGE"])],
"Projection": projection,
}}],
)
while True:
gsis = client.describe_table(TableName="Orders")["Table"]["GlobalSecondaryIndexes"]
if all(g["IndexStatus"] == "ACTIVE" for g in gsis):
return
time.sleep(5)
# 1. 射影の違いを比べるための GSI(KEYS_ONLY と INCLUDE)
add_gsi("gsi_status_keys", ["status", "created_at"], {"ProjectionType": "KEYS_ONLY"})
add_gsi("gsi_status_inc", ["status", "created_at"],
{"ProjectionType": "INCLUDE", "NonKeyAttributes": ["total"]})
# 2. スパースインデックス用の GSI(tracking_no を持つアイテムだけが載る)
add_gsi("gsi_tracking", ["tracking_no"], {"ProjectionType": "KEYS_ONLY"})
# 3. データを入れる
items = [
{"customer_id": "C001", "order_id": "O-1001", "created_at": "2026-09-01", "status": "shipped", "total": 3200, "item_name": "ノート", "tracking_no": "T-501"},
{"customer_id": "C001", "order_id": "O-1002", "created_at": "2026-09-20", "status": "pending", "total": 1500, "item_name": "ペン"},
{"customer_id": "C001", "order_id": "O-1003", "created_at": "2026-09-10", "status": "shipped", "total": 800, "item_name": "付箋", "tracking_no": "T-502"},
{"customer_id": "C002", "order_id": "O-1004", "created_at": "2026-09-15", "status": "pending", "total": 5400, "item_name": "椅子"},
{"customer_id": "C002", "order_id": "O-1005", "created_at": "2026-09-05", "status": "shipped", "total": 2100, "item_name": "机上ライト", "tracking_no": "T-503"},
{"customer_id": "C003", "order_id": "O-1006", "created_at": "2026-09-18", "total": 990, "item_name": "下書き"}, # status なし
]
with table.batch_writer() as batch:
for it in items:
batch.put_item(Item=it)
def show(title, resp):
print(f"--- {title} Count={resp['Count']}")
for it in resp["Items"]:
print(" ", dict(sorted(it.items())))
# 4. テーブル本体:C001 を order_id 順に
show("table: customer_id=C001", table.query(
KeyConditionExpression=Key("customer_id").eq("C001")))
# 5. LSI:同じ C001 を created_at 順に(期間で絞る)
show("LSI lsi_created: C001, 9/5 以降", table.query(
IndexName="lsi_created",
KeyConditionExpression=Key("customer_id").eq("C001") & Key("created_at").gte("2026-09-05")))
# 6. GSI:顧客をまたいで status=shipped を新しい順に
show("GSI gsi_status: shipped (新しい順)", table.query(
IndexName="gsi_status",
KeyConditionExpression=Key("status").eq("shipped"),
ScanIndexForward=False))
# 7. 射影の違い
for name in ["gsi_status_keys", "gsi_status_inc", "gsi_status"]:
show(f"{name}: pending", table.query(
IndexName=name, KeyConditionExpression=Key("status").eq("pending")))
# 8. スパースインデックス
print("--- item counts")
print(" table :", table.scan(Select="COUNT")["Count"])
print(" gsi_status :", table.scan(IndexName="gsi_status", Select="COUNT")["Count"])
print(" gsi_tracking :", table.scan(IndexName="gsi_tracking", Select="COUNT")["Count"])
show("gsi_tracking: scan", table.scan(IndexName="gsi_tracking"))
# 9. GSI に ConsistentRead=True
for name in ["lsi_created", "gsi_status"]:
kc = Key("customer_id").eq("C001") if name.startswith("lsi") else Key("status").eq("shipped")
try:
r = table.query(IndexName=name, KeyConditionExpression=kc, ConsistentRead=True)
print(f"--- {name} ConsistentRead=True -> OK Count={r['Count']}")
except ClientError as e:
print(f"--- {name} ConsistentRead=True ->", e.response["Error"]["Code"])
print(" ", e.response["Error"]["Message"])
実行は python3 gsi_demo.py です。出力を、見たいことごとに区切って読んでいきます。
テーブル本体・LSI・GSIで返るアイテムの違い
--- table: customer_id=C001 Count=3
{'created_at': '2026-09-01', 'customer_id': 'C001', 'item_name': 'ノート', 'order_id': 'O-1001', 'status': 'shipped', 'total': Decimal('3200'), 'tracking_no': 'T-501'}
{'created_at': '2026-09-20', 'customer_id': 'C001', 'item_name': 'ペン', 'order_id': 'O-1002', 'status': 'pending', 'total': Decimal('1500')}
{'created_at': '2026-09-10', 'customer_id': 'C001', 'item_name': '付箋', 'order_id': 'O-1003', 'status': 'shipped', 'total': Decimal('800'), 'tracking_no': 'T-502'}
--- LSI lsi_created: C001, 9/5 以降 Count=2
{'created_at': '2026-09-10', 'customer_id': 'C001', 'order_id': 'O-1003', 'total': Decimal('800')}
{'created_at': '2026-09-20', 'customer_id': 'C001', 'order_id': 'O-1002', 'total': Decimal('1500')}
--- GSI gsi_status: shipped (新しい順) Count=3
{'created_at': '2026-09-10', 'customer_id': 'C001', 'item_name': '付箋', 'order_id': 'O-1003', 'status': 'shipped', 'total': Decimal('800'), 'tracking_no': 'T-502'}
{'created_at': '2026-09-05', 'customer_id': 'C002', 'item_name': '机上ライト', 'order_id': 'O-1005', 'status': 'shipped', 'total': Decimal('2100'), 'tracking_no': 'T-503'}
{'created_at': '2026-09-01', 'customer_id': 'C001', 'item_name': 'ノート', 'order_id': 'O-1001', 'status': 'shipped', 'total': Decimal('3200'), 'tracking_no': 'T-501'}
- テーブル本体:C001 の注文が、ソートキー order_id の順に並びます。
- LSI(lsi_created):同じ C001 の中で、created_at の順に並び、「9/5 以降」という範囲指定が効いています。返る属性は、テーブルのキー・索引のキー・INCLUDE で指定した total だけです。
- GSI(gsi_status):C001 と C002 の注文が混ざって返っています。パーティション(顧客)をまたいで引けるのは GSI だけです。ScanIndexForward=False で新しい順にしました。
Query の書き方は、テーブル本体のときと同じで、IndexName を足し、KeyConditionExpression に「その索引のキー」を書くだけです。
AWS CLIでIndexNameを指定してQueryする
CLI では --index-name を付けます。status は DynamoDB の予約語なので、#s という別名に置き換えて書きます。
aws dynamodb query --table-name Orders --index-name gsi_status \
--key-condition-expression '#s = :s AND created_at >= :d' \
--expression-attribute-names '{"#s":"status"}' \
--expression-attribute-values '{":s":{"S":"shipped"},":d":{"S":"2026-09-05"}}' \
--no-scan-index-forward --output table \
--query 'Items[].{customer:customer_id.S,order:order_id.S,created:created_at.S,total:total.N}'
-----------------------------------------------
| Query |
+-------------+------------+---------+--------+
| created | customer | order | total |
+-------------+------------+---------+--------+
| 2026-09-10 | C001 | O-1003 | 800 |
| 2026-09-05 | C002 | O-1005 | 2100 |
+-------------+------------+---------+--------+
LSI は --consistent-read を付けて、強い整合性で読めます。
aws dynamodb query --table-name Orders --index-name lsi_created \
--key-condition-expression 'customer_id = :c' \
--expression-attribute-values '{":c":{"S":"C002"}}' --consistent-read \
--query '{Count:Count,Items:Items[].[order_id.S,created_at.S,total.N]}'
{
"Count": 2,
"Items": [
[
"O-1005",
"2026-09-05",
"2100"
],
[
"O-1004",
"2026-09-15",
"5400"
]
]
}
射影(ProjectionType)で返る属性はこう変わる
射影とは、テーブルのどの属性を索引にコピーしておくかの指定です。キーが同じ(status + created_at)で射影だけが違う 3 つの GSI に、同じ条件(status = pending)で Query した結果が次です。
--- gsi_status_keys: pending Count=2
{'created_at': '2026-09-15', 'customer_id': 'C002', 'order_id': 'O-1004', 'status': 'pending'}
{'created_at': '2026-09-20', 'customer_id': 'C001', 'order_id': 'O-1002', 'status': 'pending'}
--- gsi_status_inc: pending Count=2
{'created_at': '2026-09-15', 'customer_id': 'C002', 'order_id': 'O-1004', 'status': 'pending', 'total': Decimal('5400')}
{'created_at': '2026-09-20', 'customer_id': 'C001', 'order_id': 'O-1002', 'status': 'pending', 'total': Decimal('1500')}
--- gsi_status: pending Count=2
{'created_at': '2026-09-15', 'customer_id': 'C002', 'item_name': '椅子', 'order_id': 'O-1004', 'status': 'pending', 'total': Decimal('5400')}
{'created_at': '2026-09-20', 'customer_id': 'C001', 'item_name': 'ペン', 'order_id': 'O-1002', 'status': 'pending', 'total': Decimal('1500')}
| ProjectionType | 索引に入る属性 | 上の出力で返った属性 | 向いている場面 |
|---|---|---|---|
| KEYS_ONLY | テーブルのキー + 索引のキー | customer_id、order_id、status、created_at | 一覧でキーだけ分かればよい。該当するキーを得てから本体を GetItem する |
| INCLUDE | KEYS_ONLY の内容 + NonKeyAttributes に書いた属性 | 上の 4 つ + total | 一覧画面に出す数項目だけを索引から返したい |
| ALL | テーブルの全属性 | 上に加えて item_name など全部 | 索引だけで詳細まで返したい。索引のサイズは最も大きくなる |
選ぶときの考え方は次の 3 点です。
- GSI は射影していない属性を返せません。 公式ドキュメントに「GSI の Query はテーブル本体から属性を取得できない」とあります。あとで必要になりそうな属性は INCLUDE に入れておくか、キーを得てから本体を GetItem で読みます。
- LSI は射影していない属性も要求できます。 その場合は DynamoDB がテーブル本体から自動で取得し、そのぶん読み込みの消費と応答時間が増える、と公式ドキュメントに書かれています。
- INCLUDE に書ける属性は、テーブルの全索引の合計で 100 個までです。同じ属性名を 2 つの索引に入れると 2 個と数えます(KEYS_ONLY と ALL はこの数に含まれません)。
GSIにConsistentRead=Trueを付けるとValidationException
強い整合性の読み込み(ConsistentRead=True)は、テーブル本体と LSI で使えます。GSI に付けると、リクエストが受け付けられません。スクリプトの最後の部分の出力です。
--- lsi_created ConsistentRead=True -> OK Count=3
--- gsi_status ConsistentRead=True -> ValidationException
Consistent reads are not supported on global secondary indexes
CLI でも同じです。
aws dynamodb query --table-name Orders --index-name gsi_status \
--key-condition-expression '#s = :s' --expression-attribute-names '{"#s":"status"}' \
--expression-attribute-values '{":s":{"S":"shipped"}}' --consistent-read
An error occurred (ValidationException) when calling the Query operation: Consistent reads are not supported on global secondary indexes
GSI は、テーブルへの書き込みが少し遅れて反映される「結果整合性」の索引です。書き込んだ直後の値を必ず読みたい処理は、テーブル本体(または LSI)に ConsistentRead=True で問い合わせる形にします。共通の関数で ConsistentRead を一律に付けている場合は、IndexName が GSI のときだけ外す分岐を入れると収まります。
キー属性を持たないアイテムは載らない(スパースインデックス)
索引のキーにした属性を持たないアイテムは、その索引に書き込まれません。これを利用して「条件に合うアイテムだけの小さな索引」を作るのが、スパースインデックスです。
入れた 6 件のうち、status を持たないアイテムが 1 件(O-1006)、tracking_no を持つアイテムが 3 件あります。件数を数えた出力です。
--- item counts
table : 6
gsi_status : 5
gsi_tracking : 3
--- gsi_tracking: scan Count=3
{'customer_id': 'C001', 'order_id': 'O-1001', 'tracking_no': 'T-501'}
{'customer_id': 'C001', 'order_id': 'O-1003', 'tracking_no': 'T-502'}
{'customer_id': 'C002', 'order_id': 'O-1005', 'tracking_no': 'T-503'}
テーブル本体は 6 件、gsi_status は 5 件、gsi_tracking は 3 件になりました。gsi_tracking を Scan すれば「追跡番号が付いた注文だけ」を、テーブル全体を読まずに取り出せます。
属性をあとから足したり消したりすると、索引の中身も追従します。
# sparse.py
import boto3
table = boto3.resource("dynamodb").Table("Orders")
def count(index):
return table.scan(IndexName=index, Select="COUNT")["Count"]
print("before :", {"gsi_status": count("gsi_status"), "gsi_tracking": count("gsi_tracking")})
# status が無かった O-1006 に status を付ける → gsi_status に載る
table.update_item(
Key={"customer_id": "C003", "order_id": "O-1006"},
UpdateExpression="SET #s = :s",
ExpressionAttributeNames={"#s": "status"},
ExpressionAttributeValues={":s": "pending"},
)
# O-1001 から tracking_no を消す → gsi_tracking から外れる
table.update_item(
Key={"customer_id": "C001", "order_id": "O-1001"},
UpdateExpression="REMOVE tracking_no",
)
print("after :", {"gsi_status": count("gsi_status"), "gsi_tracking": count("gsi_tracking")})
before : {'gsi_status': 5, 'gsi_tracking': 3}
after : {'gsi_status': 6, 'gsi_tracking': 2}
| やりたいこと | スパースインデックスの作り方 |
|---|---|
| 「処理待ち」のものだけ一覧したい | 処理待ちの間だけ付ける属性(例:pending_since)を GSI のキーにし、処理が済んだら REMOVE する |
| 任意入力の番号から逆引きしたい | その番号の属性を GSI のパーティションキーにする(未入力のアイテムは載らない) |
| 特定の種類のアイテムだけ集めたい | その種類にだけ持たせる属性をキーにする |
Queryでつまずきやすい2つのエラー
# err.py
import boto3
from boto3.dynamodb.conditions import Key
from botocore.exceptions import ClientError
table = boto3.resource("dynamodb").Table("Orders")
cases = {
"IndexName を付け忘れた": dict(KeyConditionExpression=Key("status").eq("shipped")),
"GSI でソートキーだけ指定": dict(IndexName="gsi_status",
KeyConditionExpression=Key("created_at").gte("2026-09-10")),
}
for label, kwargs in cases.items():
try:
table.query(**kwargs)
except ClientError as e:
print(f"{label}: {e.response['Error']['Code']}")
print(" ", e.response["Error"]["Message"])
IndexName を付け忘れた: ValidationException
Query condition missed key schema element: customer_id
GSI でソートキーだけ指定: ValidationException
Query condition missed key schema element: status
| メッセージ | 原因 | 直し方 |
|---|---|---|
| Query condition missed key schema element: customer_id | IndexName が無いので、テーブル本体のキー(customer_id)が必要と判断された | IndexName="gsi_status" を足す |
| Query condition missed key schema element: status | 索引のパーティションキーを指定していない | パーティションキーは必ず「=」で指定し、ソートキーの条件はそれに & で足す |
| Consistent reads are not supported on global secondary indexes | GSI に ConsistentRead=True | GSI では外す。必要ならテーブル本体か LSI を読む |
索引を付けても、Query の条件は「パーティションキーを等号で 1 つ、ソートキーは範囲や前方一致」という決まりのままです。索引は「その決まりを当てはめるキーの組を増やすもの」と考えると整理できます。
アクセスパターンからキーを決める手順
索引は「どう引きたいか」から逆算して決めます。
| 手順 | やること | Orders テーブルでの例 |
|---|---|---|
| 1 | 画面や処理が必要とする取り方(アクセスパターン)を全部書き出す | A:顧客の注文を番号順に/B:顧客の注文を日付の期間で/C:状態ごとの注文を新しい順に/D:追跡番号から注文を 1 件 |
| 2 | いちばん多い取り方を、テーブル本体のキーにする | A → customer_id + order_id |
| 3 | 残りを「パーティションキーが本体と同じか」で分ける | B は同じ(customer_id)、C・D は別 |
| 4 | 同じなら LSI の候補、別なら GSI | B → LSI、C・D → GSI |
| 5 | LSI の候補は、条件を確かめる | 強い整合性が要るか、1 つのパーティションキー値が 10 GB に収まるか、テーブル作成時に決められるか。どれかが合わなければ GSI にする |
| 6 | 射影を決める | 一覧に出す項目だけなら INCLUDE、詳細まで返すなら ALL、キーだけでよければ KEYS_ONLY |
| 7 | 一部のアイテムだけが対象なら、スパースにする | D は tracking_no を持つ注文だけが載る |
| 8 | 索引の数と書き込み量を見直す | 索引が増えるほど、書き込みのたびに更新される索引も増える |
GSIとLSIのどちらにするかの判断表
| 状況 | 選ぶもの |
|---|---|
| テーブルと別の属性で検索したい | GSI |
| すでに稼働しているテーブルに索引を足したい | GSI |
| 同じパーティションキーの中で、別の属性の順に並べたい・範囲で絞りたい | LSI(作成時に決められる場合)または GSI |
| 索引を読むときにも強い整合性が必要 | LSI |
| 1 つのパーティションキー値に大量のアイテムが集まる | GSI(LSI つきのテーブルは 10 GB の上限がかかる) |
| 将来、索引を消したり作り直したりする可能性がある | GSI |
LSI が付いたテーブルで、1 つのパーティションキー値のアイテム合計が 10 GB を超えると、ItemCollectionSizeLimitExceededException が返ることがある、と公式ドキュメントにあります。データが増え続ける設計では、この点を先に見積もっておくと安心です。
よくある質問
Q. GSIとLSIは、同じテーブルに両方作れますか? A. 作れます。この記事の Orders テーブルは LSI を 1 つ、GSI を複数持っています。LSI は 5 個まで、GSI は既定で 20 個までです。
Q. LSIをあとから追加したくなったら、どうすればよいですか? A. LSI は作成時にしか定義できないので、同じキー(パーティションキーが本体と同じ、ソートキーが別)の GSI を追加する方法が手軽です。GSI は結果整合性のみ、という違いだけ確認しておきます。
Q. GSIのキーの値は、重複していてもよいですか? A. かまいません。テーブル本体のキーは一意である必要がありますが、GSI・LSI のキーは一意でなくてよいと公式ドキュメントにあります。上の例でも status = shipped のアイテムが 3 件返っています。
Q. GSIを追加した直後にQueryしてもよいですか? A. IndexStatus が ACTIVE になってから使います。記事の add_gsi 関数のように、describe_table で状態を見て待つ処理を入れておくと確実です。
まとめ
- GSI は別のパーティションキーでテーブル全体を引く索引、LSI は同じパーティションキーの中で並び順を変える索引です。
- LSI は create-table のときだけ、GSI は update-table の GlobalSecondaryIndexUpdates であとから追加・削除できます(1 回に 1 つ)。
- Query は IndexName を足して、その索引のキーを KeyConditionExpression に書くだけです。
- 射影は KEYS_ONLY・INCLUDE・ALL の 3 種類で、返る属性が変わります。GSI は射影した属性だけを返します。
- GSI に ConsistentRead=True を付けると ValidationException になります。強い整合性が必要な読み込みは本体か LSI で行います。
- キー属性を持たないアイテムは索引に載りません。 この性質を使うと、必要なアイテムだけの小さな索引(スパースインデックス)を作れます。
- 索引は、アクセスパターンを書き出してから、本体のキー → LSI か GSI か → 射影の順に決めます。
参考・出典
- Amazon DynamoDB 開発者ガイド:Improving data access with secondary indexes
- Amazon DynamoDB 開発者ガイド:Using Global Secondary Indexes
- Amazon DynamoDB 開発者ガイド:Managing Global Secondary Indexes
- Amazon DynamoDB 開発者ガイド:Local secondary indexes
- Amazon DynamoDB 開発者ガイド:Quotas
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「DynamoDBのGSIとLSIの違い|作成・クエリ・射影をコードで解説」
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。