MapleCheng

在浩瀚的網路世界中無限潛水欸少年郎!

0%

別把 runtime state 放進雲端同步資料夾

最近又踩到一個很有既視感的坑:一個平常穩定運作的 AI agent gateway,突然變成「看起來有反應,但就是不回話」。訊息進來了,系統也有做出收到的動作,偏偏後面的回覆流程卡死。這種狀況最煩,因為它不是完全壞掉,而是壞得很曖昧。

最後挖到的根因其實很樸素:我把 live runtime state 放在雲端同步資料夾底下了。對,就是那種「方便備份、每台電腦都看得到」的目錄。聽起來很合理,結果對一個會頻繁寫入 SQLite、session、WAL、queue state 的服務來說,這根本是在請它自殺。

不是沒收到,是卡在回覆前

一開始看到的症狀很容易誤判。

聊天平台那邊有收到事件,bot 也有做出「我看到了」的反應,所以直覺會往幾個方向查:是不是權限壞了?是不是 token 過期?是不是 intent 沒開?是不是 webhook 或 gateway 掉線?

但這些都不是。真正的線索是:事件已經進到服務裡,後面卻停在一個很無聊的位置——寫狀態。

更精準一點說,是某段 response loop 要把 session state commit 到本機資料庫時卡住。這種卡住不一定會立刻丟 exception,它可能就是靜靜地等,等到 heartbeat 超時、connection 卡死、整個 container 看起來像還活著但其實已經半身不遂。

這也是我這次最大的提醒:debug agent 系統時,不要只看「入口有沒有收到」,也要看「狀態有沒有成功落盤」。

雲端同步不是檔案系統的透明替代品

技術人的通病是很愛把東西收斂到一個「有備份」的位置。設定檔、資料、記憶、session,全丟進同步資料夾,看起來就很安心。機器壞了還能救,換電腦也不用重配,理論上很漂亮。

問題是,雲端同步資料夾不是一般 local filesystem 的透明替代品。尤其在 macOS 上,很多同步服務背後其實是 File Provider 之類的機制。它要處理 placeholder、下載、上傳、衝突、metadata、檔案協調。一般文件、圖片、筆記放在那裡通常沒事;但高頻率、小檔案、鎖定敏感、需要強一致性的 runtime database,就完全是另一回事。

SQLite 特別容易中槍。SQLite 很可靠,但它的可靠建立在「底層檔案系統行為正常且可預期」這件事上。WAL、lock、fsync、rename,這些操作如果被雲端同步層插手,問題就會變得很玄。它不一定每次都壞,而是偶爾在最不想要的時候卡住。

這種偶發性最討厭,因為你很難用一個簡單測試重現。平常都好好的,只有在某次剛好同步、剛好 commit、剛好 container I/O 壓力比較大時,整個服務就開始裝死。

我後來怎麼切

最後解法沒有什麼魔法:把 live runtime data 從雲端同步資料夾搬回真正的本機磁碟。

概念上我現在會把資料分成三類:

  1. 原始設定:可以進 Git 或同步資料夾,例如 template、非敏感 config、部署說明。
  2. 可重建資料:可以定期匯出或備份,例如報表、快照、非即時索引。
  3. live runtime state:不要放雲端同步資料夾,例如 SQLite database、WAL、session store、queue、lock file、container volume。

第三類就是這次的重點。它可以備份,但不要「即時同步」。比較好的做法是放在 local volume、Docker volume,或一個明確不被雲端工具接管的本機目錄。需要備份時,用排程做 snapshot、dump、rsync 到備份位置,而不是讓同步工具直接盯著正在被服務寫入的檔案。

大概長這樣:

            
            flowchart LR
    A[Agent Service] --> B[Local Runtime Volume]
    B --> C[SQLite / WAL / Session / Queue]
    B --> D[Scheduled Snapshot]
    D --> E[Backup / Cloud Sync]

    A -. 不要直接寫 .-> E
          

這張圖的重點只有一句:服務寫 local,備份再同步。不要讓服務直接寫 cloud sync。

Container 也救不了爛 mount

另一個容易誤會的地方是:服務跑在 container 裡,好像就跟 host filesystem 隔離了。

沒有。只要你把 host 的雲端同步目錄 mount 進 container,container 裡面的程式看到的還是那個有問題的底層行為。Docker 只是包裝執行環境,不會把不穩定的 filesystem 變穩定。

這也是我這次覺得有點蠢的地方。明明平常在部署 production service 時,我不會把 database volume 掛到奇怪的位置;但在自己的工具鏈上,因為想省事、想備份方便,就不自覺把原則放掉。結果 production 經驗沒救到我,反而是自己的懶惰埋了一顆雷。

如果你有跑任何長駐型 agent、bot、crawler、scheduler,我會建議直接檢查:

  • runtime database 是不是在雲端同步資料夾裡?
  • Docker bind mount 的 host path 是不是被同步工具管理?
  • session / queue / cache 是否跟文件備份混在一起?
  • 有沒有把「備份」跟「即時工作目錄」當成同一件事?

如果答案是有,先搬。不要等它壞。

Debug 這類問題的幾個訊號

這次也讓我整理出幾個判斷方向。

第一,如果 bot 或服務「有收到事件但沒有產出結果」,不要只查入口。要看 response loop 卡在哪裡,特別是 database commit、state save、log write 這些看似無害的地方。

第二,如果 container restart、kill 都變得很怪,或 runtime process 卡到連正常關閉都不乾脆,就要懷疑底層 I/O。很多時候不是 application logic 死掉,而是某個檔案操作堵住。

第三,如果問題發生在 macOS、桌面機、開發機、自架工具,而且路徑裡有雲端同步痕跡,那真的不要鐵齒。雲端同步很適合保存成果,不適合承擔資料庫的熱寫入。

備份是好事,但同步不是萬靈丹

我現在對這件事的結論很簡單:備份策略要分層,不要把所有資料都丟進同一個同步資料夾假裝安全。

真正重要的資料不只是「要不要備份」,還要問:它在運作中需要什麼一致性?它會不會高頻寫入?它壞掉時能不能重建?它適不適合被雲端工具即時監控?

對 CTO 或技術主管來說,這不是一個小工具的踩坑而已,而是很典型的架構取捨:便利性、安全感、可恢復性、穩定性,常常不是同一個方向。雲端同步給的是便利跟某種心理安全感,但 runtime 系統要的是可預期的 I/O。

下次如果我又想把某個服務的 data directory 丟進同步資料夾,我希望自己先停三秒,問一句:這裡面有沒有 database、lock、queue、WAL?

有的話,拜託,放過它,也放過未來半夜 debug 的自己。