中級読了 3 分

変化する (可変な) 属性で DynamoDB をソートする

ある属性を軸にソートキーを設計して、その順序でアイテムをクエリできるようにする——ところが その属性が変わってしまう。チケットのステータス、注文の状態、タスクの優先度。DynamoDB の ルール: キー属性はその場で更新できません。 プライマリキーは アイテムの生涯を通じて不変です。キーの一部である値を変えるのは、アイテムを編集している のではなく、動かしていることになり、DynamoDB はそれを明示的にやらせます。

DynamoDB のソートキーは変更できますか?

いいえ。ソートキーはプライマリキーの一部であり、DynamoDB のキー属性は不変です。UpdateItem はパーティションキーやソートキーの値を編集できず、「アイテムを移動する」操作は存在しません。 変更するには、古いアイテムを削除して新しいアイテムを put するか、変わりやすい値を代わりに GSI のソートキーに持たせます。

  • キー属性は不変。 パーティションキーやソートキーの値を UpdateItem できません。 DynamoDB に「アイテムを移動する」操作はありません。
  • キーの値を変えるには、古いアイテムを削除して新しいものを put します。原子的になる よう、理想的にはトランザクションの中で行います。
  • より良い方法:変わりやすい値をベーステーブルのキーから外し、代わりに GSI のソートキーに置きます。GSI のキーは_変更できます_。ベース アイテムを更新すればインデックスのエントリが再伝播されるだけだからです。
  • 変化しないソートキーを選ぶ (タイムスタンプ、不変の id)。アクセスパターンが許す限り。

問題:ソートに使いたいのに変わり続けるステータス

サポートデスクを運営していて、チームのチケットをステータス順に一覧したいとします。そこで ステータスをソートキーに入れます。

PK: TEAM#7   SK: STATUS#open#TICKET#8842

さて、チケットが pending に移ります。ソートキーを STATUS#pending#TICKET#8842UpdateItem するだけで済ませたいところですが、DynamoDB はキー属性を変更する書き込みを すべて拒否します。キーはアイテムのアドレスです。アドレスをその場で編集することはできません。 ソートに選んだステータスこそ、じっとしていてくれないものなのです。

選択肢1:削除して作り直す (原子的に)

その値がベーステーブルのキーに必ず存在しなければならないなら、変更するには古いアイテムを 取り除いて新しいものを書きます。

1. DeleteItem  PK=TEAM#7  SK=STATUS#open#TICKET#8842
2. PutItem     PK=TEAM#7  SK=STATUS#pending#TICKET#8842  (same attributes)

これをTransactWriteItemsの中で行い、削除と put が 両方成功するか両方失敗するようにします。さもないと、その間でクラッシュするとチケットが 失われたり重複したりします。これは機能しますが、ステータス変更のたびに2つの書き込みに加えて トランザクションが発生します。たまの変更なら問題ありませんが、頻繁なものには高くつきます。

選択肢2:可変な値をベースキーから外す (推奨)

ベーステーブルのキーを不変なもの (チケット id) にし、変わりやすく ソート可能な値を GSI のソートキーに置きましょう。

Base:  PK: TICKET#8842   status: "open"   teamId: TEAM#7
GSI:   GSI1PK: TEAM#7    GSI1SK: STATUS#open#TICKET#8842

こうするとステータスの変更は、ベースアイテムの status 属性への普通の UpdateItem に なります。status はベーステーブルのキーではないので DynamoDB は_これを許します_。 そして DynamoDB は GSI のエントリを自動的に再伝播して、新しくソートされた位置へ移します。 API 呼び出しは1回で、原子性はあなたの代わりに処理されます。トランザクションも削除の手順も 不要です (内部では DynamoDB はやはり古いインデックスエントリを削除して新しいものを書くので、 インデックス化された変更のコストは約3書き込みユニット。トランザクションの削除と put の 約4に対して)。

はいいいえ、GSI のソートキーステータスが open から pendingに変化その値はベーステーブルのキー?トランザクション内で削除して再作成通常の UpdateItem。GSIが再伝播

GSI は結果整合性であり、 追加のストレージ/書き込みがかかります。しかし頻繁に変わる値については、そのほうが いくらか安く (約3対4の書き込みユニット)、変更のたびに削除と作り直しをするよりずっと シンプルです。

