
賽姬與愛人丘比特分離後,四處尋找他的下落,最後來到丘比特的母親阿芙蘿黛蒂面前,阿芙蘿黛蒂對這段關係很不滿意,沒有讓兩人見面,反而把小麥、大麥、豆子和各種種子混成一大堆,要賽姬在天黑前全部分好。
她看著眼前這堆東西,整個人都僵住了,這怎麼可能分得完?一隻螞蟻看她可憐,召來一大群同伴,才趕在阿芙蘿黛蒂回來前,把不同的穀物與種子分開。
結果隔天,阿芙蘿黛蒂又要她去取金羊毛,這次可不是把東西分好就能交差,河邊的蘆葦提醒她,那群羊在烈日下十分暴躁,會攻擊靠近的人,得等熱氣退去、羊群安靜下來,再去收集掛在灌木枝條上的羊毛,賽姬照著指引做,才把羊毛帶了回去。
但是阿芙蘿黛蒂依然不滿意,又交給她一只瓶子,要她去險峻的山上取水,穀物分好了,羊毛也帶回來了,下一個任務又是換另一種麻煩,完全沒有要讓她休息的意思,但賽姬又有什麼辦法,都是因為愛啊。
SaaS 是 Software as a Service 的縮寫,簡單來說,就是由服務商負責託管與維運軟體,使用者透過網路就能使用,不需要自己架設整套系統,常見的 SaaS 會讓多個客戶使用同一套服務,資料則由服務商負責儲存與管理,費用通常採月費或年費訂閱。
聽起來很美好,但 SaaS 背後到底藏了多少東西,真的做了才知道。
剛出社會時,第一個碰到的專案是一個正在開發中的 SaaS 系統(那時候其實還不知道 SaaS 是什麼),而我還是新鮮人嘛,覺得自己很勇,讀書的時候學了這麼多東西,唸了這麼多書,一定難不倒我的啦!
帶著這份幹勁,便開始我的工程師之旅,一開始懵懵懂懂,覺得公司都運作這麼久了,所以學長姐怎麼做我就跟著怎麼做,沒什麼毛病,收到每個 task 我都能夠完成,甚至還有餘力去幫助成員,這樣的模式持續了一年之後,似乎已經變成職場的形狀了,每天想的也都是工作的東西,下班甚至也會自主加班,因為工作都能如期完成,我便沾沾自喜,開發完成,專案也順利上線了。
而一切的開始是在正式上線之後,一開始我們的客戶數量不多,所以基本上沒有多少的客服,隨著後來客戶越來越多,一切都不對勁了,每天都要上火線滅火,修修補補到後面便漸漸收斂了不少,到了這個時候覺得系統似乎進入穩定階段了。
那時候新系統上的使用者都是全新的客戶,有一次開會,我們討論客戶數量似乎沒有穩定上升的趨勢,於是老闆開始開始講他下一階段要做的事情:
「該開始把舊客戶找回來了!」
??!? 蛤
當時震驚了一下,想說:
「這不是全新的系統嗎?哪來的舊客戶?」
因為我自從開發開始,就是個聽話又單純的 RD,接收到 Task 就照做,邏輯功能都對了,驗收也過了,對我來說完全沒有問題,從來沒有想過開發系統的目的,直到這天我才大致知道。
這是新的專案,所以我完全不知道原來老闆曾經做過這領域幾十年,會想要把舊客戶找回來是因為那些客戶的歷史悠久,對產業有一定的影響力,說不定可以透過他們來幫我們打開市場,畢竟老闆也跟他們關係不錯。
頓時真的有一種驚奇又覺得好酷的心情,但接著往下了解就覺得其實一點都不酷了:
「不過舊客戶以前用的都是單機版,所以要把他們的資料轉上來。」
頓時我的心情一點都美麗不起來...因為我從來沒看過單機版長怎樣啊!Table 結構是我自己開的,所以我知道資料結構一定不會一樣啊,但有什麼辦法!還是得要做,就邊走邊看吧..先跟客戶約時間拿 DB。
SaaS 地獄也是從這裡開始的
單機版的 DB 裡,查詢計算、寫入時的連動,很多都藏在 View 跟 Trigger 裡,View 裡面又 JOIN 了另一張 View,光是要搞清楚某個欄位的值是原本存進去的、查詢時算出來的,還是被 Trigger 寫回去的,就得一路往下翻好幾層,翻到後來都不記得從哪裡來的。
一開始轉檔的時候,我們的想法是從舊系統查出來,寫進新系統就好了,結果翻車了好幾次,把舊 View 查出來的值直接寫進新系統,然後我們的新系統寫得也不是很好,查詢時又會經過自己的 View,哪個是原始值、哪個已經算過了,這個時候就不有趣了...更別說有些欄位命名看起來一樣,但代表的完全不是同一個意思,在這個階段,我們不只要轉檔,還得同步修改新版程式,每天都跟地獄一樣 TT
再來是沒有人讀得懂的值,舊系統有很多欄位,裡面存的是數字或某種我們叫不出名字的 code,看起來像是列舉,但沒有對應文件,程式碼也只有一堆 if (status == 3) 這種東西,完全不知道 3 是什麼意思、什麼時候會是 3、還有幾種類似 40_14_22_20_... 之類的 Chain。

