中階閱讀時間 4 分鐘

DynamoDB Sort Key 策略

一個 DynamoDB 主鍵是一或兩個屬性:單獨一個 partition key,或一個 partition key 加上一個 sort key。partition key 決定哪個實體 partition 存放某個 item。

sort key 決定那個 partition 內 item 的 順序 — 而那個 排序正是讓 Query 強大的東西。

挑錯 sort key,你仍能寫入資料,但你就失去了範圍讀取、 排序,以及從一個 collection 得來的數個存取模式。

從 SQL 過來,你會在事後伸手去拿一個 ORDER BY 或一個次要 index。在 DynamoDB 中,你預先把順序烤進 key 裡,否則就得不到它。

DynamoDB sort key 是如何運作的?

DynamoDB sort key 在 partition 內排序 item,讓 Query 能執行範圍讀取 — >=betweenbegins_with — 而非每次只能取得一個 item。排序是依編碼後 key 的位元組順序,因此設計時(例如 ISO-8601 時間戳、零填補數字)需確保位元組順序符合你想讀取的順序。

  • sort key 是你 partition 內的 index。 它在磁碟上排序 item collection,所以 Query 能做範圍讀取(>=betweenbegins_with)而非 單一 GetItem
  • 排序是編碼後 key 的位元組順序。 設計 key,讓位元組順序 等於你想讀的順序 — 一個 ISO-8601 時間戳、一個零填補的 數字,絕不是原始 UUID 或 6/23/2026
  • 一個塑造良好的 sort key 服務多個存取模式。 一個複合 key (EVT#<timestamp>)同時是前綴與範圍 — 不需要 GSI。
  • 方向是免費的。 ScanIndexForward = false 以相同成本從最新讀起; 別存反轉的時間戳去假造它。

為什麼 sort key 是那個槓桿

沒有 sort key,partition 中每個 item 只能用它完整的 主鍵定址 — 頂多是一個 GetItem。加上一個 sort key,DynamoDB 就 在 partition 內 依它排序 來儲存 item,這解鎖了 Query

那意味著範圍條件(>=between)、前綴比對(begins_with), 以及一個讀升序或降序的 ScanIndexForward 旗標。

依據 AWS DynamoDB 開發人員指南,所有共用同一 partition key 的 item 組成一個 item collection,在磁碟上依 sort key 排序。

所以 sort key 不只是第二個識別子。它是你在 partition 內查詢 的 index。

那個排序是編碼後 sort key 的位元組順序:字串依 UTF-8 位元組比較,數字依數值比較。這一個事實驅動了下面幾乎每個 策略。

如果你想讓範圍查詢有意義,位元組順序就必須符合你想讀的 順序。

策略 1:讓 sort key 可排序

最常見的錯誤是一個沒有有意義排序的 sort key。一個隨機 UUID 給你唯一性,但沒有有用的範圍查詢 — 「給我最後 20 筆」 變得不可能,因為位元組順序是任意的。

改成把你排序與過濾所依據的值編碼 sort key,採用一種 位元組順序等於其邏輯順序的表示法。對時間戳而言,那 意味著一種字典序可排序的格式:一個 ISO-8601 字串或一個零填補的 epoch。

ISO-8601 的設計就是讓字串比較等於時間序比較 — 正是範圍查詢所需的。避免像 6/23/2026 這種格式;它們在月份 翻轉的當下就排錯了。

如果你依數字排序(一個版本計數器、一個分數),用 DynamoDB 原生的 Number 型別而非字串,這樣 42 才排在 9 之後而非 之前。

如果一個數字必須住在複合字串 sort key 內,把它零填補到固定 寬度。

策略 2:複合 sort key 表達階層

一個 sort key 能用一個分隔符(最常見是 #)串接多個區段來編碼 一個階層。一個 begins_with 條件就能選出一整個子樹:

SK
EVENT#2026-06#01#login
EVENT#2026-06#03#export
EVENT#2026-07#02#login

begins_with(SK, "EVENT#2026-06#") 只回傳六月的事件;更廣的 begins_with(SK, "EVENT#") 回傳全部。

區段順序是一個設計決定。由粗到細(年 → 月 → 日)會讓 相關 item 保持連續,所以一次範圍讀取仍是一個便宜查詢,而非在 partition 上四散。

策略 3:用 ScanIndexForward 控制方向

DynamoDB 以 升序 sort-key 順序儲存 item,並預設那樣讀。 要從最新讀起 — 活動動態的自然順序 — 在 Query 上設定 ScanIndexForward = false

這是一個讀取期旗標,不是 schema 決定:同一個 collection 以相同成本 服務兩個方向。別反轉你的時間戳(存一個「反向 epoch」)只為了得到降序讀取。

一個 item collection,以升序儲存一次,兩種讀法:

ScanIndexForward = trueScanIndexForward = falseItem collection(一個 PK)SK EVT#09:00SK EVT#14:00SK EVT#隔天最舊在前最新在前

相同 item、相同 partition、相同成本 — 只有讀取方向不同。

唯一例外:當你特別需要降序也同時是某個稀疏 index 或 分頁游標前進的順序時。除此之外, ScanIndexForward 是更簡單的槓桿。

演練範例:以行為者為範圍的 audit log

假設你在一個 SaaS 產品中記錄由行為者 — 使用者、服務、API key — 產生的帶時間戳事件,而你有兩個讀取:

  1. 某個行為者的活動串流,最新事件在前。
  2. 某個行為者在某時間窗口內的事件(例如「兩次部署之間的 一切」),用於調查。

兩個讀取都以單一行為者為範圍,所以行為者是 partition key,而 事件時間是 sort key。用通用的 key 名稱,讓同一張表稍後能容納其他 實體:

PKSKattributes
ACTOR#u_8814EVT#2026-06-23T09:12:04Zaction=login, ip, ua
ACTOR#u_8814EVT#2026-06-23T14:05:11Zaction=export, target
ACTOR#u_8814EVT#2026-06-24T08:40:55Zaction=login, ip, ua
ACTOR#svc_billingEVT#2026-06-23T00:00:00Zaction=invoice.run

EVT# 前綴加上一個 ISO-8601 時間戳給出一個可排序的 sort key。讀取 1 是 Query PK = "ACTOR#u_8814" 搭配 ScanIndexForward = false 取得最新在前。讀取 2 用 sort key 上的一個 between 條件縮窄同一個 partition:

Query
PK = "ACTOR#u_8814"
AND SK BETWEEN "EVT#2026-06-23T00:00:00Z"
AND "EVT#2026-06-23T23:59:59Z"

一個 collection、兩個存取模式、不需要 GSI — 因為 sort key 同時是一個前綴 (EVT#)與一個範圍(時間戳)。降序讀取與窗口讀取是 相同的 item、相同的順序;只有參數不同。

手寫那個 key condition,很容易搞砸 between 的邊界,或 屬性名稱上保留字的逸出。

DynamoDB Expression Builder 會為一個 begins_withbetween sort-key 條件產生 KeyConditionExpressionExpressionAttributeNamesExpressionAttributeValues

把它直接複製進你的 SDK 呼叫,而不必在執行期除錯逸出。

在 DynoTable 中做它

設計一個 sort key 是反覆的:寫幾個有代表性的 item、跑範圍 查詢、檢查列以你預期的順序回來。在一個 GUI 中對一張真實表這麼做, 勝過繞著程式碼來回往返。

在 DynoTable 中以一個 between 條件查詢某行為者的 audit-log collection,結果依最新在前排序。
在 DynoTable 中以一個 between 條件查詢某行為者的 audit-log collection,結果依最新在前排序。

翻轉排序方向、收緊 between 邊界,看著回傳的 collection 變化,而不寫一行程式碼 — 在你定下一個 sort-key 設計 之前確認它最快的方式。

陷阱與下一步

  • sort key 在一個 partition 內必須唯一。 如果兩個事件可能共用一個 時間戳,在 sort key 上附加一個區別子(一個序號或短 id), 讓複合鍵保持唯一。
  • 熱 partition 無法靠排序繞過。 如果某個行為者產生遠多於 其他人的事件,sort key 救不了你 — 你需要一個能散開負載的 partition-key 設計。請見 single-table design
  • 第二種排序順序需要第二個 index。 base table 的 sort key 給出 一種排序。要以不同方式(例如依事件類型)排序同一批 item,加一個 sort key 不同的 GSI — 並權衡 local vs global secondary index 的取捨。
  • 別為了「稍後再排序」而伸手去拿 ScanScan 之後做客戶端 排序會讀整張表並丟掉排序;那就是 Scan 地雷。改成把順序推進 sort key。

一旦 key condition 對了,試試 DynoTable 去建模那個 collection、並排跑升序與降序查詢,並在它出貨前對真實資料 驗證你的 sort-key 策略。

已更新