進階閱讀時間 3 分鐘

DynamoDB 物理分割區

物理分割區是 DynamoDB 實際存放你資料的單位:一片 SSD,跨可用區複製,儲存你鍵空間的一個切片。你的表是一個邏輯上的東西。分割區才是位元組——以及吞吐量限制——真正所在之處。

DynamoDB 分割區是如何運作的?

DynamoDB 把你的表存放在多個物理分割區上——跨可用區複製的 SSD 切片。每個分割區上限約為 10 GB、每秒 3,000 個讀取單位、每秒 1,000 個寫入單位。你的的雜湊決定一個條目落在哪個分割區上,而 DynamoDB 會隨著分割區增長或變熱而自動分裂它們。

  • 每個分割區上限約為 10 GB 儲存、每秒 3,000 個讀取單位、每秒 1,000 個寫入單位。這些上限是每分割區的,不是每表的。
  • 你的的雜湊挑選分割區。鍵相同的條目落在一起;單個熱——或者一個單調遞增的排序索引鍵——就是把某一個分割區釘死的東西。
  • DynamoDB 替你分裂分割區——按大小,也按持續的熱度——包括在排序索引鍵邊界上分裂一個鍵的條目集合,除非一個 LSI 或一個不斷遞增的排序索引鍵擋住了它。
  • 在還有富餘容量時限流就是那個訊號。當你的表處在 5% 使用率時出現 ProvisionedThroughputExceeded 錯誤,說明單個分割區已經打滿了。

一個條目如何找到它的分割區

DynamoDB 把你的分割區索引鍵值餵給一個內部雜湊函式。雜湊輸出挑選物理分割區。同一個鍵進去,同一個分割區出來——每一次都是。

從 SQL 過來,這沒有對應物。沒有你要調優的索引 B 樹,沒有你手工指派的分片鍵。放置是一個你不控制、也永遠看不見的雜湊。

共享同一個分割區索引鍵的條目構成一個,一起儲存並按排序索引鍵排序。這就是為什麼對一個鍵的 Query 是便宜的——它讀取一個分割區上一段連續的區間。(參見 Query vs Scan。)

拿一個遊戲的對局事件儲存來說。表的鍵是 arenaId(分割區)和 eventKey(排序):

# Item
arenaId    = "ARENA#7f3a"
eventKey   = "EVT#1719100800#a91c"
playerTag  = "Nightjar"
dmgDealt   = 412

競技場 7f3a 的每一條事件都雜湊到同一個分割區,並按排序索引鍵順序堆疊。對"讀取這場對局的時間線"很棒。但如果那一個競技場攬下了全部流量,就是個隱患。

每個分割區都強制執行的三個上限

單個分割區被設計為最多提供:

上限每分割區計為
儲存~10 GB原始條目位元組
讀取容量每秒 3,000 個讀取單位1 RU = 一次 4 KB 的強一致讀取
寫入容量每秒 1,000 個寫入單位1 WU = 一次 1 KB 的寫入

來源:AWS《分割區索引鍵設計最佳實踐》指南。

條目大小會放大這套數學。一個 20 KB 的條目每次強一致讀取耗費 5 個讀取單位,所以一個分割區在限流之前只能服務約 600 次這樣的讀取/秒——不是 3,000。寫入成本每 1 KB 向上取整,讀取成本每 4 KB 向上取整。

陷阱在於:這些是_分割區_限制,不是_表_限制。你的表可以預置為 40,000 WCU,卻仍然限流,因為所有寫入都在猛捶一個上限為 1,000 的分割區。

分割區如何分裂

DynamoDB 在兩種情況下自動增加分割區。你從不執行任何命令。

按大小分裂。當一個分割區填充到接近 ~10 GB 時,DynamoDB 把它的鍵範圍一分為二,並把一半條目移到一個新分割區。儲存透明地增長;你的讀取和寫入全程照常工作。

為熱度分裂。當一個分割區承受接近其吞吐量上限的持續流量時,DynamoDB 分裂那個熱鍵範圍,好讓每一半都落在自己的分割區上。AWS 把這叫作 split-for-heat(為熱度分裂)機制。那些自行停下的短暫限流突發,往往意味著 split-for-heat 起效了——儘管短暫的尖峰也可能只是突發容量耗盡了。

大小熱度分割區 A~10 GB /分裂觸發條件?按儲存位元組把範圍對半分按流量把範圍對半分兩個分割區各有自己的容量單個熱條目仍在一個分割區上

分裂在眾多鍵之間騰出空間,而 split-for-heat 甚至能在排序索引鍵的某處切開一個鍵的條目集合。它無法分散的,是單個熱_條目_、一個不斷遞增的排序索引鍵,或一個被 LSI 釘住的集合。

為什麼一個熱鍵能贏過分裂器

這裡是那個陷阱。分裂重新分配的是分割區索引鍵的_範圍_。如果你的流量集中在一個鍵值上,每個請求都雜湊到同一個分割區,就沒有剩下的範圍可分了。

如果競技場 7f3a 是一場錦標賽決賽,拉著每秒 4,000 次寫入,而其他每個競技場都空閒著,你就會在 1,000 處限流——而 split-for-heat 在這裡救不了它,因為帶時間戳字首的 eventKey 是單調的,所以每一次新寫入都落在一個狹窄排序索引鍵範圍的前沿,沒有可切下來的東西。較新的 KeyRangeThroughputExceeded 限流原因恰恰點名了這件事:是一個分割區的鍵範圍、而非整張表,超出了它的限制。

修復在資料模型裡,不在容量滑塊上。對熱鍵做寫分片:追加一個小字尾,讓一個邏輯競技場鋪開到 N 個物理分割區上。

arenaId = "ARENA#7f3a#3"   # shard 0..9, chosen per write

讀取隨後在各分片間扇出,並在用戶端合併。你可以在動一行應用程式碼之前,用 DynamoDB 運算式構建器對每個分片原型化鍵形狀和 Query

一個細微之處:LSI 例外

有一種情況下,儲存_確實_是按分割區索引鍵封頂的。沒有時,一個條目集合會跨越它服務自身儲存位元組和吞吐量所需的任意多個分割區——數十億個排序索引鍵值都沒問題。

加上一個 LSI,一個分割區索引鍵的整個集合就必須裝進單個 10 GB 的分割區裡,因為 LSI 共享它。這就是 GSI vs LSI 裡講到的每 PK 懸崖——也是大多數團隊轉而選擇 GSI 的又一個原因。

設計得讓分割區保持涼爽

你真正能控制的槓桿是分割區索引鍵。挑一個相對於行數有許多不同值的鍵,好讓流量均勻鋪開。(更多模式見單表設計。)

  • 高基數鍵。一個每使用者或每租戶的鍵,勝過一個人人同時猛捶的每天或每狀態的鍵。
  • 留意已知的熱鍵。一個"當前錦標賽"或"今天"的值,在你上線之前就是一個集中風險,而不是之後。
  • 對不可避免的熱鍵做分片。當一個鍵必須承受超額流量時,加個字尾是標準的逃生艙。

在還有富餘容量時限流,就是你的訊號:某一個分割區熱了。檢查那個傾斜的條目集合,並在 DynoTable 裡演練一套分片鍵佈局——把它指向你自己的表,在 SQL Workbench 裡對分割區索引鍵做 GROUP BY,看清哪些鍵佔了主導,並在它把你叫醒之前建模好修復方案。

已更新