← 全部概念地圖
標準

概念地圖・CQRS 讀寫分離

分類:系統

這一頁不只是把一個概念解釋一遍——一張自己畫的結構圖,加上拿台灣情境從頭走一次的實例演練、這個框架什麼時候會騙你,以及它接回 P.A.I.N. 之後長什麼樣。一頁必須交付的三層 →

CQRS 讀寫分離:把改狀態與看狀態拆成兩條路

一塊用了七年的產線控制盤,按鈕是一顆一顆加上去的,最後一排是給廠長看產量用的。有一次操作員要停機,手伸過去,按到隔壁那顆切換報表區間的鍵。CQRS 這四個字母攤開是「命令與查詢職責分離」:會改變狀態的動作走一條路,只是把狀態看一眼的動作走另一條路,中間隔一條事件流。它長在軟體架構裡,問的卻是一件更早的事——你把「按下去會發生事」跟「按下去只是換一頁」擺在同一塊板子上,擺了多久。

命令與查詢的雙棧,中間隔一條由上往下的事件流 左右兩欄各走一條路。左欄是命令:操作員按下停機鍵,命令模型先驗規則,機台狀態真的改變,再往右送出一筆事件。中間的金色直條是事件流,由上往下走。事件流往右指向右欄的讀模型,讀模型把版面預先排好,觸控面板上的數字才顯示出來。右欄下方標著「這裡的數字會晚 2 秒」。全圖箭頭單向,沒有一條線往回走。 命令與查詢的雙棧,中間隔一條事件流 命令(會改變狀態) 查詢(只顯示資訊) 控制盤上 11 顆 控制盤上 21 顆 操作員按下停機鍵 命令模型先驗規則 機台狀態真的改變 11 顆留在實體面板 每顆加一個防護蓋 事件流 依序往下推 廠長要看產量 查詢只讀取 讀模型:預先排好 觸控面板上的數字 這裡的數字會晚 2 秒
一條路動了機台,另一條路把結果排好給人看。

兩條棧與一條事件流,各走什麼路

把盤面上的三段路攤平,各自在做什麼、壞掉的時候長什麼樣子,一眼就分得出來。

這一段它在做的事壞掉時的樣子
命令那一路操作員按下去,模型先擋掉不合規則的,機台狀態真的改變誤按一次,產線就停
事件流狀態每改一次就留下一筆,順著往下游推漏推一筆,右邊永遠停在舊數字
查詢那一路讀模型照畫面的需要預先排好版,面板直接取用排法一改,六個分頁要跟著重排
中間那道縫兩邊各自改版、各自上線數字對不起來時,得沿事件流回頭查

表的最後一列才是這張圖真正的價錢。左邊那一路只要驗一件事:這個動作合不合規則。右邊那一路只要煩一件事:這個數字要排成什麼樣子給人看。代價是中間多了一道縫,而縫裡的行話叫最終一致性——查詢那一側看到的數字,會比實際狀態晚一點點。晚多久,得自己量。

值不值得把讀跟寫拆開,看四個訊號

一、盤面上,按下去會動起來的有幾顆。先數,不要先設計。一塊 32 顆按鈕的控制盤,數完可能只有 11 顆真的會改變機台狀態,其餘 21 顆按下去只是換一頁看。這個比例決定了拆開之後,你要保護的其實只有一小撮——防護蓋不必買 32 個。自問:這塊盤上,按下去會讓機器真的動的有幾顆?

二、誤按的代價,落在誰身上。翻半年的紀錄:誤按 7 次,其中 3 次讓產線停下來,每次平均 12 分鐘。停機的那 36 分鐘落在現場,不落在畫誰的畫面的人身上。代價落點越遠,越沒有人會主動把兩條路分開。自問:最近半年誤按幾次,其中幾次真的停了機?

三、看的人願不願意接受慢一點。拆開之後,觸控面板上的數字是事件流推過去的,會比實際狀態晚 2 秒。對盯產量的廠長,2 秒沒有任何差別;對正要判斷機台有沒有停住的操作員,2 秒足以讓他多按一次。同一個延遲,兩種人的答案不一樣。自問:晚 2 秒的數字,會讓現場做出不同的動作嗎?

四、查看那一側的花樣有多少。21 顆查詢類按鈕要收進一塊觸控面板,最後分成 6 個分頁——那 6 個分頁就是 6 種看法。看法越多,讀模型越值得獨立長;看法只有一種,拆開只是多養一套東西。自問:同一份數字,現場有幾種不同的看法要排?

一塊產線控制盤,重新分區一次

底下這塊台灣中部工廠的產線控制盤,是為了把這個結構講清楚而編出來的示意情境,沒有指涉任何真實公司。

實例演練
  • 先數一遍:盤面 32 顆按鈕,會改變狀態的命令類 11 顆,只顯示資訊的查詢類 21 顆。數完才知道要保護的只有三分之一
  • 翻舊帳:改版前半年誤按 7 次,其中 3 次造成停機,每次平均 12 分鐘,合計 36 分鐘。36 分鐘全記在現場頭上
  • 命令那一側:11 顆留在實體面板,每顆加一個掀蓋,手要先掀才按得到。動作變慢一拍,是刻意的
  • 查詢那一側:21 顆收進一塊觸控面板,分成 6 個分頁,班別統計跟良率趨勢各自一頁。鐵板上空出一大片
  • 交出去的代價:觸控面板的數字由事件流推過去,比實際狀態晚 2 秒。停機那一刻,面板還亮著上一個讀數
  • 半年後:誤按 1 次,沒有造成停機。7 次變 1 次