圖片擷取自網路
遇到這種欄位就只能問,就這樣反覆來回,我們後來把這個過程叫做「考古」,考古是真的很累,但又沒辦法不做,因為只要有一個欄位的語意弄錯,轉進來的資料就會壞掉,而且不一定馬上發現,很多問題甚至都是靠客戶在測試階段發現後,我們才回頭追溯。
再來就是我們想也沒想過的,版本。
我們以為單機版就是「一個」單機版,後來才知道在以前不同時期賣給不同客戶的,schema 跟程式碼根本不一樣,有的客戶版本比較舊,某些欄位還不存在,有的客戶版本更新一點,多了幾張表,對我們來說每次都是從頭來過。
當然還有很多問題,我們到最後都有相對應的配套措施,也慢慢的學習到比較正規的做法了,在這個過程團隊踩過坑的地方真的太多了,實在無法靠一篇文章全部講完,但不管遇到什麼樣的麻煩,其實最後我們總結出一個結論:
一直問就對了!
在問的過程不免會跟客戶接洽,老闆也鼓勵我們直接跟客戶聯繫,於是從原本只會接 Task 的小 RD,慢慢補齊六邊形的某一個角落了。
而最後幾個大客戶慢慢地轉上來,我們也越來越熟練了。
後來我們意識到其實很多的客戶覺得根本沒有必要轉到新系統:
「我原本的系統用得好好的,速度快,不用連網,資料就在我自己的電腦裡,為什麼我要把東西搬到你的雲端?萬一哪天你公司倒了怎麼辦?」
說實在,這些問題我們沒辦法完全駁回,人家說的也不是沒道理,舊系統是買斷的,一次買下來就是自己的,沒有月費,如果不是老闆主動找上門,他們根本不會有想轉的念頭,對我們來說那是一個 Legacy System,但對客戶來說,那是一套用了十幾年、每天都能正常工作的系統。
所以有一部分時間,其實是在做說服的工作,跟他們聊新系統能做到哪些舊系統做不到的事情,優勢是什麼,還有萬一硬體壞了資料會不會就這樣消失這件事,有些人聽完會點頭,有些人就是不為所動,就算功能都測過了,還是會覺得再想想。
後來慢慢摸索出幾個有用的方式,以前我們會問:
「你要不要轉?」
現在我們會先問:
「你現在用起來有哪些地方不順」
這樣反而更有效,把他們的痛點找出來,再帶著他們看功能在新系統怎麼處理,實際 Demo 給他們看,這比任何簡報都管用。
再來就是因為每個租戶在意的習慣、用的版本,跟現在都完全不同,租戶之間的意見會形成衝突,這樣的問題比單純是全新的客戶還要來得嚴重,這也是 SaaS 麻煩的地方,租戶用的是同一套服務,某個租戶想改掉的操作,另一個可能每天都在用,總不能誰最後打電話來,就照誰的意思改吧 XD,所以初期我們為了能夠留住客戶,也曾經做過用設定檔去做客製化的方式過。
這個階段讓我第一次真正理解到,系統遷移這件事,技術只是其中一部分,更重要的是能不能讓對方相信換過來以後工作真的會比較順,人情味跟情緒價值當然很重要。
隨著眾多單機版用戶轉上新系統後,累積了快 20 年的資料量可不是開玩笑的,緊接而來的是效能問題,在開發的時候,為了求快,所以資料庫結構跟查詢語法會對效能帶來什麼影響,對沒有 SaaS 經驗的我們來說根本沒有概念,品質什麼的也是,直到維護階段我們才知道有多災難。
記得有一次要處理的是一支 Query API,request 裡光是輸入欄位就接近 200 個,輸出也有幾百個欄位,主程式收到 request 後,會逐個判斷、組合出查詢語句,偏偏不同輸入的組合,又會影響其他支線怎麼走,到這裡雖然麻煩,至少還能針對組 SQL 的邏輯補單元測試,不過可怕的是最後組好的語句會去查一張超大的 View,而這張 View 裡面又 JOIN 了好幾張 View,還有很多呼叫端直接使用它...

