表設計最佳實務

更新時間:
Copy as MD

Table Store按分區鍵範圍自動將資料切分到多個服務節點。分區鍵選擇不當會導致熱點、資料扭曲或擴充瓶頸。本文介紹主鍵設計、屬性列設計和分表規劃的最佳實務,協助設計高效能、可擴充的資料表結構。

主鍵設計

Table Store根據分區鍵(第一列主鍵列)的範圍將資料自動切分為多個分區,分布在不同的服務節點上。主鍵設計的核心目標是讓資料和訪問壓力均勻分散,避免熱點。

資料散列

在分布式資料系統中,資料分布不均勻會導致以下問題:

  • 讀寫能力受限於單個分區,存在明顯瓶頸。

  • 熱點資料分布不均導致長尾效應,拖慢整體處理速度。

  • 熱點分區成為整個業務鏈路的瓶頸,影響上下遊系統。

資料均勻分布在多個分區時,讀寫壓力分散到各分區,每個請求僅覆蓋局部資料,可通過增加機器資源實現水平擴充。

說明

如果業務負載較低(TPS/QPS 在 1000 以下、資料量在 10 GB 以內且不會大幅增長),熱點問題的影響較小,單分區即可支撐。但架構設計上不應依賴單分區能力。

常見熱點問題與解決方案

以監控情境為例:資料表格儲存體每台機器在每個時間點的指標值,主鍵設計如下:

Timestamp(分區鍵)

MachineIp

問題

1718000001

10.10.0.1

每次寫入都追加到表的末尾(最後一個分區),產生尾部熱點

1718000002

10.10.0.2

1718000003

10.10.0.1

由於資料按分區鍵定界分割,所有寫入都集中在最後一個分區,導致尾部熱點,無法通過分區分裂來平衡負載。

方法一:調整分區鍵順序

將 MachineIp 放到主鍵第一列,Timestamp 放到第二列。調整後主鍵為 MachineIp = "10.10.0.1", Timestamp = 1718000001,寫入壓力按機器維度分散到不同分區。類似的分區鍵選擇如 UserIdDeviceIdOrderId 等業務上天然分散的欄位,都是常見做法。

說明

如果某個 IP 段的寫入量集中在同一分區,Table Store會自動將該分區切分,將壓力分散到多個分區上。

方法二:拼接 MD5 首碼

對 MachineIp 計算 MD5,取前 4 位十六進位字元拼接到 IP 前面作為分區鍵。例如 10.10.0.1 的 MD5 前 4 位為 a1b2,拼接後分區鍵變為 a1b2,10.10.0.1。這種方式打散了 IP 段的順序性,同時十六進位首碼有利於系統進行預分區。

方法三:全域有序需求的替代方案

如果業務需要按時間全域有序查詢,但又不希望產生尾部熱點,可以採用以下替代方案:

  • 局部有序:將分散欄位放在第一列,同一分散欄位下的資料仍按時間有序。例如在某個 MachineIp 下按 Timestamp 有序。

  • 分桶寫入:將時間對 N 模數作為第一列主鍵(0 到 N-1),時間作為第二列。例如使用 16 個桶,寫入時分區鍵為 timestamp % 16 = 3,讀取時並行查詢 16 個桶後合并。桶數越多,壓力越分散,但讀取時需查詢的桶也越多。

  • 多元索引:資料表使用均勻散列方式寫入,通過多元索引實現按時間等欄位的有序查詢。多元索引自動將資料散列到多個分區,查詢時自動合并結果。

主鍵列長度與資料量約束

  • 同一分區索引值下的資料量建議控制在 10 GB 以內(無硬性限制),因為相同分區索引值的行無法進一步切分。

  • 主鍵列長度限制為 1 KB,使用較短的主鍵有利於提升查詢速度。

分區鍵設計原則

分區鍵是資料表的第一列主鍵列。Table Store根據分區鍵的範圍將資料自動切分為多個分區,每個分區調度到不同的服務節點。分區鍵設計需要滿足以下原則:

  • 同一分區索引值下的資料量不超過 10 GB。

  • 不同分區索引值中的資料在邏輯上獨立。

  • 訪問壓力不集中在小範圍連續的分區索引值中。

