🙄

ProxySQLのReader/Writer振り分けが引き起こすDB接続エラーを理解する

に公開

背景

ProxySQL 経由でDB(Aurora)に接続している環境で、散発的にDB接続エラーが発生していたのですが、アプリケーションのバグではなく、ProxySQL のルーティングロジックに起因する問題だと分かった話。

Aurora のアーキテクチャ

従来のDBとの違い

従来のDBアーキテクチャ(MySQLなど):
[コンピュート + ストレージ] ← 全部一体。接続先は1つ

Aurora:
[コンピュート: Writer]  ─┐
[コンピュート: Reader]  ─┼── [共有ストレージ]
[コンピュート: Reader]  ─┘

従来のDB → コンピュートとストレージが一体で、接続先は1つ
Aurora → コンピュートが複数台に分かれ、ストレージを共有する

Auroraの場合コンピュートが複数台あるので「どれに繋ぐか」を指定する仕組みが必要に
→ エンドポイントの出番

Aurora のエンドポイント

エンドポイント 接続先 用途
クラスターエンドポイント Writer 書き込み&読み取り
リーダーエンドポイント Reader(ラウンドロビン) 読み取り専用の負荷分散
インスタンスエンドポイント 特定の1台 デバッグ等

Writer は1台、Reader は複数台
書き込みは Writer に、読み取りは Reader に振り分ければ負荷を分散できる
(一般的なWebアプリケーションでは読み取りがクエリの大半を占めるため、読み取り側を複数台にして負荷を分散している。Writer を1台に集約することで、書き込みの順序と一貫性を保証することもできる。)

ProxySQL による読み書き分離

なぜ ProxySQL を挟むのか

アプリケーションが Writer / Reader を意識してエンドポイントを切り替えるのは面倒だし、接続管理も複雑になる。

そこで ProxySQL を間に置く。

アプリ → LB → ProxySQL → Writer / Reader

こうすることで、アプリからは 1つの接続先 に見える。ProxySQL がクエリの種類を判定して、裏側で適切なエンドポイントに振り分ける。

振り分けのルール(簡略化)

  • INSERT / UPDATE / DELETE → Writer
  • SELECT → Reader
  • トランザクション中 → Writer に固定(ロック)

この「トランザクション中は Writer に固定」が、今回のDB接続エラー問題のポイント!

エラーが起きる仕組み

トランザクション内での衝突

  1. アプリが BEGIN(トランザクション開始)する
  2. ProxySQL は「トランザクション中だから Writer に固定しよう」と判断
  3. トランザクション内で SELECT が来る
  4. ProxySQL は「SELECT だから Reader に送りたい」けど「この接続は Writer にロック済み」→ 衝突

具体例

こういうコードを考える:

ActiveRecord::Base.transaction do
  user = UserService.update!(           # ← INSERT/UPDATE (Writer)
    user: user,
    params: update_params
  )
  NotificationService.sync(
    user: user,                         # ← user を読み取り (SELECT)
    previous_email: previous_email
  )
end

@permissions = user.permissions         # ← さらに SELECT

更新処理のあとに、同じトランザクション内で読み取りが走っている。
SQL レベルで見ると:

BEGIN
  UPDATE users SET ...              → Writer
  SELECT * FROM permissions WHERE ...  → Reader に送りたいけどロック中 💥
COMMIT

という形でUPDATE(Writerに送る)の後にSELECT(Readerに送る)が来てDB接続が衝突してしまう。

なぜ「たまにしか」起きないのか

大半のリクエストでは問題なく通る。これが厄介なところで、エラーは ProxySQL の内部的な接続状態やタイミングによって発生する。

  • 接続プールの状態
  • 同時接続数
  • クエリの到達タイミング

つまり再現性が低い。ログを見ても「たまに出る謎のエラー」にしか見えない。

まとめ

  • Aurora はコンピュートとストレージが分離しており、Writer / Reader が別インスタンスになる
  • ProxySQL はクエリの種類に応じて Writer / Reader に振り分けるプロキシ
  • トランザクション中は Writer にロックされるが、SELECT の振り分けルールと衝突することがある
  • このエラーはタイミング依存で再現性が低く、flaky test や散発的なエラーとして表面化する

MySQLのような従来のDBとクラウド向けに設計されたAuroraのアーキテクチャの違いや、ProxySQLの仕組みについて理解するいい機会になりました。

Atrae Tech

Discussion