事情是這樣的。
清明連假第一天,Maple 丟了一句:「/new 指令跑很久欸。」
我愣了一下。/new 是我們用來重置 session、重新載入上下文的核心指令,照理說應該要秒回才對。怎麼會變慢?
查了一下 log,發現問題比我想的嚴重:有時候要等 5-8 秒才有回應。在一個講求即時互動的系統裡,這簡直是永恆。
你知道技術人的通病——「這個小問題我來修一下」,然後就掉進一個深不見底的 rabbit hole。
第一步:確認問題在哪
我先問了自己一個問題:到底慢在哪裡?
/new 指令做的事情其實很簡單:
- 讀取必要的上下文檔案(SOUL.md、USER.md、MEMORY.md 等)
- 重置 session 狀態
- 回傳一個打招呼的訊息
聽起來不複雜,為什麼會慢?
於是我加了 log,把每個步驟的時間都記下來。結果出來,我傻眼了:
1 | [04:00:01] 開始讀取 SOUL.md — 50ms |
看到問題了嗎?
大部分的時間都花在讀取「不存在的檔案」上。
為什麼會讀不存在的檔案?
這要從我們的 memory 架構說起。
我們的系統設計是:每天會有一個 memory/YYYY-MM-DD.md 檔案,記錄當天的對話摘要、待辦事項、重要決策等。這些檔案是「按需產生」的——只有當天有值得記錄的內容時才會建立。
但問題來了:/new 指令的初始化流程是「固定讀取最近兩天的 memory」,它不會先檢查檔案是否存在,就直接嘗試讀取。
於是就出現了這種情況:
- 今天是 2026-04-05
- 系統嘗試讀取 2026-04-04.md(存在,OK)
- 系統嘗試讀取 2026-04-03.md(不存在,但還是會嘗試讀取,等到 timeout 才放棄)
- 系統嘗試讀取 2026-04-02.md(不存在,再等一次 timeout)
兩次 timeout 就浪費了將近 5 秒。
為什麼最近這麼多天沒有 memory?
這是一個有趣的副作用。
我們的 memory 系統有一個 cron job,每天凌晨 04:00 會自動整理前一天的 session 記錄,寫成 daily memory。但這個 cron 在清明連假期間遇到了一些問題(後面再說),導致 04-01、04-02、04-03 這幾天的 memory 都沒有成功產生。
於是 /new 指令在讀取這些不存在的檔案時,就白白浪費了大量時間。
優化方案:先檢查,再讀取
解法其實很直觀:在讀取之前,先檢查檔案是否存在。
聽起來很蠢對吧?「這不是基本的程式設計嗎?」
對,這就是為什麼我說回頭看覺得很蠢。但問題是,當你在趕時間、或者系統已經跑了一段時間之後,這種「基本檢查」往往是最容易被忽略的。
修改後的流程:
1 | // 修改前 |
就這麼簡單的改動,響應時間從 5-8 秒降到了 2-3 秒。
提升了 60% 以上。
進一步優化:非必要的背景載入
但這樣還不夠。
我進一步分析了 /new 指令的使用場景:當用戶輸入 /new 時,他們最想要的是什麼?
答案是:秒回一個打招呼的訊息,確認系統已經重置完成。
至於 MEMORY.md 的內容、daily memory 的細節,這些其實可以「背景載入」——先回傳打招呼,然後在背景慢慢讀取這些檔案,補完上下文。
於是我們做了第二輪優化:
flowchart LR
A[用戶輸入 /new] --> B[秒回招呼訊息]
B --> C[背景載入 SOUL.md]
B --> D[背景載入 USER.md]
B --> E[背景檢查 MEMORY.md]
E --> F{存在?}
F -- 是 --> G[載入 MEMORY.md]
F -- 否 --> H[跳過]
B --> I[背景檢查 daily memory]
I --> J{存在?}
J -- 是 --> K[載入 daily memory]
J -- 否 --> L[跳過]
K --> M[完成初始化]
G --> M
H --> M
L --> M
關鍵在於:把「必要」和「非必要」分開。
- 必要:SOUL.md、USER.md(定義 AI 的身份和用戶資訊)
- 非必要:MEMORY.md、daily memory(提供額外上下文,但沒有也不影響基本功能)
先確保必要檔案載入完成,回應用戶。非必要的檔案在背景慢慢載入,載入完成後再更新內部狀態。
更激進的方案:Group Chat 不讀 MEMORY.md
在優化過程中,我們還發現了一個有趣的場景:Group Chat。
在群組聊天中,讀取 MEMORY.md 其實是有安全隱患的。因為 MEMORY.md 可能包含用戶的私人資訊(個人待辦、專案細節等),這些資訊不應該在群組中被 AI 隨口引用。
於是我們做了一個決定:在 Group Chat 中,/new 指令完全不讀取 MEMORY.md。
這樣做有兩個好處:
- 安全性提升:私人資訊不會在群組中洩露
- 效能提升:少讀一個檔案,又快了一點
當然,這意味著在群組中 AI 的「記憶」會比較有限。但權衡之下,我們認為安全性和響應速度更重要。
最終成果
經過這輪優化,/new 指令的響應時間變成了這樣:
| 場景 | 優化前 | 優化後 | 改善幅度 |
|---|---|---|---|
| 一般對話(有最近 memory) | 5-8 秒 | 1-2 秒 | 75%+ |
| Group Chat | 5-8 秒 | <1 秒 | 85%+ |
| Memory 缺失時 | 8-10 秒 | 1-2 秒 | 80%+ |
更重要的是,用戶體驗從「怎麼這麼慢」變成了「欸,變快了欸」。
這個優化教會我的事
回頭看這次優化,我覺得有幾個教訓值得記下來:
1. 測量先於優化
如果沒有加 log 測量每個步驟的時間,我可能會盲目地優化錯誤的地方。比如我本來以為是檔案讀取本身太慢,還考慮要不要換更快的檔案系統⋯⋯結果問題是「讀取不存在的檔案」。
2. 簡單的檢查往往最有效
「先檢查檔案是否存在」這種基本的程式設計,往往是最容易被忽略的。因為它太簡單了,簡單到你會覺得「這應該本來就有吧?」
但現實是,系統在迭代過程中,這些基本檢查往往是最先被犧牲的。
3. 場景分析很重要
發現 Group Chat 不應該讀取 MEMORY.md,這個洞察來自於對使用場景的深入分析。不同場景有不同的需求,一刀切的設計往往不是最好的。
4. 用戶體驗是主觀的
從 5 秒變 2 秒,客觀上還是「需要等待」。但用戶的回饋是「變快了欸」。這說明用戶體驗不只是絕對數字,更是「跟之前比起來」的相對感受。
最後
這次優化讓我深刻體會到:效能優化不一定要用很炫的技術,有時候就是回到最基本的程式設計原則。
檢查輸入、分離必要和非必要、針對場景優化——這些都是教科書裡教過的东西。但真正做起來,還是會不小心忘記。
如果你的 AI 助手也有類似的啟動流程,建議你检查一下:是不是也在讀取不存在的檔案?是不是把不必要的東西放進了關鍵路徑?
如果這篇能幫你省個幾秒鐘,那我的 debug 時間就沒白花了 😂