圖片擷取自網路
這只是其中一支 API 喔,簡直是地獄啊! 光是確認改完有沒有壞,就是一個超大的門檻了,更別說效能優化了。
所以特徵測試很關鍵,我使用 Golden Master 去對這種高複雜度的 Query 做第一階段的保護,但其實撞牆了好多次,改了程式碼,以為輸出應該會變、測試會亮紅燈,結果竟然是綠燈,又有一次改了 View,以為行為應該沒變,測試卻紅了一整片,原本是想靠測試安心一點,結果連測試到底能不能相信,都開始懷疑了 TT
再來慢慢調整測試的寫法與用法,也加入 覆蓋率跟突變測試 做為輔助,之後才越來越順手。
等我們對這次要動的查詢結果有了一定的保護後,才把原本的 schema 複製一份出來,先在副本上做效能優化,結果呢?效能是有變好,但因為 View 實在太多層了,紅燈亮的次數已經數不清了,這時候才真的能夠體會到測試的好,如果沒有這些回饋,哪些地方被改掉了,我們真的到被客戶轟炸死都不會知道....光是在開始優化之前,就已經花了這麼多力氣了。
而且這張 View 表服務的可不只一個租戶,光是某個租戶的查詢變快,還不能代表其他人都沒事,挑測試案例時,也得把不同客戶會用到的查詢條件也考慮進去。
後續優化的每支 API ,思路都大差不差,而這整個過程,其實真的和 Strangler Fig Pattern 的思路很像。
以前的單機版是一個客戶一個資料庫,而我們一開始開發系統時,除了沒有經驗,成本考量也是一大原因,所以選擇讓所有客戶共用一個資料庫,這件事情並不是什麼錯,也不是 SaaS 一定得這樣做,一個租戶一個 DB 也可以,只是成本、隔離跟維護方式會不一樣。
麻煩的是,共用資料庫以後,大家用的就是同一份資源,一個租戶的查詢如果就把資源吃光,其他人也會跟著慢,我們當時程式也寫得不好,導致跟 DB 的耦合程度極高...
這讓我們對整體架構的改動有限,能做是能做,但成本真的太高了,不可能才剛上線沒多久,馬上就要改版重寫,我們只能在現有的架構內盡量加索引、優化查詢、部分高頻的讀取拉出來做快取、限流...等等,不對整個系統動大手術,頭痛醫頭、腳痛醫腳,在時間壓力跟團隊規模下,這可能已經是最好的決定了。
而到了後來,效能確實有好上不少,但因為從結構上就設計得不好了,所以能改善的幅度仍然有限,這時候我們才開始慢慢拆,先從最頭痛的報表服務下手,因為只要有一個人開始產大量即時報表,所有人都會受影響,那種程度是連 DTU 調到最頂還是被吃滿的程度,也是在處理這些問題的時候,我們才接觸到 CQRS,原來查詢跟寫入不一定得共用同一套模型,再往後也學到了很多這方面的技術,也是在這時候團隊知道了什麼是微服務,遷移策略是什麼,但過程實在太過漫長了。
產品並不是上線就結束了,客戶會一直用,我們也得一直維護,在這個漫長之旅中我們遇到種種的問題,不管是技術還是人,都逼迫我們認真的看待並 調整開發方式跟團隊協作的方式,越來越把需求品質放到更前面,這漸進演化的過程其實就是被折磨出來的。
有些東西,我們通常就是見招拆招,坑踩多了才慢慢知道說:
「哦!原來測試這麼重要喔。」
「哦!原來程式碼這裡應該這樣寫。」
「哦!原來 Table 不是開得出來、資料塞得進去就好了。」
「哦!原來我們在做的事情叫 Strangler Fig Pattern 喔。」
「哦!原來不同客戶一起用同一套服務,資料隔離跟資源分配是可以這樣設計的。」
「哦!原來這種提供軟體服務的方式,叫做 SaaS 啊。」
但最重要的,我們在開發到上線也沒有意識到說:
「哦....原來我們做出來的其實已經是 Legacy System....」
誰知道會隨著業務變化改動這麼多啊!誰知道還有遠古客戶啊!要是早知道就多讀幾本書了!
SaaS 其實就這幾個字,定義不難,但什麼是 SaaS 的麻煩、代價?會隨著系統的型態而不同,不同的租戶共用一套服務,聽起來理所當然,背後卻是資料隔離、效能分配、版本差異、遷移成本,每一個問題都是要等系統跑起來之後,才意識到原來欠缺了哪些。
當然不可能每件事一開始就選對,能做的大概就是用當下知道的東西,盡量別讓下一次修改比這次更痛苦,即使到現在可能還是很多東西都不知道,每天都還是過得太痛苦了,所以書也就越買越多了 XD
賽姬似乎有無盡的關卡,這都是因為他對丘比特的愛,我們也是憑藉著對軟體工程的熱情,才能一關一關硬撐過去,當然,方法有了也努力試過了,還是可能覺得很累,畢竟一直在維護 Legacy System 也會累啊!
所以明天最後一天就把篇幅留給我們自己,聊聊:
真的覺得快撐不下去的時候,可以怎麼辦?