如何在網頁 App 中執行背景工作:佇列、排程器與 Cron
要在網頁 App 中執行背景工作,首先要把工作分成兩種。觸發式工作(發送歡迎郵件、處理上傳的檔案)可以存入工作佇列,通常是資料庫中的一張表,並由具備重試機制的工作程式處理。排程式工作(每晚的報表、每 15 分鐘一次的提醒)則需要某種能按時程觸發的機制:主機的 cron 功能,或是呼叫你 App 中受保護端點的外部排程器。務必讓工作能安全地重複執行、記錄失敗情況,而且絕不要依賴那個可能休眠或重新啟動的伺服器內部計時器。
本指南說明各種選項、各自適合的情境,以及如何透過 AI App 建置工具進行設定,包括在會縮減至零的主機上。
什麼算是背景工作
任何緩慢、不可靠,或不是使用者立即回應所必需的工作:
- 寄送電子郵件、通知與收據
- 調整圖片大小、轉碼影片、產生 PDF
- 呼叫緩慢的第三方 API,包括執行長時間任務的 AI 模型
- 從其他系統匯入或同步資料
- 每晚的清理、報表與提醒
把這些工作放在請求流程中,會讓頁面變慢且脆弱:如果郵件服務商故障,使用者的註冊就會失敗。把它們移到背景執行,使用者就能立即得到回應,而且工作可以重試。
各種選項
| 選項 | 運作方式 | 優點 | 缺點 |
|---|---|---|---|
| 同一程序中的即發即忘 | 先回應,然後在伺服器中繼續執行工作 | 不需設定 | 伺服器停止時會遺失;沒有重試 |
程序內計時器(setInterval、setTimeout) | 在執行中的伺服器內建立計時器 | 非常簡單 | 伺服器休眠時不會執行;多個副本時會重複 |
| 資料庫式佇列 | 工作以資料列儲存;工作程式認領並執行 | 使用你既有的資料庫;持久耐用;容易檢查 | 你需要自行實作重試與鎖定;需要某個東西來執行工作程式 |
| 專用佇列(基於 Redis 或託管服務) | 佇列服務加上工作程式 | 重試與排程功能成熟 | 又多一個需要運行與付費的服務 |
| 託管的工作或工作流程服務 | 第三方會呼叫你的端點,並提供重試與排程 | 排程與重試都由服務處理 | 依賴廠商;多一個帳號與金鑰 |
| 外部排程器呼叫端點 | 其他地方的計時器依排程發送 HTTP 請求 | 可在任何主機上運作,包括縮減至零的主機 | 有最短間隔與延遲;需要保護端點 |
| 主機內建的 cron | 平台依排程執行指令 | 有提供時簡單易用 | 並非每個主機都提供 |
沒有唯一正確的答案。小型 App 通常只需要一個資料庫佇列加上外部排程器。
為什麼縮減至零的主機改變了情況
許多現代主機在沒有人使用你的 App 時會讓它休眠,並在下一次請求時喚醒。這樣能省錢,但休眠中的伺服器不會執行任何程式碼:計時器不會觸發,工作程式也不會輪詢。在這些主機上,排程工作必須由一個進來的請求來啟動,而最簡單的請求來源就是外部排程器。每次呼叫都會喚醒 App(通常也會喚醒資料庫),因此每分鐘呼叫一次的成本,會高於每 15 分鐘呼叫一次。請選擇能滿足需求的最慢排程。
模式一:資料庫工作佇列
適用於觸發式工作:
- 建立一張
jobs表:類型、酬載(JSON)、狀態(pending、running、done、failed)、嘗試次數、run_after、last_error、時間戳記。 - 當某件事發生時,在造成該變更的同一個交易中插入一筆工作資料列,讓兩者同時成功或同時失敗。
- 工作程式挑選到期的工作,將其標記為
running,執行後標記為done。 - 失敗時,增加
attempts,把run_after設定到更晚的時間(例如 1 分鐘、5 分鐘,然後 30 分鐘),並儲存錯誤訊息。達到最大次數後,將其標記為failed並通知相關人員。
如果可能有多個工作程式同時運行,工作必須被安全地認領。在 Postgres 上,SELECT ... FOR UPDATE SKIP LOCKED 能讓每個工作程式認領不同的資料列,而不會互相衝突。
由什麼來執行工作程式?在一直運行的伺服器上,可以是獨立程序中的迴圈。在縮減至零的主機上,模式二中的排程端點可以在每次呼叫時處理一批到期的工作。
模式二:由外部排程器呼叫你的 App
適用於排程式工作:
- 新增一個端點,例如
POST /api/cron/send-reminders。 - 保護它:要求在標頭中帶有一組機密權杖,該權杖作為機密儲存在雙方,並拒絕其他任何請求。
- 讓它具備冪等性:同一分鐘內執行兩次,不可以寄出兩次提醒。記錄已經寄出的內容。
- 讓每次呼叫都簡短:處理一批(例如 100 筆),剩下的由下一次呼叫接手。
- 將排程器指向這個端點。
排程器的選擇:
- GitHub Actions 排程工作流程。 在 GitHub 的使用限制內免費。根據 GitHub 截至 2026 年 9 月的文件,最短間隔為每 5 分鐘一次;在尖峰時段(特別是每小時的開頭)執行可能會延遲;排程預設使用 UTC;而公開儲存庫中的排程工作流程,若超過 60 天沒有儲存庫活動,會被停用。
- 託管的 cron 服務。 有許多服務的唯一工作就是依排程呼叫一個網址。在依賴它之前,請檢查其可靠性、最短間隔與告警功能。
- 你的雲端服務商排程器。 如果你已經在使用大型雲端服務商,它會提供可以呼叫 HTTPS 端點的託管排程器。
- 託管的工作或工作流程服務。 這些服務結合了排程、佇列與重試,並在每個步驟呼叫你的端點。請在各家廠商的網站上比較價格與限制。
模式三:交給服務商處理
有時候,最好的背景工作是根本不需要自己做:
- 付款。 Stripe 會重試失敗的訂閱付款並發送 Webhook;你只需回應,而不必輪詢。請參閱什麼是 Webhook。
- 電子郵件。 一旦你交出郵件,服務商會為遞送排隊並重試。
- 大量的 AI 工作。 OpenAI 與 Anthropic 都提供批次 API,適用於不急迫的請求,你可以提交後再回收結果。
交給 AI 建置工具的提示詞
建立佇列:
使用我們資料庫中的 jobs 表(類型、酬載 JSON、狀態、嘗試次數、run_after、last_error)新增一個背景工作佇列。當使用者註冊時,在同一個交易中插入一筆 send_welcome_email 工作,而不是在流程中直接寄信。新增一個 processJobs(limit) 函式,使用 SELECT ... FOR UPDATE SKIP LOCKED 認領到期的工作、執行它們,並在失敗後分別於 1、5、30 分鐘後重試,4 次失敗後標記為 failed。
設定排程觸發:
新增受不記名權杖保護的 POST /api/cron/tick,權杖來自機密 CRON_SECRET;沒有該權杖的請求一律拒絕。每次呼叫都執行 processJobs(100),並寄出未來 24 小時內到期、且尚未寄出的預約提醒,同時記錄已寄出的提醒,確保不會重複寄送。以 JSON 摘要回傳執行結果。接著撰寫一個 GitHub Actions 工作流程,每 15 分鐘呼叫這個端點一次,並從儲存庫機密中讀取網址與權杖。
常見錯誤
- 依賴
setInterval。 當伺服器休眠或重新啟動時,它會悄悄停止;而當兩個副本同時運行時,又會重複執行。 - 未受保護的 cron 端點。 任何找到它的人都能觸發你的工作,或是讓你的電子郵件帳單暴增。
- 工作不具冪等性。 重試與重複的排程器呼叫一定會發生。設計時就要考慮到這點。
- 沒有失敗的可見性。 一項工作悄悄失敗一整週,比大聲失敗更糟。請記錄錯誤,並對重複的失敗發出警示。
- 在單一請求中執行很長的工作。 請求會逾時。請分批處理。
- 排程太頻繁。 在縮減至零的主機上,每次呼叫都會喚醒並計費。請讓間隔配合實際需求。
- 時區問題。「早上 9 點」對你的排程器(通常是 UTC)與對你的使用者意義不同。請以 UTC 儲存時間,並依使用者換算。
- 把機密寫在工作流程檔案中。 請把權杖保存在排程器的機密儲存區中。請參閱如何安全地保管 API 金鑰。
檢查清單
- 緩慢的工作已移出使用者請求
- 工作被持久儲存,而不只存在記憶體中
- 重試採用遞增的延遲時間,並設有最大次數
- 每項工作都能安全地重複執行
- 排程端點受到機密權杖保護
- 每次執行都處理有限的一批
- 失敗的工作可見,並設有警示
- 排程間隔的選擇已考慮託管成本
- 時間以 UTC 儲存
Mythex 中的背景工作
Mythex 的文件並未說明排程或 cron 工作,而且已發布的 App 與其資料庫在閒置時會休眠,並在下一次請求時喚醒(請參閱主機限制)。因此,不要依賴 App 內部的計時器。可行的做法是上述的方式:請 Agent 為你建立一張 jobs 表與一個受權杖保護的端點,然後將外部排程器指向你已發布的網址。把權杖存為專案機密,並記住每次喚醒都會為 App 與資料庫醒著的時間消耗點數,因此請選擇合理的間隔。如果你的 App 需要 API 後端來處理這件事,REST API 後端教學是很好的起點,而如何建置 REST API則說明了如何設計端點。如果你的產品核心是需要持續運行的工作程式或機器人,那麼專為持久性程序設計的主機可能更適合你。
常見問題
什麼是背景工作?
背景工作是指 App 在使用者正在等待的請求之外所執行的工作,例如寄送電子郵件、調整圖片大小、產生報表或同步資料。使用者能快速得到回應,工作則另外執行,失敗時也可以重試。
如果主機沒有 cron,我要如何執行排程任務?
新增一個受保護的端點來執行工作,然後讓外部排程器依照時程呼叫它。可選的方式包括排程的 GitHub Actions 工作流程、託管的 cron 服務,或你雲端服務商的排程功能。請用一組機密權杖來保護這個端點。
排程任務可以使用 setInterval 或 setTimeout 嗎?
只適合用於小型、不重要的工作,而且伺服器必須持續運行。如果伺服器重新啟動、縮減至零,或同時運行多個副本,計時器就會遺失,或重複執行。請把排程工作存放在資料庫中,或使用排程器。
Mythex 支援 cron 工作嗎?
Mythex 的文件並未說明排程或 cron 工作,而且已發布的 App 在閒置時會休眠。最可靠的做法是讓外部排程器呼叫你 App 中受保護的端點,這會喚醒 App 並執行工作。