DynoTable でキーを設計する

ベースの読み取りと GSI の読み取りの両方について、キー条件をDynamoDB 式ビルダー で組み立ててプレビューできます。

DynoTable では、続いてクエリをどのインデックス経由で実行するかを選び、ベースアイテムが 不変のキーを保つ一方で変わりやすい値が GSI 上でソートされる様子を見られます。両方の 読み取りを実データで並べて確認できます。

DynoTable で、ベースアイテムが不変のキーを保ったまま、ステータスでソートした GSI をクエリしている様子。
DynoTable で、ベースアイテムが不変のキーを保ったまま、ステータスでソートした GSI をクエリしている様子。

落とし穴と次のステップ

  • キー属性を UpdateItem しようとしない。 拒否されます。キーの値はアイテムの生涯を 通じて固定です。
  • どうしても動かすなら、削除と put をトランザクションで行う。 決してガードのない2つの 書き込みとしてはいけません。
  • 不変のベースキー + GSI を優先する。 ソートに使い_かつ_変化させるあらゆる属性について。
  • GSI の結果整合性を忘れない。 再ソートされたエントリは短い伝播遅延の後に現れます。
  • 関連:ソートキー戦略GSI と LSIトランザクション

可変な属性が GSI 上でベーステーブルと比べてどうソートされるか見てみたいですか? DynoTable をダウンロードして、自分のインデックスを直接探索しましょう。

書き込みコスト: 削除と put vs GSI 更新

us-east-1 オンデマンドで 1 KB のチケットアイテムを想定した、おおよその WCU 比較です(実際の課金は AWS の丸め規則に従います)。

パターンAPI 呼び出し典型的な WCU 影響
ベースキー上のトランザクション削除 + putTransactWriteItems (2 ops)トランザクション料金で操作あたりアイテムサイズの約2×
status 属性を更新。GSI が再伝播1回の UpdateItemベース書き込み + GSI 書き込み(1 KB アイテム + 射影属性で約 2 WCU)

GSI 経路はアプリ層のオーケストレーションを避け、削除と put のあいだでクラッシュして行が失われる窓を消します。シンプルな書き込みと引き換えに、インデックス読み取りの結果整合性を受け入れます。

ステータス変更が毎分何度も飛ぶなら、料金計算機 でアイテムサイズと更新レートを見積もってください。

ステータス順リスト向けのスパース GSI

open のチケットだけがステータス順キューを必要とするなら、スパースインデックス を使います。status = open のあいだだけ GSI1PK = TEAM#7GSI1SK = STATUS#open#... を書きます。チケットがクローズしたら、更新時に GSI キー属性を外すか省略します — ベースのソートキーで削除と put をしなくても、アイテムはインデックスから落ちます。

インデックスを小さく保ち、一覧しないクローズ済みチケットを索引しないようにできます。

優先すべき不変のベースキー

変動フィールドベーステーブル SKより良いベース SK変動フィールドの置き場所
注文ステータスSTATUS#shipped#ORD#99ORD#99GSI ソートまたは属性
タスク優先度P#1#TASK#12TASK#12GSI ソート
ユーザー表示名NAME#alice#USER#5USER#5非キー属性

タイムスタンプと不変 id(CREATED#2026-06-27T10:00:00ZTICKET#8842)は、ベーステーブル自体で時系列順が必要なときの安定したベースソートキーになります。

コーディング前に GSI を設計する

シングルテーブル設計ツール でアクセスパターンを写像してください — 「チーム別・優先度順に未解決チケットを一覧」と入れ、提案された GSI1PK / GSI1SK テンプレートを確認します。次に 式ビルダー でキー条件を組み立て、結合テスト用に クエリビルダー でページ付きクエリを吐き出します。

ステータス変更後の read-your-writes

UpdateItem のあと、ベーステーブルへの強い整合性読み取りは新しい status をすぐ示します。GSI へのクエリは少し遅れることがあります。GSI ソートのキューへリダイレクトする UI は、古い行を許容するか、精度が要るときは id でベーステーブルから取り直すべきです。

更新日