一台資料庫,真的可以一直加大嗎?

剛開始做網站時,資料庫通常只有一台。使用者、文章、訂單、留言,全都存在同一個 PostgreSQL、MySQL 或其他資料庫裡。資料量不大時,這是最簡單也通常最正確的設計。

流量增加後,我們可以先升級 CPU、RAM、SSD,或改善 SQL、加入 Index、Cache 和 Read Replica。這些方法都能延長單一資料庫的壽命。

但硬體不可能無限升級。

假設一個服務累積了數十億筆資料,而且每秒都有大量寫入。即使讀取可以交給 Replica,所有主要寫入仍可能集中在一個 Primary。這時問題不再只是「查詢夠不夠快」,而是單一資料庫本身已經成為容量與吞吐量的邊界。

其中一種解法,就是 Sharding。

Sharding 是什麼?

Sharding 可以翻成「分片」。核心概念很直接:

不要把所有資料放在同一個資料庫,而是依照某個規則,把不同資料分配到不同資料庫。

例如一個服務有 3 億名使用者,可以粗略想成:

Shard A:使用者 1~1 億

Shard B:使用者 1 億~2 億

Shard C:使用者 2 億~3 億

每個 Shard 都保存整體資料的一部分,而且通常可以獨立處理查詢與寫入。

這與 Replication 有一個重要差異。

Replication 是「同一份資料做多個副本」;Sharding 則是「不同資料拆到不同節點」。

如果原本有 900 GB 資料,做三份 Replica 並不會讓每個節點只存 300 GB;每個 Replica 原則上仍要保存那 900 GB。若使用 Sharding,則可能讓三個 Shard 各自負責約 300 GB。

所以兩者解決的問題不同,也經常一起使用:每個 Shard 本身還可以再配置 Replica,提高可用性與讀取能力。

真正困難的問題:資料要怎麼分?

建立三台資料庫並不難。困難的是決定「某一筆資料應該去哪一台」。

這個決定通常依靠 Shard Key。

例如以 user_id 作為 Shard Key:

user_id = 1203 → Shard A
user_id = 8459012 → Shard B
user_id = 20133001 → Shard C

應用程式或中介層收到請求後,先根據 Shard Key 判斷資料在哪裡,再把 SQL 送到正確的 Shard。

因此 Shard Key 的選擇非常重要。選錯之後,重新分配數十億筆資料可能是一場昂貴而高風險的 Migration。

Range-based Sharding

最容易理解的方法,是按照範圍切分。

例如:

user_id 1~1,000,000 → Shard A
user_id 1,000,001~2,000,000 → Shard B
user_id 2,000,001~3,000,000 → Shard C

優點是規則直觀,而且查詢某段連續範圍時很好判斷資料位置。

但它有一個明顯風險:Hotspot。

假設 user_id 持續遞增,新註冊使用者永遠落在最新的範圍,那麼大量新寫入可能全部集中到最後一個 Shard。雖然你有很多台資料庫,真正忙碌的卻只有其中一台。

Hash-based Sharding

另一種常見方式,是先對 Shard Key 做 Hash,再決定資料位置。

簡化來看,可以想成:

shard = hash(user_id) % 4

結果為 0 的資料進 Shard 0,結果為 1 的進 Shard 1,以此類推。

這通常能讓資料分布得比較平均,降低某個連續 ID 區間形成 Hotspot 的機率。

但新的問題也出現了。

如果原本只有 4 個 Shard,後來增加到 5 個,取模結果會改變,大量資料的位置可能因此需要重新計算。這會造成龐大的資料搬移成本。

因此實際的大型系統常會使用更進階的映射方式,例如 Consistent Hashing、Virtual Shard 或獨立的 Shard Map,避免每次擴容都重搬幾乎全部資料。

什麼是 Hot Shard?

即使資料筆數平均,負載也不一定平均。

假設社群平台依 user_id 分片,而某個 Shard 剛好放了幾個超大型帳號。它們每秒產生的讀寫量遠高於普通使用者,那個 Shard 就可能比其他節點忙很多。

這叫做 Hot Shard。

所以好的 Sharding 不是只看「每台有幾筆資料」,還要考慮資料的存取模式。

100 GB 很少被讀取的歷史資料,可能比 5 GB 每秒被大量更新的熱門資料更輕鬆。

Cross-shard Query 為什麼麻煩?

單一資料庫的方便之處,是資料都在同一個地方。

例如:

SELECT * FROM users ORDER BY created_at DESC LIMIT 100;

如果所有 users 都在同一個資料庫,資料庫可以直接執行這個查詢。

但如果 users 被拆到 20 個 Shard 呢?

系統可能必須:

向 20 個 Shard 分別查詢;

取得每個 Shard 的結果;

在應用層或中介層合併;

重新排序;

最後才取出真正的前 100 筆。

這就是 Cross-shard Query 的成本。