通過拼接方式使用分區鍵

如果單個分區索引值的所有行總資料量可能超過 10 GB,建議將多個主鍵列拼接為一個新的分區鍵,以分散資料。拼接時需注意以下規則:

  • 拼接後的分區鍵必須能有效地將原來相同分區索引值的記錄拆分為不同分區索引值的記錄。

  • 拼接整數類型主鍵列時在高位補 0,保持排序一致。例如 OrderNumber = 123 補零後為 00000123,確保字典序與數值序一致。

  • 串連符應選擇 ASCII 值小於所有可用資料字元的字元,避免影響新分區鍵的字典序。具體選擇取決於業務資料的字元集。例如,假設 SellerID 的取值包含數字和字母(如 a100a1001),不同串連符對排序的影響如下:

    串連符

    拼接後的排序

    原因

    結果

    :

    000054:a1001 排在 000054:a100: 前面

    : 的 ASCII 值大於數字,a1001 在第 5 位比較 1 < :

    排序錯誤

    ,

    000054,a100, 排在 000054,a1001 前面

    , 的 ASCII 值小於所有數字和字母,字典序與未經處理資料順序一致

    排序正確

在分區鍵中加入雜湊首碼

如果必須使用順序增長的列作為分區鍵,建議在分區鍵前加入雜湊首碼,使相鄰的資料在表中隨機分布,保證訪問壓力均勻。

說明

加入雜湊首碼後,原來連續的資料會被打散,無法再使用範圍讀取操作讀取邏輯上連續的資料。如果需要範圍查詢,可以使用多元索引替代。

屬性列設計

  • 控制行寬度:Table Store支援寬行(最多數十萬個屬性列),但一次性讀取超寬行可能逾時。原則上不建議超過萬列,可通過指定列名或分頁方式讀取。

  • 屬性列大小限制:單個屬性列值不超過 2 MB。超過該限制的資料需要拆分成多列儲存,或使用Object Storage Service進行儲存。

  • 按訪問頻率拆分表:當行的屬性列較多且各列的訪問頻率差異較大時,將高頻和低頻屬性列分別儲存在不同的表中。例如商品管理系統中,商品數量和商品價格訪問頻率高,商品簡介(大文本)訪問頻率低,可以拆分為兩張表。

  • 壓縮大文本:較大的屬性列文字建議壓縮後以二進位類型儲存,節省儲存空間並降低讀寫計算開銷。

  • 貨幣和價格使用 Long 類型:Table Store不支援 BigDecimal 類型。涉及金額等需要精確計算的欄位,推薦使用 Long 類型儲存最小單位值(例如 5 元 3 角 2 分儲存為 53200),避免 Double 類型的精度損失。

分表與容量規劃

分表格儲存體

推薦單個多元索引的資料量控制在 200 億行以內。如果資料規模超過 200 億行,請聯絡Table Store支援人員進行分表評估和設計。

例如某使用者最大的日誌表當前為 61 億行,年增長 21 億行,3 至 5 年內不會超過 200 億行,因此無需分表。當存量資料較大且增長速度快時,應提前規劃分表策略。

冷熱資料分離

資料通常具有時效性:近期資料訪問頻率高,較早的資料逐漸成為冷資料。冷熱資料混存在同一張表中會導致訪問壓力不均勻,預留讀寫輸送量無法充分利用。

建議用不同的表區分冷熱資料,並設定不同的預留讀寫輸送量。對熱資料可建立多元索引支援多維查詢,冷資料使用固定維度二級索引降低成本。多元索引支援資料生命週期(TTL),可以通過索引 TTL 實現冷熱資料的自動區分。

大批量資料匯入

Table Store大大量匯入資料時,如果按主鍵順序寫入,寫入壓力會集中在某個分區,影響匯入速度。建議採取以下措施:

  • 將巨量資料集切分成多個小集合,使用多個背景工作執行緒隨機選取小集合并行匯入。

  • 寫入前聯絡Table Store支援人員進行預分區,使資料寫入時即可分散到多個分區。

  • 使用 TableStoreWriter 進行高並發非同步寫入,自動實現分桶分發和批量提交。

