前面談過的快取(Day 10)、原子操作(Day 11)、正規化資料(Day 6、23),都是在資料量還不算太大的前提下討論的。今天要把視角拉遠:如果代購 App 真的成功,使用者和訂單量成長了 100 倍,這些既有的設計該怎麼跟著演進。
垂直擴展(Scale Up):把單一伺服器的規格加大——更多 CPU、更多記憶體。做法簡單,但終究會碰到硬體規格的物理上限,而且單一伺服器掛掉時,整個系統就會停擺。
水平擴展(Scale Out):增加更多台伺服器一起分攤負載,而不是依賴一台更強的機器。理論上沒有硬性的規模上限,單一機器故障時,其他機器仍能繼續運作,但需要處理「資料該怎麼分散在多台機器上」這個新問題。
索引(Index):資料表還小的時候,就算沒有索引,全表掃描也感覺不出差異;但資料量一大,沒有索引的查詢就會呈現 Day 23 談過的線性成長,變得越來越慢。
複製(Replication):把資料庫整份複製到多台伺服器,讓讀取的請求可以分散到不同的複本上,提升讀取吞吐量與系統可用性,但無法解決單一資料表本身過大、或寫入量過高的問題。
分片(Sharding):把一張巨大的資料表,依照某個規則(例如買家 ID)拆分到多台資料庫伺服器上,每台伺服器只負責其中一部分資料。這是真正解決「資料量本身太大、單台機器裝不下」問題的做法,但也讓查詢邏輯變得更複雜(例如跨分片的查詢)。
資料量一大,Day 10 談過的快取策略變得更重要——把常被查詢、不常變動的資料(例如商品分類)放進 Redis 這類快取層,減少每次都要打資料庫的壓力。而 Day 11 談過的原子操作,在高併發的場景下也往往不夠:當瞬間湧入大量訂單請求時,直接讓每個請求都同步搶著更新同一張庫存資料表,資料庫很容易撐不住,這時候通常會把「扣庫存、建訂單」這類寫入操作,改成丟進一個訊息佇列(Message Queue),由後端依序、非同步地消化,而不是要求資料庫立刻同步處理每一筆請求。
當訂單資料表從幾千筆成長到几百萬筆:原本沒有索引的 buyer_id 查詢,速度會明顯下降,第一步通常是先補上索引;當讀取流量(買家查詢自己的訂單)遠大於寫入流量時,加入 Read Replica 分攤查詢壓力,搭配 Day 10 的快取策略,把熱門商品資訂快取起來;當某次促銷活動湧入大量下單請求時,不再讓每筆請求直接同步寫入資料庫,而是先把訂單請求放進佇列,由背景服務依序處理,搭配 Day 11 的原子操作確保庫存不會被搶超。
規模化不是一次性的架構重建,而是隨著資料量與流量的成長,逐步把「單一伺服器、同步處理」的簡單假設,替換成「多台伺服器分攤、非同步處理」的設計。這些調整大多不是全新的概念,而是把前面幾天談過的索引、快取、原子操作,應用在更大的規模下,並補上分片、佇列這類新的工具來因應資料量帶來的新限制。