[TOOLS] 14 分鐘閱讀OraCore 編輯部

Dometrain 把系統設計變成營運模板

我把 Dometrain 的進階系統設計課拆成一份可抄模板,重點放在分散式狀態、發布安全、多租戶與營運操作。

分享 LinkedIn
Dometrain 把系統設計變成營運模板

我把 Dometrain 的進階系統設計課拆成一份可直接抄的營運模板,重點是分散式狀態、發布安全、多租戶和故障處理。

我看過太多系統設計內容,前半段都很像樣:畫框、講 quorum、談水平擴充,聽起來很順。可是我一用到真實系統,就開始不對勁。因為真正麻煩的不是圖畫不出來,而是上線後誰擁有狀態、重試會不會重複扣款、某個 tenant 爆量時誰先死、某個 region 掛掉時到底要不要硬切。這些才是每天會咬人的地方。

我最近看的是 Dometrain 的 Hands-On: Advanced System Design,作者是 Nick Chapsas。它最對我胃口的地方,是它不把系統設計當作文科題目,而是直接往 ops 走:怎麼協調、怎麼補償、怎麼避免 poison message、怎麼做 canary、怎麼管多租戶。這才像真的在做服務,不是在白板上表演。

我這篇不是在幫它吹捧。我是把它拆成我自己會拿去用的 playbook。你可以把它當成一份 architecture review 的檢查表,少掉很多「看起來可以」的幻覺。

先講清楚:我引用的是課程頁面本身,沒有看到它提供觀看數、星數或 bookmark 數,所以我不會亂編。這篇的重點是把課綱翻成能落地的做法,而不是幫任何人做流量公關。

不要把分散式狀態當成一台資料庫

訂閱 AI 趨勢週報

每週精選模型發布、工具應用與深度分析,直送信箱。不定期,不騷擾。

不會寄垃圾信,隨時可取消。

“You will start by tackling distributed state, exploring leader election among replicas, quorum reads and writes, and distributed locks.”

翻譯一下就是:你一旦有多個節點,狀態就不再是單機那種「誰先寫進去就算數」的簡單世界。你得先回答誰說了算、誰要等、誰可以重試、誰要退讓。沒有這些規則,系統只是在很多台機器上同時製造衝突。

Dometrain 把系統設計變成營運模板

我很討厭那種把「我們有資料庫」當成「我們有協調能力」的說法。沒有。資料庫可以幫你,但它不會自動替你的應用決定誰是 leader、誰能拿鎖、誰要做 quorum。這些事情你不先定義,後面就會變成事故時才開始補作文。

這一段我最在意的是 leader election、quorum reads/writes、distributed locks 這三件事被放在一起。這代表課程不是只教你名詞,而是逼你面對「一致性到底怎麼被維持」。我之前在一個排程系統上踩過雷,大家都以為某個 job 只有一個 worker 會碰,結果 failover 後兩邊都覺得自己是 owner。最後不是 bug,而是設計沒寫清楚。

實操寫法我會這樣做:

  • 先列出哪些工作必須單一擁有者,像排程、補償、清理、對帳。
  • 對每個工作定義 leader 選舉或 lease 機制,不要只寫「用 lock」。
  • 把 quorum 規則寫成 failure matrix,說清楚半數掛掉時會怎樣。
  • 把鎖的 expiry、renewal、失敗回收寫進 runbook,不然 stale lock 早晚咬你。

我現在看任何架構圖,都會先問一個很土但很有效的問題:如果兩個 node 都覺得自己有權做同一件事,會發生什麼?答不出來,這個設計就還沒長大。

Saga 不是高級名詞,是補洞流程

“You will learn how to implement Sagas using both orchestration and choreography, build Event Sourcing systems with snapshots, and construct Change Data Capture pipelines.”

也就是說,這章不是在講漂亮架構圖,而是在講交易邊界外面的爛事。真實業務流程常常不會整齊收尾:付款成功、庫存失敗;通知寄了兩次;使用者看到半套結果。這時候你不能再假裝一個 ACID transaction 能包住全世界,Saga 才是你面對半途失敗的方式。

我喜歡這門課把 orchestration 和 choreography 分開講,因為很多人嘴上會把兩者混成「事件驅動」四個字,然後裝作差不多。根本不一樣。orchestration 有明確控制器,適合你想掌握流程順序和補償步驟;choreography 把責任分散到事件和 handler,彈性高,但也更容易長成事件義大利麵,最後沒人知道流程主控在哪。

Event Sourcing 和 CDC 也放在同一脈絡裡,我覺得很合理。它們都在處理「變更要怎麼被保存、觀察、重放」。snapshot 不是裝飾品,是你真的 replay 不動之後的救命繩;CDC 也不是新潮詞,是你不想重寫舊系統,又得把變更同步出去時的務實解法。

