Cloudflare 公開 Hyperdrive MySQL 從 beta 到 GA 的除錯實錄:utf8 其實不是完整 UTF-8、連線重置會把編碼打回伺服器預設,還有一個只在 0.001% 請求出現、因兩個節點共用快取而回傳空結果的怪 bug。
產品公告通常長這樣:「我們很高興宣布 XXX 正式上線!」然後是三張漂亮的架構圖和一個註冊按鈕。Cloudflare 這次的公告多了一句很誠實的話:beta 和 GA 之間的差距,其實就是「20 個 bug 疊起來穿著一件風衣」。
2026 年 10 月 14 日,Cloudflare 工程師 Pranoy Dutta 發文宣布 Hyperdrive 的 MySQL 支援正式 GA,並把除錯過程攤開來講。這篇文章對有在跑 MySQL 的團隊——包括大量 Laravel 專案——是一份難得的「別人踩過的坑」清單。
先搞懂:Hyperdrive 在做什麼
Hyperdrive 是 Cloudflare 給 Workers 用的分散式資料庫連線池。它在全球網路上維持「暖」的 SQL 連線,讓許多 Worker 請求共用;也能把常見查詢的結果放進 CDN 快取,降低延遲與來源資料庫負載。
為什麼需要它?文章指出,建立一條 SQL 連線至少要五次網路往返。在 Workers 這種無伺服器平台上,每個請求都重新連線,延遲和連線數都會爆掉。MySQL 雖然不像 Postgres 那樣每條連線用一整個作業系統行程,但連線數一多仍會吃緊。Hyperdrive 原本只支援 Postgres,MySQL 的 beta 是去年宣布的。
Beta 期間的目標,是驗證真實客戶流量能達到 99.99% 的可用性目標。團隊會追蹤 Worker 發出的用戶端連線中,有多少比例以 Hyperdrive 相關錯誤結束,所以他們很清楚可靠性還有缺口;再加上 beta 客戶的回報,才一路挖出後面這幾個又小又陰險的問題。
坑一:utf8 不是你以為的 UTF-8
Beta 最早被回報的問題之一:寫進資料庫的 emoji 讀回來是錯的。原因出在連線握手時協商的編碼是 utf8——而在 MySQL 裡,utf8 是已棄用的 utf8mb3 別名,每個字元只支援 1 到 3 個位元組;完整的 UTF-8 最多需要 4 個位元組。改成協商 utf8mb4 就解決了。
這個坑台灣團隊應該很熟:早年很多系統的會員暱稱、留言一遇到 emoji 就變問號,甚至整筆寫入失敗,罪魁禍首常常就是 utf8。Laravel 預設的 MySQL 設定使用 utf8mb4,但老專案、手動建立的資料表、或從舊主機搬過來的資料庫,仍值得檢查一次。
坑二:連線重置會把編碼打回原形
Hyperdrive 後來加入 COM_RESET_CONNECTION 做連線清理:這是 MySQL 一個輕量指令,用來清除連線的 session 狀態,確保不同借用者之間不會互相洩漏狀態。結果——編碼問題又回來了。
原因是:COM_RESET_CONNECTION 不會把編碼重設成「連線啟動時協商的值」,而是重設成伺服器預設值。如果伺服器預設是 latin1,重置後連線就變成 latin1。修正方式是每次重置後,都再執行一次設定 utf8mb4 的指令。
這給所有用連線池的人一個提醒:「重置」到底重置成什麼? 無論是 ProxySQL、PgBouncer 還是應用程式自己的長連線,都該確認連線被回收再借出時,編碼、時區、SQL mode 等 session 設定是否還是你要的。
坑三:只出現 0.001% 的空結果
真正的魔王出現在最大流量的 MySQL 客戶身上:有一小部分、但持續存在的查詢,回傳了「空結果」,而這些查詢理論上一定有資料。發生率大約 0.001%,連端到端追蹤都很難抓到。
團隊翻遍遙測、理解客戶邏輯、提高該執行個體的追蹤取樣率、排查網路問題,都沒找到答案。唯一線索是:只有開啟快取的 Hyperdrive 執行個體會出事。
兩個節點、一個共用快取
每個 Hyperdrive 執行個體分成兩種節點:
- Client 節點:跑在使用者 Worker 所在的機器上,處理本地快取,必要時把查詢轉給 Endpoint;
- Endpoint 節點:持有連線池,放在最靠近來源資料庫的資料中心。
兩者都使用所在資料中心的 CDN 快取。所以如果它們剛好在同一個資料中心,就會共用同一份快取。快取鍵是查詢訊息內容經正規化後的雜湊值。這套設計成立的前提是:同一個回應,不論誰寫入,快取的位元組都必須一模一樣。
capability 不一致,快取就說了兩種語言
MySQL 協定在連線階段會協商「capabilities」。團隊發現,Node.js 常用的 MySQL 驅動 mysql2 目前不支援 CLIENT_DEPRECATE_EOF。沒有這個能力時,伺服器會在欄位定義和資料列之間送一個 EOF 封包,結果集結尾再送一個 EOF;協商了這個能力,中間的 EOF 會省略,結尾改用 OK 封包。
Hyperdrive 對來源資料庫用固定的一組 capabilities,再把回應翻譯成 Worker 驅動期待的格式。問題就在這裡:Client 的快取程式沿用了 Postgres 時代的假設——「快取我寫回給 Worker 的那些位元組就好」。但在 MySQL,Client 寫回的是翻譯後的格式,Endpoint 快取的卻是未翻譯的格式。兩邊在同一資料中心互相讀到對方的快取時:
- Client 讀到 Endpoint 存的原始格式,沒翻譯就直接回給 Worker;
- Endpoint 讀到 Client 存的翻譯後格式,再交給別的 Client 翻譯一次,結果提早送出 EOF,或乾脆卡住。
修正其實很簡單:不管哪個節點寫入,快取一律存用 Hyperdrive capabilities 編碼的標準格式。 上線後空結果消失,Client 連線成功率達到 99.99%。
Laravel 專案的十分鐘自我檢查
讀完這篇,最值得馬上做的一件事,是替維護中的專案做一次編碼健檢。步驟很簡單:
-- 伺服器與目前連線的編碼設定
SHOW VARIABLES LIKE 'character_set%';
-- 每張資料表的定序,找出還停在 utf8 或 latin1 的漏網之魚
SELECT table_name, table_collation
FROM information_schema.tables
WHERE table_schema = DATABASE();
接著確認 Laravel 的連線設定:
// config/database.php → connections.mysql
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
如果伺服器預設 character_set_server 不是 utf8mb4,又有用連線池或長連線,就要特別留意 Hyperdrive 遇到的那種「重置後編碼被打回預設」情境。最後別忘了實測:用一個帶 emoji 的暱稱註冊、留言、匯出報表,從頭到尾走一遍,比任何設定檔都誠實。
給維運與後端團隊的五個啟示
- 全面使用 utf8mb4。 資料庫、資料表、連線設定都檢查一次;Laravel 專案確認
config/database.php的charset與collation。 - 連線池的 session 狀態要明確重設。 不要假設「重置」會回到你協商的值。
- 快取存「標準格式」,不要存「呈現格式」。 同一份資料若有多種輸出(API 版本、語系、裝置),在快取鍵或內容中明確標示格式版本,或統一存原始資料、輸出時再轉換。
- 罕見 bug 從「功能開關」找相關性。 Cloudflare 的突破口是「只有開快取的才出事」。平常就讓遙測記錄每個請求開了哪些功能,出事時才有線索可比對。
- 可觀測性要針對單一租戶放大。 提高特定執行個體的追蹤取樣率,是找出極低頻問題的實用手段。
你可能會問
Q:我們的 Laravel 不是跑在 Workers 上,這跟我們有關嗎?
A:Hyperdrive 本身是給 Workers 用的,但 utf8mb4、連線重置與快取格式這三個教訓,適用於任何 MySQL 加連線池加快取的架構。
Q:怎麼開始用 Hyperdrive 連 MySQL?
A:可以從控制台或 wrangler 建立指向既有 MySQL 相容資料庫的 Hyperdrive,也能直接連新的 PlanetScale 資料庫。官方範例是 npx wrangler hyperdrive create,搭配 MySQL 連線字串。
Q:0.001% 這麼低,值得花那麼多力氣嗎?
A:對最大流量的客戶來說,0.001% 也是穩定發生、會被業務注意到的錯誤;而且「空結果」比「報錯」更危險,因為程式會把它當成正常資料繼續跑。
結語
這篇文章最值得學的,不是 Hyperdrive 的架構,而是 Cloudflare 願意把「我們的假設錯在哪」寫出來:一個在 Postgres 合理的假設,搬到 MySQL 就變成 0.001% 的幽靈。對維運團隊來說,可靠性不是上線那天的勳章,而是一個一個把穿風衣的 bug 抓出來的過程。
下次專案要從 beta 轉正式,不妨也列一張自己的「風衣清單」:編碼、連線狀態、快取格式,一項一項打勾,再按下那顆「正式上線」的按鈕。
來源: Inside Hyperdrive: How we solved a rare Hyperdrive MySQL caching bug(Pranoy Dutta,Cloudflare Blog,2026/10/14)。