← AWSサービスの内部原理 コース
9. GSIは影の別テーブル — 非同期複製と結果整合の宿命
GSIの不思議な挙動はすべて「影の別テーブルへ非同期にコピーされる」という一枚の絵から導けます。図で追っていきましょう。
①見た目はインデックス、実体は別テーブル
本体テーブルキー: UserId をハッシュ→配置
別のキーで並べ替えて再編成
GSIキー: GameTitle をハッシュ→別の配置
影の別テーブル- ・違うキー → 違うハッシュ値 → 違うパーティションに配置
- ・公式: GSIはベーステーブルと異なるプライマリキーで編成される
- ・インデックス=同じデータを別の並び順で持ち直したコピー、の原理そのもの
GSIは本体に足す索引ではなく、別キーで再編成された独立したもう一つのテーブル。
②書けるのは本体だけ、GSIは非同期に追従
アプリの書き込み
GSIには直接書けない本体にのみ書く
本体テーブルGSI反映を待たず 200 で完了
非同期に自動追従(通常1秒未満)
GSI結果整合モデルで更新
- ・公式: Applications never write directly to an index
- ・まれな障害時にはより長い遅延が起きうる
本体への書き込みはGSIへの反映を待たずに成功する。だから本体とGSIに時間差が必ず生まれる。
③だから結果整合性読み取りしかできない
本体 = 原本新しい値あり
コピーが遅れて伝わる
GSI = コピー先まだ古い値
ここを読むと古い値が返る強い整合性は原理的に約束できない
- ・第4回の3レプリカ間の伝播ラグと同じ構図が、本体↔GSI間で一段大きく再来
- ・--consistent-read はGSIでは使えない
- ・暗記ではなく非同期レプリケーションからの当然の帰結
原本からコピーへ遅れて伝わる以上、途中を読めば古い値が返りうる。第5回の伝播遅延の再来。
④射影 = 何をコピーするかの選択
本体テーブルの属性
射影(projection)で選ぶ
KEYS_ONLYキーだけ・最小
INCLUDE+指定した非キー属性
ALL全属性・最大
クエリ自己完結で速いがコスト増- ・すべて「コピー」なので、書き込みは本体とGSIの両方に発生
- ・書き込み総コスト = 本体のWCU + GSI更新のWCU の合計
- ・ALLほど読みは速いが、ストレージも書き込みWCUも増える
射影の選択とは、読み取りの速さと書き込み・保管コストのトレードオフを「何をコピーするか」で決めること。
⑤GSIの容量不足が本体まで詰まらせる
書き込みリクエスト
受付時にチェック
本体の書き込み容量十分?
すべてのGSIの容量十分?
どれか不足なら
本体への書き込みごとスロットリング
GSIだけでなく本体も止まる- ・同期書き込みではない。非同期反映が溜まって破綻するのを入口で防ぐバックプレッシャ
- ・公式の推奨: GSIの書き込みキャパシティは本体テーブル以上にする
コピー先(GSI)の容量が足りない書き込みは入口で受け付けない設計。破綻を未然に防ぐ事前チェック。
サンプルコード(フレームワーク環境が必要なため表示のみ)
# GSIを作成: 本体とは別のキー(GameTitle/TopScore)でデータを再編成した「影の別テーブル」を作る
# Projection=KEYS_ONLY|INCLUDE|ALL … 何をコピーするか。ALLほど速いが書き込みWCU・保管コスト増
# 下のProvisionedThroughput指定はプロビジョンドモードのテーブル前提。
# GSIは本体のキャパシティモードを継承するため、オンデマンドのテーブルでは指定不要。
aws dynamodb update-table --table-name GameScores \
--attribute-definitions AttributeName=GameTitle,AttributeType=S AttributeName=TopScore,AttributeType=N \
--global-secondary-index-updates '[{"Create":{
"IndexName":"GameTitleIndex",
"KeySchema":[{"AttributeName":"GameTitle","KeyType":"HASH"},{"AttributeName":"TopScore","KeyType":"RANGE"}],
"Projection":{"ProjectionType":"KEYS_ONLY"},
"ProvisionedThroughput":{"ReadCapacityUnits":10,"WriteCapacityUnits":10}}}]'
# GSIへのクエリ: 結果整合性読み取りのみ(--consistent-read は使えない=非同期コピーゆえの宿命)
# 本体には書けてもGSIにまだ届いていない一瞬があり、古い結果が返りうる前提で設計する
aws dynamodb query --table-name GameScores --index-name GameTitleIndex \
--key-condition-expression "GameTitle = :t" \
--expression-attribute-values '{":t":{"S":"Meteor Blasters"}}'