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 か → 射影の順に決めます。

参考・出典

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「DynamoDBのGSIとLSIの違い|作成・クエリ・射影をコードで解説」

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?