設計案例:學生卡消費記錄

以某大學學生卡消費記錄系統為例,說明表設計的決策過程。資料表的主鍵列包括學生卡 ID(CardID)、商家 ID(SellerID)、消費終端 ID(DeviceID)和訂單號(OrderNumber)。商務規則如下:

  • 每張學生卡對應一個 CardID,每個商家對應一個 SellerID。

  • 每個消費終端對應一個全域唯一的 DeviceID。

  • OrderNumber 在單個終端內唯一且按時間遞增,但全域不唯一。

  • 消費記錄即時寫入。

主鍵與分區鍵選擇

首先確定分區鍵。根據各主鍵列的特性,對比不同分區方式的效果:

分區方式

分析

CardID 為分區鍵

每個學生日均消費次數有限,寫入壓力分散在幾萬張卡上,資料散列度較好。推薦。

SellerID 為分區鍵

商家數量少,大部分消費集中在少數商家,容易產生熱點,不推薦。

DeviceID 為分區鍵

終端裝置全域唯一,分散度好。但少量高頻終端(如熱門食堂的刷卡機)仍可能產生局部熱點。

OrderNumber 為分區鍵

OrderNumber 在終端內按時間遞增,直接作為分區鍵會導致尾部熱點。需要加入雜湊首碼。

設計結論

綜合分析,CardID 的資料散列度最好(幾萬張卡、每人每天幾次消費),推薦作為分區鍵。確定主鍵順序為 CardID, DeviceID, SellerID, OrderNumber。對於大多數業務情境,完成主鍵和分區鍵的選擇後即可滿足需求。

進階最佳化

基礎的分區鍵選擇可能不足以滿足效能需求,以下介紹兩種常見的進階最佳化方式。

情境一:單一分區鍵資料量過大時,拼接多列為分區鍵

如果業務要求按 DeviceID 查詢並選擇其作為分區鍵,單個 DeviceID 下的資料量可能超過 10 GB。此時可以將 DeviceID、SellerID 和 CardID 三列拼接為新的分區鍵,將同一終端裝置的記錄按商家和學生卡進一步拆分。

拼接前

DeviceID(分區鍵)

SellerID

CardID

OrderNumber

問題

54

10

1001

000001

DeviceID=54 的所有記錄在同一分區,長期累積資料量可能超 10 GB

54

20

1002

000002

78

20

1001

000003

拼接後

DeviceID,SellerID,CardID(新分區鍵)

OrderNumber

效果

54,10,1001

000001

同一終端裝置在不同商家和學生卡的消費記錄被拆分到不同分區索引值下

54,20,1002

000002

78,20,1001

000003

情境二:分區索引值順序增長時,加入雜湊首碼

如果業務要求按 OrderNumber 查詢並選擇其作為分區鍵,由於 OrderNumber 在終端內按時間遞增,會導致尾部熱點。此時需要對 OrderNumber 計算 MD5 雜湊首碼來打散資料分布:

原始 OrderNumber

MD5 前 4 位

HashOrderNumber(分區鍵)

000001

e2c8

e2c8,000001

000002

3f79

3f79,000002

000003

a5b1

a5b1,000003

加入雜湊首碼後,可以使用相同演算法對 OrderNumber 計算雜湊首碼得到對應的 HashOrderNumber。但雜湊首碼會打散原有順序,無法再使用範圍讀取操作讀取邏輯上連續的記錄。

冷熱資料分離

學生卡消費記錄具有明顯的時效性:近期記錄查詢頻率高,已畢業學生的記錄幾乎不再訪問。建議按以下方式規劃冷熱資料:

  • 將近期活躍資料(如近 1 年)和歷史資料分別儲存在不同的表中,設定不同的預留讀寫輸送量。

  • 活躍表配置較高的預留輸送量,歷史表配置較低的輸送量,降低儲存成本。

  • 對活躍表建立多元索引支援多維度查詢(如按商家、按時間範圍),歷史表僅保留主鍵查詢即可。

  • 定期將活躍表中超過時效的資料移轉到歷史表中。