喚醒資料庫時不再遇到失效連線
在 Mythex 上已發佈的應用程式及其資料庫,在沒有人使用時都會進入休眠。應用程式醒來時,其第一個請求可能會卡住 10 至 12 秒,因為它嘗試重用那些在一切休眠期間已經失效的資料庫連線。現在我們為 Spring Boot 應用程式設定了在休眠後仍然安全的資料庫參數,並要求代理以同樣的方式建構任何技術棧的應用程式。在託管平台上,喚醒後的第一個請求由 12.7 秒降至 2.3 秒。
為什麼應用程式和資料庫會休眠
已發佈的 Mythex 應用程式運行在一台閒置幾分鐘後便會暫停的機器上,而它的資料庫是無伺服器的 Postgres,閒置五分鐘後便會休眠,並在下一個查詢到達時喚醒。這正是讓一個冷清的應用程式保持低成本的原因:你只需為資料庫實際用量付費,而休眠中的應用程式不會在運算上花費任何費用。我們在按實際用量計算資料庫費用一文中談過計費的那一面。
麻煩在於,兩者各自休眠,而且對「連線」的理解並不一致。
症狀:首次造訪很慢
大多數伺服器應用程式都會保持一個連線池:預先開啟的一小批資料庫連線,使每個請求都不必承擔開啟新連線的成本。對於一直運行的應用程式,這是正確的設計。
一旦休眠介入,就會發生以下情況:
- 應用程式運行中,其池中有幾條閒置連線。
- 沒有人造訪。應用程式的機器暫停——凍結,連線池完好無損。
- 資料庫進入休眠,並切斷它那一端的所有連線。
- 訪客到達。應用程式原樣醒來,但仍持有資料庫已不再認得的連線。
- 連線池派出其中一條連線。它必須先試用並等待,才能確定它已死亡,然後才放棄它。
在 Spring Boot 應用程式中,驗證這些失效連線會令第一個請求被阻塞約 10 秒,然後才以「Connection is not available」失敗。對訪客來說,網站就是卡住,然後可能顯示一個錯誤。
為什麼 Spring 應用程式受影響最大
Spring Boot 是熱門的 Java 框架,它預設使用一個名為 HikariCP 的連線池。Spring 可以透過設定來配置該池,包括要保留多少條閒置連線。
但 Mythex 會把每個應用程式的資料庫以單一 DATABASE_URL 提供,格式為 postgresql://user:pass@host/db。Spring 無法直接讀取這個格式。因此,在 Mythex 上建構的 Spring 應用程式,都是自行以程式碼解析該 URL 來建立連線池——而手動建立的連線池會忽略 Spring 所有的 spring.datasource.* 設定。即使有人知道正確的池設定,也沒有地方讓它們生效。
修復方法之一:Spring 可以使用的設定
當專案有資料庫時,我們現在還會提供 Spring 原生讀取的環境變數:
SPRING_DATASOURCE_URL、SPRING_DATASOURCE_USERNAME和SPRING_DATASOURCE_PASSWORD,由DATABASE_URL轉換而來(除非 URL 另有指定,否則要求 SSL)。- 能在休眠後保持有效的池設定:
| 設定 | 數值 | 原因 |
|---|---|---|
| 最少閒置連線數 | 0 | 在請求之間不保留任何開啟的連線,因此沒有東西會過時 |
| 閒置逾時 | 30 秒 | 盡快關閉未使用的連線 |
| 最長存活時間 | 2 分鐘 | 在任何連線變老之前將其退役 |
| 連線逾時 | 5 秒 | 若資料庫回應緩慢,不讓請求被長時間掛起 |
由於不保留任何閒置連線,醒來的應用程式會開啟一條全新的連線,而不必在失效連線中逐一嘗試。
當這些變數存在時,Spring 會自行配置連線池,這些設定便會生效。專案自己設定的任何值都永遠優先;我們只填補缺少的部分。非 Spring 的應用程式會忽略這些變數。
差點令它毫無作用的細節
Spring 把環境變數對應到其設定的規則很容易弄錯。設定 minimum-idle 必須寫成 SPRING_DATASOURCE_HIKARI_MINIMUMIDLE——去掉連字號,字詞連在一起。如果寫成看起來更自然的 SPRING_DATASOURCE_HIKARI_MINIMUM_IDLE,它會對應到另一個不存在的設定(minimum.idle),並被悄悄忽略。
這類錯誤能通過每一次審查,卻在正式環境中毫無作用。因此,我們的測試會同時檢查數值是否已設定,以及是否沒有任何變數使用以底線分隔的寫法。
修復方法之二:其他所有技術棧
Spring 並不是唯一有連線池的框架,我們也無法為每種語言中的每個函式庫預先配置。因此,負責撰寫你的應用程式的 Mythex 代理,現在對任何技術棧都有一條常設指示:已發佈的應用程式及其資料庫在閒置時會休眠,所以保持開啟的連線在下一個請求時可能已經失效。它應該:
- 不保留任何閒置的資料庫連線,或在使用前逐一檢查,
- 遇到連線錯誤時重試一次,以及
- 使用平台在環境中提供的資料庫設定,而不是自行解析
DATABASE_URL建立連線池。
最後一點與第一部分相互呼應:使用平台所提供設定的應用程式,會自動採用能應付休眠的配置。
結果
在我們用來發佈應用程式的託管平台上測量,喚醒後的第一個請求由 12.7 秒降至 2.3 秒。
我們的心得
- 每個會休眠的層,都必須就什麼能夠保留達成一致。 凍結的應用程式和休眠的資料庫各自作出了合理的選擇。合在一起,應用程式醒來時所持有的狀態已經不再是真實的。
- 為常開伺服器設計的預設值,不適合會縮減至零的應用程式。 保持連線溫暖是好主意,前提是周圍的一切都沒有變冷。
- 以框架自己的格式提供資料。 單一 URL 對我們來說很簡單,卻迫使每個 Spring 應用程式手動建立一個忽略自身設定的連線池。
- 測試設定的拼寫是否符合框架的讀取方式。 名稱錯誤的正確值毫無作用,而且會悄無聲息。
如果你對 Mythex 上資料庫的運作還不熟悉,請參閱資料庫文件,以及我們的解說什麼是 Postgres。