我之前碰過一個案子,產品想要 CRUD,稽核想要可追溯,資料團隊又想要可重放。三邊都沒錯,錯的是大家都想把自己的需求塞進同一種資料模型。最後我們承認有三個真相:寫模型、讀模型、歷史軌跡。承認這件事之後,架構才開始像架構,不再像願望清單。

實操寫法我會這樣落地:

  • 流程長、步驟多、要看得懂進度時,用 orchestration。
  • 每個服務都清楚擁有自己的反應邏輯時,才考慮 choreography。
  • Event Sourcing 的 replay 開始痛了,就加 snapshot,不要硬撐。
  • 舊系統不能大改、又要拿到變更流時,再上 CDC。

這章給我的一句話是:別再把分散式工作流想成原子操作,直接設計 compensation、重放和衍生資料,少做很多白工。

重試安全不是加一個 retry 就結束

“You will also learn how to guarantee reliable processing using idempotency keys, the outbox pattern, deduplication windows, and dead-letter queues for poison messages.”

白話講,這一章在教你一件很煩但很重要的事:重試會害死人,除非系統認得出這次是不是同一筆。沒有 idempotency,任何暫時性失敗都可能變成重複扣款、重複發送、重複建立資料。大家最愛說「反正重試就好」,但重試本身就是風險來源。

Dometrain 把系統設計變成營運模板

我很喜歡課程把 outbox、deduplication window、DLQ 放在一起,因為這三個東西本來就該一起看。outbox 解的是資料庫寫入和訊息發佈不同步的問題;deduplication window 解的是 at-least-once delivery 的重複問題;dead-letter queue 解的是 poison message 卡死主線的問題。這些不是加分題,是基本防線。

我以前看過一個團隊把 retry 拿來當信仰:失敗就重送,重送到成功為止。結果依賴服務一抖,自己的系統就像在打自己。那不是韌性,那是自動化自殘。真正的做法是先讓工作可重複,再讓失敗可隔離,最後才談恢復。

實操寫法我會這樣做:

  • 所有可能被客戶端或 worker 重送的操作,都加 idempotency key。
  • 資料寫入和事件發佈要一致時,用 outbox,不要賭兩邊同時成功。
  • 把 poison message 丟到 DLQ,並寫清楚誰有權重放、何時重放。
  • timeout budget 要從最上層往下算,不要每個 service 各自亂設。

這一章其實很殘酷:可靠性不是一個功能,它是一堆限制條件。你愈早承認,後面愈少爆炸。

多區域不是多開幾台機器,是多一堆政治問題

“We cover global scale by examining active-active multi-region deployments, resolving concurrent cross-region writes, and maintaining data residency.”

這句話我很有感。很多人一講 multi-region,腦中浮現的是「全球都快」。實際上你會先碰到的是延遲、衝突寫入、資料駐留法規,還有產品團隊想要 A 方案的速度、B 方案的安全、C 方案的成本,然後全部都要。

課程把 active-passive 和 active-active 分開講,我覺得很必要。active-passive 比較像保守派,重點是好切換、好恢復;active-active 比較像愛折騰派,重點是區域獨立和在地寫入,但代價是衝突處理更麻煩。這兩種不是同一件事,只是都叫 multi-region。

我以前看過一個跨區部署提案,簡報很漂亮,真正沒講的是:如果兩個 region 同時收到同一筆更新,誰贏?如果法規要求資料只能留在特定地區,路由和複寫怎麼配合?這些問題沒先寫下來,最後就會在 compliance review 被打回來,然後大家假裝很驚訝。

實操寫法我會這樣做:

  • 先選 region-primary 還是 region-symmetric,別一開始就想兩個都要。
  • 把跨區衝突怎麼解決寫清楚,別只寫「最後一致」。
  • 把使用者路由和資料放置分開設計,才看得出 latency 和 residency。
  • 真的要上線前,拿區域級故障做演練,不要只測服務重啟。

這章最實在的地方在於,它提醒我:全球化不是把伺服器撒出去而已,而是把政策、風險和協調成本一起撒出去。

觀測性如果不影響決策,就只是昂貴日記

“You will see how to safely deploy, evolve, and monitor these systems using backward-compatible contract evolution, canary releases, distributed tracing, and SLO-based alerting with error budgets.”

翻成白話就是:你收再多 log、metric、trace,如果它不會影響上線、回滾、告警或 incident decision,那它只是很貴的紀錄檔。很多團隊說自己有 observability,實際上只是有很多圖,沒有判斷。

我喜歡這一章把 tracing、SLO、error budget 放一起,因為這才是運營的正確順序。trace 讓你知道請求走去哪裡;metric 讓你知道系統長期怎麼變;log 讓你知道怪事發生時的細節;SLO 讓你知道使用者到底有沒有被傷到。少一個都可以活,但少了 SLO,大家就會回到「我覺得還好」這種很危險的說法。

