分區:range 還是 hash、二級索引擺哪、以及怎麼重新平衡
· tech
#distributed-systems#book-notes#partitioning
📑 目錄
複製是同一份資料放多台;分區(partitioning,也叫 sharding)是把資料切開,每台只放一部分——當資料量一台裝不下、或寫入 throughput 一台吃不消,這是唯一的出路。兩者幾乎總是並用:資料先切成分區,每個分區自己再做主從複製。你其實已經見過好幾個分區系統了:Redis Cluster 的 16384 個 slot、Kafka 的 partition、MPP 資料庫的分片——這章給你的,是它們背後共通的三道難題:怎麼切、索引擺哪、怎麼重新平衡。
第一題:怎麼切——range 保順序,hash 打熱點
分區的目標是把資料和負載攤平;最怕的就是傾斜(skew)——大家擠在同一區(熱點),分了等於沒分。兩種主流切法,正好是一對取捨:
第二題:二級索引擺哪——local 還是 global
主鍵查詢好辦(key → 分區),麻煩的是二級索引:「找出所有紅色的車」——紅色的車散在每個分區裡,索引該擺哪?兩種答案,又是一對取捨:
第三題:節點增減了,怎麼重新平衡
加機器、換機器,分區要跟著搬——rebalancing 的鐵則只有一條:千萬別用 hash mod N。 N 一變,幾乎所有 key 的歸屬都變,等於整庫大搬家。主流解法你其實已經在 Redis Cluster 那篇看過活教材:
- 固定分區數:一開始就切出遠多於節點數的分區(Redis Cluster 的 16384 個 slot、Kafka 的 partition、Elasticsearch 的 shard),節點增減時只搬整個分區的歸屬,key→分區的對應永遠不變。加一層間接,搬家變成搬整齊的箱子。
- 動態分區:分區大到門檻就自動分裂、小了就合併(HBase)——分區數跟著資料量長。
- 自動 vs 手動:全自動 rebalancing 很誘人,但 DDIA 提醒它有個陰暗面——節點只是過載變慢時,自動化若誤判它掛了、開始搬資料,搬移本身的負載會把事情雪上加霜,像極了連鎖失效的劇本。所以成熟系統多半「自動提案、人類按下確認鍵」。
最後是路由:請求怎麼找到對的分區?三種——隨便問一台由它轉發、掛一層路由服務(靠 ZooKeeper 記分區表)、或客戶端自己知道(Redis Cluster 的 MOVED 就是讓客戶端把 slot 表快取起來的這一型)。
反思
「保順序」和「打熱點」不可兼得,複合主鍵是我最愛的折衷
range vs hash 這對取捨,我在真實資料裡撞過:時間序列資料按時間 range 切,結果所有當下的寫入永遠砸在最後一個分區——分了十個區,九個在納涼。想通這章後我才看懂 Cassandra 複合主鍵的漂亮:用 hash 決定「去哪一區」(打散熱點),用其餘欄位在「區內」排序(保住範圍掃描)——兩個要求各給一半,但各給在對的維度上。這也解釋了 Kafka 的 key→partition、Redis 的 hash tag 為什麼都長這樣:先用 hash 公平地分家,再在家裡維持秩序。 遇到「既要均勻又要有序」的需求,先想「能不能把兩個要求拆到兩個層級」,常常就解了。
同一道「帳付在哪」的選擇題,第三次出現了
local vs global index,我讀到一半就笑了——這不就是 LSM vs B-tree、乃至一路以來那道題嗎:同一筆成本,你要付在寫入時,還是付在讀取時? local index 寫入便宜(只動本區),把帳掛在每次讀的 scatter/gather 上;global index 讀取精準,把帳掛在跨區的寫入上(所以多半只敢非同步,索引慢半拍)。判準也跟從前一致:讀寫比例決定一切——讀遠多於寫、查詢模式集中,global 划算;寫入密集、查詢能忍 scatter,local 簡單可靠。一個模式在一本書裡反覆出現三次,那它就不是知識點,是這個領域的地心引力——把它內化,新工具的文件你能一目十行。
過載時的自動搬遷,是好心辦壞事的經典劇本
Rebalancing 那段對「全自動」的警告,踩中我 SRE 的神經:節點過載變慢 → 自動化誤判它死了 → 開始把它的分區搬走 → 搬移吃掉更多頻寬與 I/O → 其他節點也慢了 → 更多誤判、更多搬移——教科書級的連鎖失效,而且每一步都是「為你好」。這跟我在 Sentinel 講的「誤判比不作為更貴」同源,但資料搬移的版本更兇,因為搬資料本身就是重負載。所以我對「資料層的自動化」的立場比對「無狀態層」保守一級:Pod 掛了自動補、沒問題;但要大規模搬資料的決定,自動提案、人來按鍵——在最混亂的時刻,把「踩油門」的權力留給人。這章用一個機制設計,講透了一條維運哲學。