Upgrade to Pro — share decks privately, control downloads, hide ads and more …

未知の原因によるDB負荷の上昇とその対応プロセス

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers.

 未知の原因によるDB負荷の上昇とその対応プロセス

Avatar for Taiki Shimizu

Taiki Shimizu

June 02, 2024
Tweet

Other Decks in Technology

Transcript

  1.   freee のデータベース利⽤状況 • RDBMS には Amazon Aurora for MySQL

    を採⽤している ◦ Aurora2 (MySQL 5.7互換)とAurora3 (MySQL 8.0互換) が混在 ◦ Writer/Reader 構成 • 各プロダクトは原則として 1 つ以上の RDBMS を持つ ◦ 本番環境で数⼗クラスタが稼働中
  2.   今回 DB 負荷が上昇したプロダクトの構成 • Rails 6 製のアプリケーション ◦ ORM

    として ActiveRecord を利⽤ • 接続先の DB が複数ある ◦ いずれも Aurora2 (MySQL 5.7互換) • ワークロードとして書き込みが多いことに加え Writer インスタンスに流れている SELECT クエリも多い
  3.   Performance Insights によるモニタリング • Performance Insights の有効化で可視化されるもの(⼀部) ◦ DBLoad

    とその内訳 ◦ 負荷が⼤きいクエリの統計情報 • 負荷の指標としては DBLoad を重要視している ◦ アクティブなセッションの同時実⾏数の平均 ◦ vCPU 数を超えると性能が劣化し始める傾向がある
  4.   • 内部的な処理(待機イベント)ごとに使われている時間 ◦ 待機イベントは MySQL の Performance Schema の

    イベントに対応 ◦ 例: synch/cond/innodb/row_lock_wait が増えていると、 ⾏ロック待ちに時間が使われていることが分かる DBLoad の内訳
  5.   • 毎年同時期にリクエストが増加するイベント ◦ 2-3 ヶ⽉ほどかけて徐々に増え、 さらに期間最後の 1 週間程で急激に負荷が増える •

    事前にキャパシティプランニングを⾏いリソースを増強 ◦ 過去の傾向をもとに DB もインスタンスクラスを上げていた 季節性イベント イベント期間最終⽇にかけて増え続ける qps の様⼦
  6.   • CPU やメモリなどリソース的な観点では問題がなかった • DBLoad のベース部分が⼤きくなっていた ◦ イベントの性質上書き込みが増えるため、 redo_log_flush

    イベントが DBLoad を押し上げていた ◦ vCPU を超える時間はクエリが遅くなっていたが、 今回の事象ほどの性能劣化を引き起こすレベルではない 原因調査
  7.   • 特徴的だった待機イベント fil_space_latch に注⽬ ◦ Aurora や MySQL のドキュメントに説明はなかったため、

    mysql-server のソースコードや AWS サポートとのやりとり などを通して情報を収集した • fil_space_latch は内部⼀時テーブル作成時に発⽣する ラッチ獲得に関するイベントらしいことがわかってきた ◦ Performance Insights で上位の SQL にも 内部⼀時テーブルを作成するクエリが多く⼊っていた 原因調査
  8.   • MySQL ではクエリの中間結果を記録するため 暗黙的に内部⼀時テーブルが作成されることがある • グローバル⼀時テーブルスペース ◦ 実体としてはスレッド間で共有されるファイル ◦

    内部⼀時テーブル作成でディスク使⽤時にアクセスされる • スペースへのアクセス時にラッチが獲得される ◦ 獲得待ちで記録されるイベントが fil_space_latch 内部⼀時テーブル作成時の動作 Thread (Session) Thread (Session) shared temporary tablespace ‧‧‧ SELECT * FROM t1 JOIN t2 ON t1.id = t2.t1_id ... ORDER BY t2.id SELECT * FROM t3 JOIN t4 ON t3.id = t4.t1_id ... ORDER BY t4.id
  9.   • 最⼩限のテストケースを使った実験設計 ◦ 内部⼀時テーブルを作るサンプルクエリを作成 ▪ JOIN と ORDER BY

    を組み合わせたクエリ ◦ mysqlslap を利⽤してクエリを並列に実⾏ • fil_space_latch が急激に増えた後 DBLoad が徐々に下がる 様⼦が再現して仮説を確認することができた 仮説検証のための再現実験
  10.   • 緊急度の⾼さから組織横断的に並⾏で対策を打っていった ◦ DB パラメータの調整 ◦ アプリケーションコードの修正 ◦ ActiveRecord

    の対応 組織横断で並⾏に対策を実施 DB Product プロダクト チーム プロダクト チーム ‧‧‧ ‧‧‧ ‧‧‧ DBRE 基盤チーム ライブラリ‧ ミドルウェア ‧‧‧ アプリケーションコードの 修正 ActiveRecord の対応 DB パラメータの調整
  11.   • tmp_table_size (max_heap_table_size) を上げる ◦ 各セッションで内部⼀時テーブルに使うメモリの最⼤値で、 これを超えるとディスクに書き出し始める ◦ OOM

    を避けるためセッション数やメモリ残量から 値を⾒積もり段階的に上げていった • fil_space_latch に対する効果を適⽤前に確認 ◦ 先述の検証で作成したテストケースを利⽤できた DB パラメータの調整
  12.   • 内部⼀時テーブル作成数が多いクエリを DBRE でリストアップ • プロダクトチーム側で可能なものは修正 ◦ クエリを Reader

    インスタンスに向ける ◦ 内部⼀時テーブルを作成しないようにクエリを修正 アプリケーションコードの修正
  13.   • 内部⼀時テーブルを作成するクエリで最も多かったのは SHOW COLUMNS ... などスキーマ情報を取得するもの ◦ ActiveRecord がモデル構築のため

    DB コネクション作成時に 発⾏していた ◦ MySQL5.7 の場合これらのクエリは必ずディスクを使って⼀ 時テーブルを作成するため、 tmp_table_size を変えても効 果がなかった ActiveRecord の対応
  14.   • 事前にスキーマ情報をダンプしたファイルを アプリケーションに読ませてクエリ発⾏をなくす ◦ Rails の機能としても存在するが、対象プロダクトで使われ ていた Rails 6

    では MultiDB に未対応だった ◦ ActiveRecord に Monckey Patch をあてることで解決した • 結果的にはこの対応による効果が最も⼤きく、 問題の fil_space_latch イベントは発⽣しなくなった ActiveRecord の対応 内部⼀時テーブルの作成数が急激に下がる様⼦
  15.   継続的な DB 改善活動の強化 • DB のパフォーマンス改善が進みづらい課題があった ◦ 関わる開発者が多い DB

    はオーナーシップが弱くなりやすい ◦ DBRE で課題を⾒つけても、アプリケーションコードに直接 ⼿を⼊れるのは効率が悪い • DB の運⽤やパフォーマンスの改善を推進するチームが 今回の問題が起きたプロダクトで発⾜ ◦ プロダクト内の各機能の開発チームからメンバーが集合 ◦ DBRE の知⾒と組み合わせて DB の改善を進め始めている
  16.   • DBLoad 全体の⼤幅な減少 ◦ 書き込み中⼼のワークロードであったため⼤きな割合を占め ていた redo_log_flush の処理の性能が向上した ◦

    ピークタイム同⼠の⽐較で約 2/3 まで減少した • 内部⼀時テーブルのディスク使⽤率の減少 ◦ 内部⼀時テーブル作成に関する実装の変更 ◦ ディスクを使いがちだったメタデータへのアクセスが改善 Aurora2 から Aurora3 への移⾏ Aurora2(redo_log_flush がトップ) Aurora3(redo_log_flush がトップではなくなった)
  17.   まとめ • 季節性イベントによる負荷の上昇 ◦ Rails と MySQL を組み合わせたアプリケーションで 同時実⾏性が⾼い場合に発⽣する問題

    • 収束までの対応プロセス ◦ 再現実験による検証を⾏い原因を特定 ◦ チーム横断で問題に取り組み早期に収束 • 問題収束後の取り組み ◦ 継続的な DB 改善活動の強化 ◦ Aurora3 への移⾏