error budget 更重要。它把「這次 canary 能不能放」從感覺題變成數學題。你不是問自己有沒有膽量,而是問目前的錯誤率還剩多少空間可以冒險。這對喜歡憑直覺上版的人來說很不友善,但對系統來說很友善。

實操寫法我會這樣做:

  • 每條關鍵請求都帶 trace ID,跨 service 一路傳下去。
  • 只定一兩個真的對使用者有痛感的 SLO,別什麼都想量。
  • 告警要對準 error budget 的消耗,而不是每個小抖動都叫人起床。
  • canary 和自動 rollback 要綁在同一條監控鏈上,別讓部署和觀測各做各的。

我自己的判斷很簡單:如果一套監控不能幫我決定要不要繼續 rollout,那它就還沒真的進到 ops。

多租戶的邊界,其實就是產品邊界

“You will also explore … per-tenant quotas and role-based access control.”

這一段常常被低估。很多人以為多租戶只是資料表多一個 tenant_id,錯得很穩。真正的問題是隔離、公平、授權、成本控制一起來。當第一個大客戶開始吃掉 queue、cache、CPU、support 時,你才會發現你做的不是平台,是共享災難。

課程把 rate limiting、quota、RBAC 拉進來,我覺得很務實。token bucket、bulkhead、per-tenant sharding、policy-based authorization 這些詞聽起來很工程,但本質上都在回答同一件事:誰可以做什麼、能做多少、做到什麼程度就該被擋下來。

我以前看過一個 SaaS 專案,前端覺得自己只是登入,後端覺得自己只是 API,結果某個大客戶把整個共享 queue 壓爆。那次之後我才真的相信:tenant 邊界不是附屬功能,它就是產品邊界。你不在 edge 管好,後面每個 service 都會替你付代價。

實操寫法我會這樣做:

  • tenant 身分盡量在 gateway 就驗掉,不要拖到深層 service。
  • quota 用 blast radius 來設,不要只看行銷方案。
  • 對 noisy neighbor 用 bulkhead,把影響切開。
  • RBAC 和業務規則分離,不然權限一改就要重寫一堆邏輯。

這章我最想抄走的一句話是:平台要是做得好,大家會覺得它很無聊。這其實是最高讚美。

可抄的模板

# 系統設計轉營運模板:我自己會拿去開 review 的版本

## 1) 分散式狀態
- [ ] 哪些工作必須單一擁有者
- [ ] leader election 或 lease 怎麼做
- [ ] quorum read/write 的失敗行為
- [ ] distributed lock 的 expiry / renewal / recovery
- [ ] 兩個 node 同時自認擁有權時怎麼處理

## 2) 工作流與補償
- [ ] 哪些流程要 orchestration
- [ ] 哪些流程才適合 choreography
- [ ] 每一步失敗後的 compensation
- [ ] event sourcing 是否需要 snapshot
- [ ] 需不需要 CDC 來接舊系統

## 3) 重試安全
- [ ] 所有可重送操作都加 idempotency key
- [ ] outbox 是否和寫入同交易邏輯
- [ ] deduplication window 多久
- [ ] poison message 怎麼進 DLQ
- [ ] DLQ 的重放責任人是誰

## 4) 故障隔離
- [ ] timeout budget 是否從入口往下算
- [ ] flaky dependency 是否有 circuit breaker
- [ ] degraded mode 要怎麼降級
- [ ] load shedding 何時啟動
- [ ] fail open / fail closed 的選擇

## 5) 多區域
- [ ] region-primary 還是 region-symmetric
- [ ] active-passive 還是 active-active
- [ ] 跨區寫入衝突怎麼解
- [ ] data residency 規則怎麼落地
- [ ] failover 有沒有真的演練過

## 6) 觀測與發布
- [ ] trace ID 是否跨 service 傳遞
- [ ] SLO 是否對應使用者痛點
- [ ] error budget 是否影響 rollout 決策
- [ ] canary / rollback 是否自動化
- [ ] alert 是症狀還是噪音

## 7) 多租戶
- [ ] tenant identity 是否在 edge 驗證
- [ ] quota 是否按 blast radius 設定
- [ ] noisy neighbor 是否有 bulkhead
- [ ] RBAC 是否獨立於業務邏輯
- [ ] policy 是否可單獨調整

## 8) 發版門檻
如果下面任何一題答不出來,我不會說設計完成:
- 誰擁有這個工作?
- 重試會不會重複副作用?
- 半途失敗怎麼補償?
- 資料怎麼恢復?
- 使用者痛點怎麼被觀測?
- tenant / region 邊界是什麼?
- 回滾時會壞什麼?

這份模板不是為了好看,是為了逼人回答那些最容易被跳過的問題。我會拿它去做 design review,也會拿它去看自己是不是又在畫漂亮圖。

原始來源是 Dometrain 的課程頁 Hands-On: Advanced System Design,作者是 Nick Chapsas。上面這份拆解和模板是我自己的整理,課程主軸是衍生自原始課綱,不是逐字轉錄。