上級読了 3 分

ダウンタイムなしの DynamoDB 移行

SQL の出身者には、移行はテーブルをロックしてすべての行を書き換える ALTER TABLE です。DynamoDB には変更すべきスキーマがありません — アイテムはスキーマレスなので、属性や新しいエンティティ型の追加は無料です。

難しいのは、新しいデータが奉仕しなければならないアクセスパターンと、世界を止める書き換えなしにそれに奉仕するようライブデータを再形成することです。

ダウンタイムなしで DynamoDB テーブルを移行するには?

DynamoDB には ALTER TABLE がないため、移行でテーブルがロックされることはありません。属性、新しいキーの形、または新しい UpdateTable でオンラインに追加し、ライブデータを段階的に再形成します。古いアイテムは読み取り時に遅延バックフィルするか、スロットルされたスイープで処理し、移行期間中は両方の形式にデュアルライトします。一斉切り替えは不要です。

  • ALTER TABLE はない。 アイテムはスキーマレスです。「移行」は、属性、新しいキーの形、新しいインデックスを追加することを意味し — 固定された列の集合を書き換えることでは決してありません。
  • 新しい書き込みは簡単、古いアイテムが問題。 既存の行は新しい属性を持たないので、新しいインデックスやクエリは、バックフィルするまで静かにそれらを取りこぼします。
  • インデックスはオンラインで追加し、遅延でバックフィルする。 UpdateTable はライブテーブルに GSI を構築します。古いアイテムは読み取り時に(遅延)か、制御されたスイープでバックフィルしましょう — 決して一斉切り替えではなく。
  • 移行期間中はデュアルライトする。 両方の形が共存する間、古い形式と新しい形式をまとめて書き込み、どちらの読み取りパスも古くならないようにします。

列ではなくアクセスパターンとして捉える

1つのテーブルで SaaS のワークスペース製品を運用しているとします。アイテムは PK = "WS#<id>" を使い、SK はエンティティごとにオーバーロードされています:

PKSKattributes
WS#a91METAname, tier
WS#a91DOC#2026-04-01#x7title, author, body
WS#a91DOC#2026-04-02#k2title, author, body

ここでプロダクトがドキュメントへのコメントと、新しい読み取りを望みます: 「あるメンバーがワークスペース全体で書いたすべてのコメントを、新しい順にリストする」。その最後の節が移行です。新しいエンティティ型だけなら些末ですが、現在のキーが答えられないクエリに奉仕することが仕事です。

まず新しいエンティティ型を追加する

コメントは同じパーティション内の新しいアイテムにすぎません — 移行の儀式も、新しいテーブルもありません:

PKSKattributes
WS#a91DOC#2026-04-01#x7#CMT#01HZ...author, text, createdAt

PK = "WS#a91"SK begins_with "DOC#2026-04-01#x7#CMT#" を付けた Query が、既に1つのドキュメントのコメントをリストします。既存のドキュメントは手つかずです。この半分は初日に出荷されます — 同じパーティションが両方を保持する理由はアイテムコレクションとオーバーロードされたキーを参照してください。

新しいクエリには GSI が必要

「あるメンバーによるすべてのコメント、新しい順」はベーステーブルでは提供できません — memberIdPK でも SK のプレフィックスでもないからです。それは新しいインデックスで、それを正しく選ぶこと自体が独自の決定です: GSI と LSI の比較を参照してください(LSI はテーブル作成時に存在しなければならないので、ライブテーブルでの移行には GSI が唯一の選択肢です)。

汎用的な GSI1 を追加し、新しい属性を新しいコメントアイテムに書き込みます:

GSI1PKGSI1SK
MEMBER#u442026-04-02T09:15:00Z

Query GSI1 WHERE GSI1PK = "MEMBER#u44"ScanIndexForward = false を付けると、メンバーごとの新しい順のコメントが得られます。

インデックスをオンラインで構築する

UpdateTable はダウンタイムなしでライブテーブルに GSI を追加します。DynamoDB はバックグラウンドで既存のアイテムをインデックスにバックフィルします。インデックスは完了するまで CREATING/バックフィル中を報告し、その後 ACTIVE に切り替わります(GSI の管理)。

UpdateTable: GSI1 を追加インデックスステータス:CREATING既存アイテムをバックフィルステータス: ACTIVEGSI1 Query が安全

ここに2つの罠があります。1つ目、AWS は、新しいキーが不均等に分散する場合、GSI の追加がベーステーブルの書き込みをスロットリングし得ると警告します — 低トラフィックの時間帯に追加し、CloudWatch を監視しましょう。2つ目、インデックスは ACTIVE になった後でも結果整合性です。書き込みが一瞬 GSI 上に見えないことがあります。なぜ GSI は結果整合性なのかを参照してください。

古いアイテムをバックフィルする

GSI は GSI1PK/GSI1SK持つアイテムだけをインデックスします。移行前のコメント — 属性が存在する前に書かれたもの — は、バックフィルが完了した後でも決して現れません。オンライン GSI のバックフィルは既存のアイテムをコピーしますが、それらに無い属性を作り出すことはできません。値を追加しなければなりません。

2つの戦略:

戦略仕組み使うべきとき
遅延古いアイテムの読み取り時に、新しい属性を書き戻す古いアイテムが頻繁に読まれる。コストを少しずつ
スイープページネーションされた Scan がすべての古いアイテムを1度更新期限までに GSI を完成させる必要がある

スイープでは、Scan でページングし、古いコメントごとに条件付き UpdateItem でインデックス属性を追加し、並行する書き込みを決して上書きしないようにします。

条件は、属性がまだ存在しないことをガードします。attribute_not_exists(GSI1PK) を手で打つのではなく、正確な ConditionExpressionUpdateExpressionDynamoDB Expression Builder で構築してコピーしましょう。

移行期間を通してデュアルライトする

すべての古いアイテムが新しい属性を持つまで、2つの形が共存します。書き込みパスはすべての書き込みで新しい形式を埋めなければなりません — 新しいコメントも、古いものへのあらゆる更新も — そうしてギャップだけが縮みます。

検証できるバックフィルの終了条件を選びましょう: スイープがテーブル全体をページングした、または遅延パスが、未変換のアイテムが設計上古いと言えるほど長く実行された。そのときに初めて古い読み取りパスを取り除きます。これを省くと、クエリの一部が静かに不完全な結果を返したまま、移行が「完了」します。

バックフィル中に、新しいインデックス属性が欠けているアイテムを見つけるため、DynoTable でテーブルをページングしている様子。
バックフィル中に、新しいインデックス属性が欠けているアイテムを見つけるため、DynoTable でテーブルをページングしている様子。

落とし穴

  • 属性の追加 ≠ バックフィル済み。 新しい GSI は古いアイテムについては空で始まります。クエリを信頼する前にカバレッジを検証しましょう。
  • キーをその場で変えるのは移行ではなく書き換え。 アイテムの PK/SK を変異させることはできません。新しいキーで新しいアイテムを書き、古いものを削除します。コピーしてから削除し、その間はデュアルリードする計画を立てましょう。
  • トランザクショナルな切り替えはない。 テーブル全体が反転する瞬間はありません。両方の形がライブである間も安全であるよう、すべてのステップを設計しましょう。

次のステップ

新しいキーとオーバーロードされたコレクションをシングルテーブル設計で健全性チェックし、ライブテーブルをページングしてバックフィルが完了したことを確認しましょう。DynoTable を試して、テーブルを閲覧し、バックフィルされていないアイテムを見つけ、自分のデータに対して条件付き更新を実行してください。

更新日