本文へスキップ
郭立 (leeguoo)

GSI を単独で import することはできない

prod の DynamoDB テーブルが CloudFormation を迂回して課金方式とインデックスを直接変更され、その結果 stack 全体が 5 日間ロールバックで詰まった。drift の原因と解決法を記録しつつ、直感に反する点を明確にする:CFN の import はリソース全体単位である。

事故はこう始まった:誰かが prod のあの DynamoDB テーブルのプロビジョンドキャパシティが足りないと思い、直接 2 本の aws dynamodb update-table を叩いた —— 課金を PROVISIONED からオンデマンドに切り替え、ついでに GSI も追加した。変更後にテストして、使えることを確認し、作業完了。

CloudFormation は通していなかった。

1 週間後、まったく無関係なリリースで、CloudFormation がそのままロールバックで固まり、stack 全体が UPDATE_ROLLBACK_COMPLETE に入り、誰も再デプロイのボタンを押せなくなった。エラーメッセージは一文だけ:Attempting to create an index which already exists。

CFN は自分の台帳だけを信じ、現実を見ない

CloudFormation の本質は台帳だ:テンプレートを渡すと、「この一式のリソースはこうあるべき」と記録し、それに従って作成・更新する。問題は、diff を取るときに比較しているのが新しいテンプレート vs 自分自身の台帳だということ —— クラウド上のリソースが実際にどうなっているかは見に行かない。

だから誰かがそれを迂回してリソースを直接変更すると、台帳と現実が一致しなくなる。これがdriftだ。

CFN 台帳(そう思っているもの) 課金:PROVISIONED GSI:4 個 ≠ クラウド上の現実(帯域外で変更済み) 課金:PAY_PER_REQUEST GSI:5 個 あなたが「正しい」新テンプレート(5 GSI)を出すと—— CFN は古い台帳(4 GSI)と比べて「1 つ足りない、create しに行く」と判断する → その GSI は現実にはとっくに存在する → already exists → ロールバック 💥
drift の致命的なところ:CFN は台帳に基づいて差分を計算し、現実にはすでに存在するリソースを create しに行く

さらに厄介なのはロールバックそのものだ:CFN が「GSI を追加する」失敗した操作をロールバックするとき、自分がたった今作ったと思い込んでいるその GSI を削除しに行く —— しかしそれは実際には本番で使っているインデックスだ。ロールバックによる誤削除。だから誰も触れなかった。デプロイのたびに、この賭けをすることになるからだ。

直感に反する点:GSI を単独で import することはできない

自然に思いつくのは、では CFN にこの帯域外で作られた GSI を改めて「引き取らせれば」いいのでは、ということだ。CloudFormation には resource import がある。

だがここに落とし穴がある:import はリソース全体単位である。テーブル全体、バケット全体は import できるが、GSI は独立したリソースではなく、AWS::DynamoDB::Table の property だ。「すでに stack に管理されているテーブル内のある子属性」だけを単独で import することはできない。

CFN に子属性の drift を受け入れさせたいなら、いったんテーブル全体を stack から外し、その後テーブル全体を import し直すしかない。

解法:remove → retain → reimport

核心は DeletionPolicy: Retain を利用すること —— CFN がリソースを「手放す」ときに、実際には削除しないようにする。

1. Retain を追加 削除=実テーブルを保持 2. テンプレートから削除 CFN 台帳から切り離す 3. 現実に合わせる 5 GSI / オンデマンド 4. IMPORT 再び引き取る 台帳 =現実 →→→→ 実テーブルは全工程でオンライン提供中:第 2 ステップの detach は DynamoDB API を一切呼ばず、CFN 台帳だけを変更する
テーブル全体を外してから import し直す。その間も実際のテーブルはずっとオンラインのまま

第 4 ステップで実際に import changeset を書くときには、目立つ場所には書かれていない落とし穴がさらに 2 つある。一度踏んで初めて分かるものだ:

aws cloudformation create-change-set \
  --change-set-type IMPORT \
  --template-body file://original.yaml \   # Original YAML(transform 前)が必要。processed JSON ではない
  --parameters "$PARAMS" \                 # UsePreviousValue で stack の全パラメータを漏れなく渡す
  --resources-to-import '[{"ResourceType":"AWS::DynamoDB::Table",...}]'
  • パラメータは UsePreviousValue を使って全件を一度に渡す必要がある(必須の数個だけではない)。渡し漏れたパラメータはテンプレートのデフォルト値に戻り、CFN が大量の無関係なリソースを「変更された」と判定して、"N resources modified" として拒否する。
  • テンプレートは Original YAML(transform 前)を使う必要がある。get-template がデフォルトで返す processed JSON ではない。processed 版をそのまま出し直すと、CFN は SAM によって展開生成されたリソースについても差分を検出してしまう。

自分へのメモ

本当に怖いのはこの一連の操作ではない —— detach のステップはそもそも DynamoDB に触れず、テーブルはずっとサービスを提供し続け、失敗しても戻せる。怖いのはそれが 1 週間埋まったままになってから爆発し、しかもまったく関係のないリリースで爆発したことだ。

  • IaC を迂回して直接 update-table したり、コンソールを変更したりするのは、遅効性の毒だ。その場では使えても、代償は帳簿に記録され、次の不運な誰かがリリースするときに一括精算される。
  • CFN の diff は「テンプレート vs 帳簿」であり、決して「現実」を含まない。だから detect-stack-drift は定期的に実行する価値がある。ロールバック中に教えられるまで待ってはいけない。
  • import はリソース粒度だ。子属性(GSI、パラメータグループの特定項目……)のドリフトは、諦めてリソース全体を reimport するしかない。

あのテーブルはいま CFN の管理下に戻り、帳簿と現実が一致し、detect-stack-drift もクリーンだ。次のリリースでは、ようやく賭けをしなくて済む。

同じカテゴリの記事 · いじって作る

2026年9月30日
3分で読めます
Pastyx:Macでコピーしたものを、ショートカット一つですべて取り戻す
さっきコピーしたコード、リンク、画像が、新しいコピーで上書きされてしまった?Pastyx はコピーしたものをすべて記録し、⌘⇧V で一発検索、Enter で貼り付けできます。ネイティブ macOS アプリ、ローカル優先、US$1.99 の買い切りで、7日間無料で試せます。
→
2026年6月27日
8分で読めます
Synology DSM で SSH ポートを開けても接続できない:ポートずれの振り返り
Synology DSM パネルで SSH を有効化したりポートを変更したりしても、ポートが開かないことがあります。原因はパスワード、アカウント、ルーターのポート転送とは限らず、DSM のフロントエンド状態と sshd_config に残った古いポート設定が重なっている場合もあります。
→
2026年6月18日
1分で読めます
月額5ドルで86万RPSに耐えて、まだ上限に届かない——ホットスポットKVのアーキテクチャ設計と思 honest な振り返り
Cloudflare Workers(月額5ドルから)上で動く秒単位のホットスポットKV。複数リージョンの負荷テストで読み取りパスは86万+ RPSに達し、workerエラーはゼロ。先にボトルネックに達したのは負荷生成側だった。この記事では、その設計思想、実際に起きた2回の障害の完全な調査(1回目は直ったと思っていたが、2回目で本当の根本原因にたどり着いた:読み取りと書き込みが同じ単一スレッドオブジェクトの同じキューで競合していた)、そして最終的な解法である読み書き分離に加え、「強整合性」という言葉を改めて明確にする。
→
← 前の記事
Codex は本当にこっそり SSD を消耗させているのか?自分のマシンで調べてみた
次の記事 →
Claude CodeにあなたのChatGPTサブスクリプションで自動画像生成させる:API key不要、Turnstileをどう回避しているのか

コメント

コメントは即時公開されますが、ポリシー違反時は非表示になる場合があります。

最大 1000 文字。