変化する (可変な) 属性で 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#8842 に
UpdateItem するだけで済ませたいところですが、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 は結果整合性であり、 追加のストレージ/書き込みがかかります。しかし頻繁に変わる値については、そのほうが いくらか安く (約3対4の書き込みユニット)、変更のたびに削除と作り直しをするよりずっと シンプルです。
DynoTable でキーを設計する
ベースの読み取りと GSI の読み取りの両方について、キー条件をDynamoDB 式ビルダー で組み立ててプレビューできます。
DynoTable では、続いてクエリをどのインデックス経由で実行するかを選び、ベースアイテムが 不変のキーを保つ一方で変わりやすい値が GSI 上でソートされる様子を見られます。両方の 読み取りを実データで並べて確認できます。

落とし穴と次のステップ
- キー属性を
UpdateItemしようとしない。 拒否されます。キーの値はアイテムの生涯を 通じて固定です。 - どうしても動かすなら、削除と put をトランザクションで行う。 決してガードのない2つの 書き込みとしてはいけません。
- 不変のベースキー + GSI を優先する。 ソートに使い_かつ_変化させるあらゆる属性について。
- GSI の結果整合性を忘れない。 再ソートされたエントリは短い伝播遅延の後に現れます。
- 関連:ソートキー戦略、 GSI と LSI、トランザクション。
可変な属性が GSI 上でベーステーブルと比べてどうソートされるか見てみたいですか? DynoTable をダウンロードして、自分のインデックスを直接探索しましょう。
書き込みコスト: 削除と put vs GSI 更新
us-east-1 オンデマンドで 1 KB のチケットアイテムを想定した、おおよその WCU 比較です(実際の課金は AWS の丸め規則に従います)。
| パターン | API 呼び出し | 典型的な WCU 影響 |
|---|---|---|
| ベースキー上のトランザクション削除 + put | TransactWriteItems (2 ops) | トランザクション料金で操作あたりアイテムサイズの約2× |
status 属性を更新。GSI が再伝播 | 1回の UpdateItem | ベース書き込み + GSI 書き込み(1 KB アイテム + 射影属性で約 2 WCU) |
GSI 経路はアプリ層のオーケストレーションを避け、削除と put のあいだでクラッシュして行が失われる窓を消します。シンプルな書き込みと引き換えに、インデックス読み取りの結果整合性を受け入れます。
ステータス変更が毎分何度も飛ぶなら、料金計算機 でアイテムサイズと更新レートを見積もってください。
ステータス順リスト向けのスパース GSI
open のチケットだけがステータス順キューを必要とするなら、スパースインデックス を使います。status = open のあいだだけ GSI1PK = TEAM#7 と GSI1SK = STATUS#open#... を書きます。チケットがクローズしたら、更新時に GSI キー属性を外すか省略します — ベースのソートキーで削除と put をしなくても、アイテムはインデックスから落ちます。
インデックスを小さく保ち、一覧しないクローズ済みチケットを索引しないようにできます。
優先すべき不変のベースキー
| 変動フィールド | ベーステーブル SK | より良いベース SK | 変動フィールドの置き場所 |
|---|---|---|---|
| 注文ステータス | STATUS#shipped#ORD#99 | ORD#99 | GSI ソートまたは属性 |
| タスク優先度 | P#1#TASK#12 | TASK#12 | GSI ソート |
| ユーザー表示名 | NAME#alice#USER#5 | USER#5 | 非キー属性 |
タイムスタンプと不変 id(CREATED#2026-06-27T10:00:00Z、TICKET#8842)は、ベーステーブル自体で時系列順が必要なときの安定したベースソートキーになります。
コーディング前に GSI を設計する
シングルテーブル設計ツール でアクセスパターンを写像してください — 「チーム別・優先度順に未解決チケットを一覧」と入れ、提案された GSI1PK / GSI1SK テンプレートを確認します。次に 式ビルダー でキー条件を組み立て、結合テスト用に クエリビルダー でページ付きクエリを吐き出します。
ステータス変更後の read-your-writes
UpdateItem のあと、ベーステーブルへの強い整合性読み取りは新しい status をすぐ示します。GSI へのクエリは少し遅れることがあります。GSI ソートのキューへリダイレクトする UI は、古い行を許容するか、精度が要るときは id でベーステーブルから取り直すべきです。