JOIN 也會變得更棘手。如果 users 在 Shard A,而某些相關資料位於 Shard C,原本資料庫內部一次完成的 JOIN,可能變成跨網路的多次查詢。

因此採用 Sharding 後,資料模型往往也要跟著改變。設計者會盡量讓經常一起查詢的資料落在同一個 Shard,減少跨 Shard 操作。

Transaction 也不再那麼簡單

假設銀行帳戶 A 在 Shard 1,帳戶 B 在 Shard 2,要完成一筆轉帳:

Shard 1 扣款成功,但 Shard 2 入帳失敗,怎麼辦?

在單一資料庫中,可以用 Transaction 和 ACID 機制處理。但跨越多個獨立資料庫後,原本的單機 Transaction 不再足夠。

系統可能需要 Distributed Transaction、Two-Phase Commit,或採用 Saga 等設計,把操作拆成多個步驟並準備補償機制。

這正是 Sharding 的核心代價之一:你得到水平擴充能力,但同時進入分散式系統的世界。

Rebalancing:增加 Shard 之後怎麼辦?

假設系統原本有 4 個 Shard,每台都快滿了,因此加入第 5 台。

新 Shard 不會自動讓舊資料平均消失到新節點。系統需要把部分資料從舊 Shard 搬到新 Shard,這個過程稱為 Rebalancing。

而搬資料時,網站通常不能停機。

因此真正的 Rebalancing 可能同時涉及:

複製既有資料;

同步搬移期間產生的新寫入;

切換 Routing;

驗證兩邊資料一致;

最後才移除舊位置的資料。

如果資料量是數 TB,這個過程可能持續很久。Sharding 因此不只是資料庫 schema 的問題,也是營運與基礎設施問題。

Sharding 和 Partitioning 一樣嗎?

兩者概念很接近,都是把資料切成多個部分,但使用語境通常不同。

Database Partitioning 常指同一個資料庫系統內部的資料切分。例如 PostgreSQL 可以把一張巨大資料表依日期切成多個 Partition,但仍由同一套資料庫系統管理。

Sharding 通常強調資料被分散到多個獨立資料庫節點,甚至不同伺服器。

可以把它粗略理解成:

Partitioning:一個資料庫裡分區管理。

Sharding:跨多個資料庫水平分片。

實際產品的術語可能略有不同,因此看文件時仍要確認該系統對 Shard 與 Partition 的定義。

Sharding 不是越早做越好

看到大型服務使用 Sharding,很容易產生一個錯覺:既然未來可能變大,那一開始就分片不是更專業嗎?

通常不是。

Sharding 會直接增加:

開發複雜度;

資料遷移難度;

查詢限制;

跨節點 Transaction 成本;

備份與還原複雜度;

監控與故障排除成本。

如果一台合理配置的資料庫就能處理目前負載,先保持單一資料庫通常更容易維護。

在考慮 Sharding 前,往往應先確認:

SQL 是否真的已經優化?

索引是否合理?

是否能把大量讀取交給 Cache?

是否能使用 Read Replica?

是否能先做 Partitioning?

是否只是某幾個 Query 特別慢?

硬體是否真的已接近合理上限?

只有當瓶頸確實需要水平拆分時,Sharding 才值得承擔它帶來的複雜度。

一個完整的請求會怎麼走?

假設一個平台以 user_id 作為 Shard Key。

使用者 81273 開啟自己的個人頁面時,流程可能是:

瀏覽器送出 request;

API Server 驗證身分;

Routing Layer 取得 user_id = 81273;

Shard Map 判斷它屬於 Shard 3;

Query 被送到 Shard 3;

Shard 3 回傳資料;

API Server 組合 response;

瀏覽器顯示頁面。

如果這筆請求還需要另一個 Shard 的資料,流程就會多出額外的網路 round trip、錯誤處理與結果合併。

所以 Sharding 的真正重點不是「多開幾台資料庫」。

它是在回答一個更難的問題:當資料已經不能由單一節點舒服地承擔時,我們要如何決定每一筆資料住在哪裡,並讓整個系統仍然像一個完整產品運作?

最後整理

Sharding 是把不同資料水平拆到多個資料庫節點的架構,用來突破單一資料庫的容量或吞吐量限制。

Shard Key 決定資料位置;Range-based 與 Hash-based 是常見分片方式;Hot Shard、Cross-shard Query、Distributed Transaction 和 Rebalancing 則是採用 Sharding 後必須面對的主要問題。

Replication 是複製相同資料,Sharding 是拆分不同資料;Partitioning 通常是在單一資料庫系統內切分資料。三者可以一起存在,但目的不同。

最重要的是:Sharding 並不是讓資料庫「自動變快」的按鈕。它是用更高的系統複雜度,換取水平擴充能力。

當單一資料庫還能可靠地解決問題時,保持簡單通常更好;真正撞到單機邊界時,再把資料拆開,才是 Sharding 應該出現的時機。

把概念放回真正的系統流程裡看,通常比只背名詞更容易理解它。