DynamoDB 分割區索引鍵如何工作
你的不是一列——它是一個地址。DynamoDB 對這個鍵做雜湊,而雜湊決定了哪臺物理機器存放這個項目。鍵選得好,負載就攤開;選得差,一臺伺服器就得獨自扛下壓力。
DynamoDB 分割區索引鍵如何工作?
DynamoDB 把你的送進一個內部雜湊函式,而那個雜湊決定了哪個物理分割區存放這個項目。這個鍵不像 SQL 列那樣被排序或索引——它是一個地址。選一個高基數的鍵,負載就攤開到許多分割區;選一個低基數的鍵,單個分割區就得扛下全部壓力。
- 鍵是被雜湊的,不是被排序的。 DynamoDB 把你的分割區索引鍵送進一個內部雜湊來挑選分割區。兩個相鄰的值在磁碟上落得八竿子打不著。
- 一個分割區是一個真實的儲存單元。 每個大約上限為 10 GB、每秒 3,000 讀取單元、每秒 1,000 寫入單元。你的流量要被你的鍵攤到多少個分割區上一除。
- 熱鍵是那個坑。 把大多數請求都灌向一個分割區索引鍵值,你就會在那個分割區上遭遇限流,而表的其餘部分閒坐著。
- 高基數的鍵才贏。 你擁有的、被均勻命中的不同鍵值越多,能吸收負載的分割區就越多。
先弄清這個鍵究竟在做什麼
從 SQL 過來,主索引鍵是一個被排序、被索引的列,你在它上面做 JOIN 和 ORDER BY。在 DynamoDB 裡,分割區索引鍵(有時叫雜湊鍵)做的是另一回事:它決定_放置位置_。
DynamoDB 把分割區索引鍵喂進一個內部雜湊函式。輸出對映到一個鍵空間,而鍵空間被切成一段段區間——每段區間由一個物理分割區擁有。那個分割區是一個真實節點上的真實儲存。
所以分割區索引鍵回答一個問題:哪臺機器持有這個項目? (如果你有的話)只在那臺機器_內部_給項目排序。它在放置位置上不起任何作用。
跟著一次寫入穿過雜湊
假設你營運一個採集裝置讀數的 SaaS。你的表 SensorReadings 用一個分割區索引鍵 deviceId 和一個排序索引鍵 readingTs。你為 deviceId = "vac-7741" 寫入一條讀數。
這就是那次寫入所走的路徑——從你的鍵,到它落上的磁碟:
vac-7741 的寫入被雜湊到鍵空間中的一個點,那個點落在 P2 的區間裡,於是項目落上 P2——在那裡按 readingTs 排序。
要內化的一點是:"vac-7741" 和 "vac-7742" 只差一個字元,但它們的雜湊毫不相關。它們幾乎必然住在不同的分割區上。分割區索引鍵空間裡沒有「相鄰」這回事。
這是 DynamoDB 從最初的設計繼承來的一致性雜湊思想——2007 年的 Amazon Dynamo 論文(「Dynamo: Amazon's Highly Available Key-value Store」)正是透過雜湊把鍵鋪散到各節點,好讓沒有單個節點成為瓶頸。
在下面貼上一串分割區索引鍵值,看看一個雜湊如何把它們撒進各個桶。一個高基數的集合會均勻攤開;重複用同一個值,它就會全都堆進單個桶——正是下一節要講的。
這是一個用於建立直覺的教學雜湊,不是 DynamoDB 真正的內部雜湊——真實的函式、鍵空間和分割區邊界都是 AWS 內部實現。用它來培養對「攤開 vs 傾斜」的感覺,而不是用來預測某個鍵會落到哪個物理分割區。
尊重分割區的硬性上限
一個物理分割區是有限的。按照 AWS DynamoDB 開發者指南,每個大約至多容納:
| 上限 | 每個分割區 |
|---|---|
| 儲存 | ~10 GB |
| 讀取吞吐 | 每秒 3,000 讀取單元 |
| 寫入吞吐 | 每秒 1,000 寫入單元 |
當一個分割區被填過 10 GB,或你的預置吞吐需要更多空間時,DynamoDB 會拆分它——鍵空間區間被切開,項目重新分佈到更多分割區上。這是自動的;你不會去觸發它。
坑在於:一次拆分可以在排序索引鍵邊界處把某個分割區索引鍵的項目集合切開,於是一個繁忙鍵的負載能攤到更多分割區上。拆分救不了的是單個熱_項目_、一個不斷增大的排序索引鍵,或一張帶 LSI 的表——這些都會把集合釘死在一個分割區上。
給這個陷阱起個名字:熱分割區
熱分割區是那個經典的坑。它發生在一個分割區索引鍵值(或極少數幾個)吸收了不成比例的流量份額時。
具體的翻車場景:你把 SensorReadings 換成一個分割區索引鍵 region,取值像 "us-east"、"eu-west"。三個地區意味著三個鍵值,意味著——至多——三個分割區在幹真活。用讀取猛砸 "us-east",它就會在 3,000 RCU 處限流,而表的總預置容量卻閒置著。
DynamoDB 的自適應容量能緩和這一點——它可以把閒置的吞吐挪向一個繁忙分割區,並把單個極熱的鍵隔離到它自己的分割區上。AWS 在 re:Invent 的「Advanced Design Patterns for DynamoDB」深入講座裡詳述過這一點。但自適應容量買來的是時間,不是免疫:單個熱_項目_、一個不斷增大的排序索引鍵,或一個 LSI,仍然會把一個鍵的上限壓在單個分割區上。要為攤開而設計;別指望這張安全網。
選一個高基數的鍵
修復之道是基數——不同鍵值的數量,以及流量命中它們有多均勻。
- 低基數(
region、status、true/false):分割區少、流量集中,你很早就限流。 - 高基數(
deviceId、userId、一個訂單 ID):許多值雜湊到許多分割區,負載攤開,餘量增長。
從 SQL 過來,你會樂呵呵地給一個 status 列建索引並在它上面過濾。但作為 DynamoDB 的分割區鍵,那是個陷阱——它攤不開。把低基數的屬性留作過濾條件,或作為次要索引的排序索引鍵,永遠別把它當作決定放置位置的東西。
當一個天然不錯的鍵仍然傾斜時——一小撮巨鯨租戶把其餘的甩在後頭——就加一個字尾,把一個邏輯值扇出到 N 個分割區上,例如給分片寫入路徑用 tenantId#3。你在讀取時再重新聚合。
一旦你的鍵攤開,要在一個分割區_內部_定位項目,你就要寫一個作用於排序索引鍵的 KeyConditionExpression。在把它接進程式碼之前,你可以在 DynamoDB 運算式構建器裡針對你自己的 schema 組裝一個:
deviceId = "vac-7741" AND readingTs BETWEEN "2026-06-01" AND "2026-06-30"
那會從單個分割區讀取一臺裝置的六月視窗——是一次 Query,不是 Scan。分割區索引鍵釘住機器;排序索引鍵條件收窄行。
陷阱與後續步驟
- 別按在 SQL 裡讀起來順手來挑鍵。 按什麼能_攤開_來挑。基數第一,查詢便利第二。
- 別以為表的總容量在每個鍵上都歸你用。 吞吐是按分割區來的;單個熱值就能限流,而表看著卻閒著。
- 別跟拆分較勁。 它是自動的、由雜湊驅動的——你的活是給它足夠多不同的鍵去攤開。
一旦你的鍵乾淨地攤開,接下來的決定就是如何在一個分割區內佈局項目——見單表設計——以及何時一個次要索引才是應對第二種訪問模式的對的工具。
下載 DynoTable,在 SQL Workbench 裡對你的分割區索引鍵跑一個 GROUP BY,看看在某個鍵變成熱分割區之前,哪些鍵在堆積項目。