Neon 睡著時,別讓使用者看到 500
客戶專案踩坑
Ricky-
08 月 22,2026

客戶網站的訂閱與發文都走同一個雲端資料庫。上線沒多久,我們注意到一個規律:只要一段時間沒人操作,下一個寫入請求就會卡很久、甚至直接回 500。使用者按下「訂閱」,轉了快一分鐘,最後拿到一個錯誤——這是最不該出現在「轉換動作」上的體驗。

把後端日誌撈出來,畫面大概長這樣:

cannot connect to Postgres: Connection terminated due to connection timeout
Connection terminated unexpectedly
POST /subscribe 500 in 88s   # 使用者就這樣等了 88 秒
現象

只有「閒置後的第一個請求」會中招;重試一次往往就正常了。這是最典型的暫時性故障——偶發、可自癒、卻剛好砸在使用者臉上。

為什麼會這樣

Neon 這類 serverless Postgres 會在閒置後自動暫停來省資源。暫停後的第一個查詢要先把資料庫喚醒、重新建立連線,這段冷啟動可能要數十秒;而連線池裡原本握著的連線這時早已失效,直接拿來用就會噴連線錯誤。

所以這不是 bug,是 serverless 的本質。我們沒辦法消滅冷啟動,但可以讓它對使用者隱形。

t0 ≈ 3.5s 資料庫暫停中 / 喚醒中… 已就緒 試 1 ✗ 逾時 退避 0.5s 試 2 ✗ 連線斷 退避 1s 試 3 ✓ 成功回應 使用者感受:慢一兩秒,但成功——不是 500
一層退避重試,把偶發的冷啟動 500 變成「慢一點但成功」。

解法:一層很薄的重試

我們加了一個包裝函式 withDbRetry(),把「取得連線+這次查詢」整段包起來。重點只有三個:只重試連線類的暫時錯誤、用指數退避給資料庫醒來的時間、以及設一個嘗試上限讓最壞情況的延遲有界。

async function withDbRetry(fn) {
  for (let n = 1; n <= ATTEMPTS; n++) {
    try { return await fn(); }
    catch (err) {
      // 不是連線錯誤、或用完次數 → 立刻拋,不要硬撐
      if (!isTransientDbError(err) || n === ATTEMPTS) throw err;
      await sleep(BASE * 2 ** (n - 1)); // 0.5s → 1s → 2s
    }
  }
}
錯誤發生 連線類 暫時錯誤? 退避後重試 0.5s → 1s → 2s(有上限) 立刻拋出:email 格式 / unique 衝突 / 任何 4xx
重試只走「暫時性連線錯誤」這條;其餘一律立刻拋,別浪費時間也別送出重複資料。

真正的眉角在這裡

重試很容易寫,但寫錯方向反而更糟。這幾條是我們在客戶站上實際踩過、才補起來的:

  • 只重試「連線」錯誤。email 格式錯、unique 衝突、任何 4xx,都要立刻往上拋。重試它們不但沒用,還可能送出重複資料。
  • 錯誤鏈要穿透。CMS 常把底層的 pg 錯誤包一層再丟出來,判斷是不是暫時性故障時,要往 err.cause / originalError 一路挖進去。
  • 寄信別包進重試。SMTP 本來就慢,把它一起重試只會讓請求更久、更容易再逾時。我們讓寄信留在資料庫重試範圍之外。
  • 冪等要顧。萬一連線是在 commit 之後才斷、重試又跑一次建立,得靠 unique 衝突被吸收成「已訂閱」,而不是變成 500 或重複寄出歡迎信。
帶得走的一句話

Serverless 的冷啟動消滅不了,但可以讓它對使用者隱形。一層只針對暫時性連線錯誤、帶退避與上限的重試,就能把偶發的 500 變成「慢一兩秒但成功」。

這是我們在這個旅遊部落格客戶專案裡,一連串「serverless 很方便、但邊角要自己收」的其中一坑。之後幾篇會接著寫我們在同一個站上遇到的其他問題。

Tags:
NeonPostgres可靠性


相關文章
同一個測驗,我隔三年重寫了一次
同一個測驗,我隔三年重寫了一次
ricky - 2026-08-23T02:50:34.654532Z
2023 年寫的三個線上測驗,2026 年整個重寫。同一份題目、同一個作者,差別不在技術:當初沒問過「他們在什麼情況下用」,而課堂上一群人拿手機填,在程式碼裡留下了一頁到底、id 重複、把觸控目標縮小...
現實的挑戰:為了生存,我不得不開始接案
現實的挑戰:為了生存,我不得不開始接案
ricky - 2025-05-01T14:28:41Z
創業的夢想雖然美好,但當你真正踏入這個領域時,現實的挑戰與費用壓力讓人無法忽視。在這篇文章中,分享了從開設公司到開始接案的心路歷程。從一開始的迷茫與不安,到逐步找到穩定的收入來源,接案成為了生存下去的...
創業起點|開公司不是寫表單,而是做選擇
創業起點|開公司不是寫表單,而是做選擇
ricky - 2025-05-19T14:08:01Z
開公司不只是填寫申請書!這篇文章分享了創業者在設立公司過程中,從公司命名、類型選擇(行號 vs. 有限公司 vs. 股份有限公司)、到稅務考量的實戰經驗與建議。如果你正準備創業,這篇避坑指南絕對值得一...
訂閱電子報
隨時掌握最新的創業故事與設計開發過程
內容包含來自麒航團隊的日常分享、專業知識與工具推薦,助你了解更多創業背後的思維與技術。
麒航私房推薦
Clelereve Blog LogoClelereve Blog LogoClelereve Blog Logo
廣告區
Clelereve Blog Logo
聯絡我們
任何合作或問題請洽:
clelereve@gmail.com
Follow us
Copyright © 2025 blog.clelereve.com - All Rights Reserved
Frontend Version: -- | Backend Version: --
創業實驗筆記 - Clelereve Blog | Neon 睡著時,別讓使用者看到 500 | Clelereve Blog