Hyperdrive MySQL がついに GA! 0.001% の謎の空レスポンスを追いかけた話
みんな、しぃちゃんだよ! 今日は Cloudflare Blog から、Hyperdrive の MySQL 対応が GA になったお話と、その裏にあった手ごわいバグ退治を紹介するね!
Cloudflare Blogなにが発表されたの?
Cloudflare の Blog で「Inside Hyperdrive: How we solved a rare Hyperdrive MySQL caching bug」が公開されたよ。Workers 向けのコネクションプーラー Hyperdrive は最初 Postgres だけだったけど、MySQL にも正式対応(GA)したの。記事では、そこに至るまでに見つけた珍しいキャッシュのバグを解説しているよ。
なぜ重要なの?
MySQL は接続の確立に最低 5 回のネットワーク往復が必要で、サーバーレス環境ではプーリングが大事なの。ベータ版は本番向けに 99.99% の可用性を目標に動いていて、ベータから GA までに入った修正は約 20 個だったそうだよ。
深く潜ってみよう
先に直した 2 つのバグ
- 絵文字の文字化け: Hyperdrive が
utf8でネゴシエーションしていたけど、これはutf8mb3の別名で 4 バイト文字が扱えなかった。utf8mb4にして解決 - 文字コードのリセット:
COM_RESET_CONNECTIONは、ネゴシエーションした文字コードではなくサーバーのデフォルトに戻す。デフォルトがlatin1のサーバーでは接続がlatin1に戻っていたので、リセットのたびにutf8mb4を設定し直すようにした
本命のバグ
最大の MySQL 顧客から、本来空じゃないはずのレスポンスが空で返ってくる、という報告が来たよ。割合は約 0.001% のリクエスト。テレメトリもトレースもネットワーク確認も手がかりなしで、わかったのは キャッシュを有効にしたときだけ起きる ことだけだったの。
Hyperdrive には 2 つの役割があるよ。
- Client: Worker と同じマシンで動き、ローカルのキャッシュを扱ってクエリを転送する
- Endpoint: コネクションプールを持ち、オリジンのデータベースの近くにいる
どちらも同じデータセンターの CDN キャッシュを使うので、同じデータセンターにいると同じキャッシュを共有するよ。キャッシュキーは正規化したクエリのワイヤーメッセージのハッシュで、値は結果のバイト列。「同じバイト列ならどのノードが書いても有効」という前提で作られていたの。
ところが MySQL ではこの前提が崩れていたよ。Endpoint は常に Hyperdrive 固定のケイパビリティで動き、Client が Worker 側ドライバーのケイパビリティに合わせて変換するの。たとえば Node.js の mysql2 は CLIENT_DEPRECATE_EOF に対応していないから、Hyperdrive は結果セットに EOF パケットを足して書き換え、OK パケットを EOF に変換するんだよ。Postgres の感覚で「Worker に送ったバイト列をそのままキャッシュ」していたせいで、共有キャッシュで 2 つの事故が起きたの。
- Endpoint が書いたものを Client が読む: Hyperdrive 形式のバイト列が変換されないまま Worker に返る
- Client が書いたものを Endpoint が読む: Worker 向けのバイト列が別の Client に渡り、再変換で途中 EOF やハングになる
修正
どのノードが書いても、キャッシュには Hyperdrive のケイパビリティでエンコードした正規形を保存して、Client が読むときに変換するようにしたよ。リリース後は空行の問題が消えて、Client の接続成功率は 99.99% に達したそうだよ。
使いはじめ方
mysql:// の接続文字列を使って npx wrangler hyperdrive create を実行するか、ダッシュボードから作成できるよ。PlanetScale にも直接対応しているの。
まとめ
- Hyperdrive の MySQL 対応が GA
- 文字コード関連の 2 つのバグを修正
- 0.001% の空レスポンスは、キャッシュに入るバイト列の形式がノードで違っていたのが原因
- キャッシュは正規形で保存して、読むときに変換する方式に変更
- Client の接続成功率は 99.99%
MySQL を Workers から使いたい人や、プロトコルまわりのバグ調査譚が好きな人に刺さるよ!