Dev Study
AWSサービスの内部原理 コース

9. GSIは影の別テーブル — 非同期複製と結果整合の宿命

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回の伝播遅延の再来。

④射影 = 何をコピーするかの選択

テーブル → GSI へコピーする属性の集合
本体テーブルの属性
射影(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"}}'
公式ドキュメントで詳しく ↗