這個場景值得記住的接縫,是盤面上空出來的那一大片鐵板。掀蓋只是把誤按的手擋了一下;真正把 7 次壓成 1 次的,是那 21 顆離開了操作員伸手就到的範圍——手滑的機率跟手能碰到的東西成正比。而那 2 秒的延遲,是這塊板子付出去的房租:現場多了一個要記住的規則——面板上的數字,是剛才那一秒的讀數。這句話寫在班前會的白板上,比寫在使用手冊裡有用。新人第一天上工要學的東西也換了形狀:以前是背 32 顆按鈕的位置,現在是先分清楚手邊這一顆屬於哪一路。以上為說明結構用的示意情境,僅供理解框架之用。

這套拆法什麼時候會騙你

  1. 它會讓你以為拆完就結束了。拆開的那一刻,你就多養了一套同步機制:命令那側加一個欄位,讀模型要跟著改一次版面,兩邊各有自己的上線節奏。以前是一塊鐵板加一顆按鈕,現在是兩份東西要對齊。這筆維護費是每個月都在付的固定支出——它不會出現在改版報告裡,只會出現在下一次「怎麼又要多改一邊」的抱怨裡。
  2. 它讓對帳從一句話變成一段路。兩邊數字對不起來的時候,以前是打開同一張表看一眼;現在要沿著事件流回頭查:哪一筆沒推過去、哪一筆推了兩次、哪一筆順序顛倒。查得動的人,通常就是當初拆的那個人——這是拆開之後最貴、也最容易被漏算的一項人力。
  3. 那 2 秒是好天氣的數字。量出來的 2 秒,是產線正常跑的時候量的。真正會出事的時刻,正好是事件最密的時刻:連續跳停、批次結批、換模同時發生,那條事件流會塞車,2 秒可能變成 20 秒。把延遲寫進圖裡是對的,但要寫成一個範圍,順便標清楚你是在什麼負載下量的。

讀跟寫拆開,誰的等待變短了

痛點視角

會改變狀態的動作跟只是查看的動作,被擺在同一塊板子上很久了。

久到沒有人記得它們本來可以分開。這張圖畫得出哪些動作會改變狀態,畫不出誰在等那個數字——而《痛點》的 P.A.I.N. 第一格問的正好是後面那件事:誰在痛。拆讀寫是一個結構動作,結構動作算得出「按下去會不會改變什麼」,算不出「改變之後,有沒有人在等結果」。所以同一套拆法,在有人盯著數字做決定的現場是救命的,在數字只是每月報表的地方就只是多一層工。判斷的方法很土:沿著那條事件流往下游走,走到底看看盡頭站著誰。盡頭沒有人,你拆的就只是一張圖的形狀。今晚拿現場那張盤面照片出來,在每一顆按鈕旁邊寫一個字——改,或看。寫到你開始猶豫的那一顆就停下來,那一顆正站在這條縫上。

讀寫分離,常見問題

CQRS 一定要搭配 Event Sourcing 嗎?

不必。CQRS 只要求命令與查詢走各自的模型,中間怎麼傳是另一個選擇題:可以推事件,也可以在寫入的同一個交易裡順手更新讀模型。Event Sourcing 是把「每一次狀態改變」當成唯一真相存起來,那是更重的一套承諾。兩件事常一起出現,是因為事件流剛好是最順手的傳送方式。控制盤那個例子只用到前半段:狀態一改就送一筆出去,讀模型收到就重排面板。

這跟資料庫的讀寫分離(主從複本)是同一件事嗎?

主從複本複製的是同一份資料表,讀那側看到的結構跟寫那側一模一樣,中間差的只有複製延遲。CQRS 換掉的是模型本身——讀那側可以長成完全不同的形狀:預先算好的班別統計、攤平的良率趨勢,寫那側根本沒有這些表。所以主從複本是省流量的做法,CQRS 是換一套詞彙的做法。很多團隊從前者開始,等到讀那側的查詢開始長出五六種樣子,才需要往後者走。

系統不大,值得拆嗎?

拿判準段那四個訊號量一次比較準。若查看的花樣只有一種、誤按也不會造成任何實際損失,拆開帶來的兩份維護成本會大於它省下的。真正划算的訊號通常是這種組合:改狀態的動作很少但很貴,查看的花樣很多但都不急。控制盤剛好落在這個組合裡——11 顆很貴、21 顆很雜。反過來,一個只有增刪查改的內部小工具,拆完你會發現兩邊每次都要一起改。

最終一致性會讓使用者看到錯的數字嗎?

看到的是舊數字,不會憑空生出沒發生過的數字。差別在於使用者知不知道。最傷的情況是他按了一個會改變狀態的動作,回頭看畫面卻沒變,於是再按一次——這在控制盤上就是誤按的另一種來源。處理方式有兩層:一層是把延遲寫在畫面上,例如標出「資料時間」;另一層是讓命令那一側自己回話,按下去立刻在原地給一個確認,不要讓使用者靠查詢那一側去確認自己剛才按過。

那塊板子重排一次之後,新人要記的東西會少很多。想再往前一步,可以看看客戶訂單分離點怎麼在流程上標出訂單從哪一點開始客製,那是另一種「在哪裡切一刀」的判斷;如果你正要重排那塊面板上的六個分頁,儀表板資訊層次談的就是那些數字該用什麼順序擺出來。

發現引用或事實有誤?請寫信回報,我會依更正政策修正。

喜歡這篇文章嗎?

每週幫你拆二本好書,把判斷力、好書金句、好問題,直送你的信箱。

不適合你可隨時退訂 · 每週兩封,陪你把書讀進生活。

送出即表示同意我們依隱私權政策使用你的 email。