[CODE]

四個容器跑四個 cron job:我把 n8n Queue Mode 換掉的原因

一套每週執行一次的 LLM 關鍵字管理系統,跑在永遠在線的四個容器上。真正的代價不是費用,而是某個容器靜默失敗七天、由客戶先發現這件事。

1 min read AI 生成
github-actions n8n automation devops

恆春的獨立民宿跟 OTA 競爭,不在預算,也不在品牌——那兩場仗早就輸了。OTA 的佣金遠比外界想像的高,廣告預算不是個人房源能比的量級,還有專職團隊全時間在優化。獨立民宿唯一能主動掌控的變數,是廣告精準度:更緊的關鍵字受眾、更乾淨的歸因、更快的迭代。

我替幾位業主跑的這套系統,就是為了讓這種精準度變成系統化的工作流程,而不是偶發性的手動操作。

每週一,系統用 GAQL 從 Google Ads API 拉取即時關鍵字成效資料,跟 Google Sheets 的關鍵字庫比對——哪些在投放、哪些剛加入、哪些已移除——再把完整資料交給 Claude 分析。LLM 回傳結構化 JSON:一個最佳關鍵字 (MVP)、觀察名單、新增建議、刪除建議。14 天新人保護規則同時寫在系統提示詞裡和 Python 程式碼裡(雙重防線,因為 LLM 偶爾在資料壓力下會忽略提示詞的規則),確保任何兩週內剛加入的關鍵字,不論早期數字多差,都不會出現在刪除名單中。Analyzer 把結果整理成 HTML 郵件寄出,內附一個任務 ID。我看過之後,在 GitHub Actions 的 UI 輸入任務 ID 觸發 executor,系統才實際修改廣告帳戶。在我確認之前,什麼都不會動。

這整套系統,最初跑在 Railway 上的 n8n Queue Mode。

Queue Mode 實際上需要什麼

n8n Queue Mode 需要四個容器:主應用、worker、webhook receiver、PostgreSQL。

在這個架構裡,Postgres 沒有承載任何業務邏輯——n8n 用它跑 LangChain 的聊天記憶體,讓 AI 在跨次執行之間保有對話狀態。關鍵字庫和任務歷程的實際狀態,存在 Google Sheets。拿掉 AI 聊天記憶體的需求,Postgres 就沒有存在的理由。

另外三個容器的存在,是因為 Queue Mode 本身要求它們——不是因為問題本身需要它們。

核准流程是郵件裡的一個按鈕,連到 Railway 的 webhook URL,這個 URL 必須永遠存活才能點擊。四個容器全天候運行,服務一個每週一次的執行和一次人工審核動作。

每月費用:十到十五美元。年化下來是個小數字,但真正的代價不是費用,而是表面積。

靜默七天

n8n 版本升級改動了 HTTP Basic Auth 節點在特定請求類型下序列化憑證的方式。節點認證成功、收到 200、繼續往下走。Google Ads API 那端靜默失敗,回了一個空的 response。下游節點拿到空資料,乾淨地結束,零結果,零錯誤。Visual editor 裡每個節點都是綠色的。

七天後,業主傳訊問我週一的分析報告去哪了。

我在 visual debugger 裡追了三個小時,才接受了這個事實:問題不在任何這個工具能顯示的地方。Auth 節點做到了它合約內的事:認證、回傳 200。Response body 裡有沒有下游節點需要的內容,不在那份合約裡。失敗存在於兩個抽象層之間的縫隙,沒有節點能渲染它。

我不是在除錯問題,而是在除錯除錯環境本身。

遷移跟問題本身等比

Python 腳本本來就在,n8n 只是把它們包在 visual interface 和一套用不到的分散式基礎設施裡。

搬到 GitHub Actions:憑證進 repository secrets,cron 設定 0 1 * * 1 UTC(台北時間週一 09:00),executor workflow 改成 workflow_dispatch,讓我可以在 GitHub Actions 的 UI 輸入任務 ID 核准關鍵字操作。Railway webhook URL 消失了。核准步驟從「點郵件裡的按鈕」變成「到 GitHub、貼上任務 ID、觸發執行」——稍微退步的 UX,換來不需要永遠存活的基礎設施。這個取捨我接受了。

一天,一個 commit。四個容器全部退場。Google Sheets 同時成為人工審查介面和狀態存儲。工作流程定義從 Postgres 搬進 git:可以在 PR 裡審查,git revert 就能回滾,本機直接跑腳本不需要額外環境。

固定月費:零。加上每四次週分析的 LLM API 費用,以目前規模來說是幾塊錢台幣。

服務真實客戶的系統,唯一重要的指標

沒有任何 pipeline 能保證不出錯。Google Ads API 行為會改。OAuth refresh token 會在意想不到的時機過期。Claude 的系統提示詞需要隨著每個房源的競爭環境調整。依賴套件版本會在沒人預期的時候升版。這些都是可以預期的失敗場景。

問題從來不是「會不會出錯」,而是「出錯時誰先知道」。

n8n 那套架構下,業主靜默七天後傳訊給我。換成 GitHub Actions,執行失敗後幾分鐘 email 就到了,附上完整的 run log。這不是反應速度的差異,而是結構性的差異:我先知道,就有機會在業主察覺之前處理好;業主先知道,就已經是一場解釋報告去哪裡了的對話。

一次靜默修好的失敗,不會動搖任何東西。七天的靜默是另一回事。業主每週能觸碰到的唯一具體東西是這份分析——廣告還在跑、費用還在扣、優化邏輯還在運作,但這些對業主來說都是別人螢幕上的事。週報沒有到達,等於服務這一週不存在。七天的靜默失敗,是七天的核心回饋機制壞掉。信任是雙向累積的。

n8n 正確的場景

n8n 的 visual editor 有真實的優勢:當讀懂或修改流程的人不是工程師時,可及性是關鍵。它的錯誤路由也比 GitHub Actions 靈活——個別節點重試、失敗分流、有狀態的 webhook 佇列管理——這些是實質能力。對需要在技術與非技術利害關係人之間流動的流程,visual editor 是它的強項。

對一個從頭到尾都是程式碼、只有工程師在讀的 pipeline,visual layer 是多出來的摩擦,不是安全網。每一層抽象都帶著可觀測性的代價,而我用七天的靜默失敗換到了這個教訓。


週一的排程,不需要四個容器全天候待命才能執行。