部落格

喚醒資料庫時不帶著失效的連線

已發佈的 Mythex App 及其資料庫在無人使用時會進入休眠。App 醒來時,第一個請求可能會卡住 10–12 秒,因為它嘗試重用在一切休眠期間已經失效的資料庫連線。現在我們為 Spring Boot App 提供了在休眠後仍安全的資料庫設定,並告訴代理人以相同方式建置任何技術堆疊的 App。在託管平台上,休眠後的第一個請求從 12.7 秒降到了 2.3 秒。

為什麼 App 和資料庫會休眠

已發佈的 Mythex App 運行在一台閒置數分鐘後就會暫停的機器上,而它的資料庫是無伺服器的 Postgres,閒置五分鐘後休眠,並在下一個請求時喚醒。這讓閒置的 App 保持便宜:你只為資料庫實際使用的部分付費,而休眠中的 App 不會在運算上花費任何費用。我們在依實際使用量計費的資料庫一文中寫過計費的部分。

問題在於,兩者各自休眠,而它們對「連線」的定義並不一致。

症狀:第一次造訪很慢

大多數伺服器端 App 都會保留一個 連線池:一組隨時待命的開啟資料庫連線,讓每個請求不必付出開新連線的成本。對於一直在線的 App 來說,這是正確的設計。

加入休眠之後,會發生以下情況:

  1. App 正在運行,連線池中有幾條閒置的連線。
  2. 沒有人造訪。App 的機器暫停——凍結,連線池保持原樣。
  3. 資料庫進入休眠,並捨棄它那一端的所有連線。
  4. 訪客到來。App 完全照原樣醒來,仍然持有資料庫已經不認識的連線。
  5. 連線池會把其中一條連線交出來。在放棄它之前,必須先嘗試並等待,才能發現它已經失效。

在 Spring Boot App 中,驗證這些失效的連線會讓第一個請求卡住約 10 秒,之後才以「Connection is not available」失敗。對訪客來說,網站就像卡住了,然後可能顯示一個錯誤。

為什麼 Spring App 受害最深

Spring Boot 是一個受歡迎的 Java 框架,預設使用一個名為 HikariCP 的連線池。Spring 可以透過設定來配置該連線池,包括要保留多少條閒置連線。

但 Mythex 會把每個 App 的資料庫以單一的 DATABASE_URL 提供,格式為 postgresql://user:pass@host/db。Spring 無法直接讀取這種格式。因此,在 Mythex 上建置的 Spring App 會透過解析該 URL 在程式碼中自行建立連線池——而手動建立的連線池會忽略 Spring 所有的 spring.datasource.* 設定。即使有人知道正確的連線池設定,也沒有地方能讓它生效。

修正方式之一:Spring 能使用的設定

當專案有資料庫時,我們現在也會提供 Spring 原生讀取的環境變數:

  • SPRING_DATASOURCE_URL、SPRING_DATASOURCE_USERNAME 與 SPRING_DATASOURCE_PASSWORD,由 DATABASE_URL 轉換而來(除非 URL 另有指定,否則需要 SSL)。
  • 能在休眠後保留的連線池設定:
設定數值原因
最小閒置連線數0請求之間不保留任何開啟的連線,因此沒有可能失效的東西
閒置逾時30 秒盡快關閉未使用的連線
最長存活時間2 分鐘在任何連線變老之前將其淘汰
連線逾時5 秒若資料庫回應緩慢,不讓請求卡太久

由於不保留任何閒置連線,醒來的 App 只需開啟一條新連線,而不必在失效的連線之間摸索。

當這些變數存在時,Spring 會自行配置連線池,這些設定就會生效。專案自己設定的值永遠優先;我們只補上缺少的部分。非 Spring 的 App 則會忽略這些變數。

差點讓它毫無作用的細節

Spring 將環境變數對應到其設定的規則很容易弄錯。minimum-idle 這個設定必須寫成 SPRING_DATASOURCE_HIKARI_MINIMUMIDLE——去掉連字號,字詞連在一起。若寫成看起來比較自然的 SPRING_DATASOURCE_HIKARI_MINIMUM_IDLE,它會對應到另一個不存在的設定(minimum.idle),並被靜默忽略。

這種錯誤能通過每次審查,但在正式環境中毫無作用。因此有一項測試會同時檢查這些值是否已設定,以及沒有任何變數使用以底線分隔的寫法。

修正方式之二:其他所有技術堆疊

Spring 並不是唯一有連線池的框架,而我們也無法為每種語言中的每個函式庫預先配置。因此,負責撰寫你的 App 的 Mythex 代理人,現在對於任何技術堆疊都有一條常駐指示:已發佈的 App 及其資料庫在閒置時會休眠,因此保持開啟的連線在下一個請求時可能已經失效。它應該:

  • 不保留任何閒置的資料庫連線,或在使用前檢查每一條連線;
  • 遇到連線錯誤時重試一次;
  • 使用平台在環境中提供的資料庫設定,而不是透過解析 DATABASE_URL 自行建立連線池。

最後一點與第一部分形成閉環:使用平台提供的設定所建置的 App,會自動取得能在休眠後保持安全的配置。

結果

在我們執行已發佈 App 的託管平台上測量,休眠後的第一個請求從 12.7 秒降到 2.3 秒。

我們從中學到什麼

  • 每一個會休眠的層級,都必須就什麼能存活達成一致。 凍結的 App 與休眠的資料庫各自做了合理的選擇。合在一起,App 醒來時卻持有已不再真實的狀態。
  • 為常駐伺服器設計的預設值,不適用於會縮放到零的 App。 讓連線保持溫熱是好主意,直到它周圍的一切都冷卻下來為止。
  • 把資料以框架能理解的格式交給它。 對我們而言,單一 URL 很簡單,卻迫使每個 Spring App 手動建立一個忽略自身設定的連線池。
  • 測試設定的拼寫是否符合框架的讀取方式。 名稱錯誤的正確數值,會靜默地毫無作用。

如果你還不熟悉 Mythex 上的資料庫運作方式,請參見資料庫文件,以及我們介紹 什麼是 Postgres 的說明。

Mythex · Pricing · 用 AI 打造任何東西 · 網站與應用程式範本 · Mythex 比較 · AI 應用程式開發詞彙表 · 免費的 AI 應用程式開發工具 · English

English አማርኛ العربية Български বাংলা Bosanski Català Čeština Dansk Deutsch Ελληνικά Español Español (España) Eesti فارسی Suomi Français Français (Canada) ગુજરાતી हिन्दी Hrvatski Magyar Հայերեն Bahasa Indonesia Íslenska Italiano 日本語 ქართული Қазақ тілі ಕನ್ನಡ 한국어 Lietuvių Latviešu Македонски മലയാളം Монгол मराठी Bahasa Melayu မြန်မာ Norsk bokmål Nederlands ਪੰਜਾਬੀ Polski Português (Brasil) Português (Portugal) Română Русский Slovenčina Slovenščina Soomaali Shqip Srpski Svenska Kiswahili தமிழ் తెలుగు ไทย Tagalog Türkçe Українська اردو Tiếng Việt 简体中文 繁體中文(香港)