🦇

Hasura, PostgreSQL, MySQL対応の速度改善ツールを作りました - GravityR

2022/10/22に公開

はじめに

DBが遅い原因の多くはインデックスの作り忘れです。
サーバーの性能アップやパラメータ変更の効果も大きいですが、まず最初に検討するべきはインデックスでしょう。
EXPLAINの結果をにらみながら、効果のありそうな場所を探します。

ただ、厄介なのはEXPLAINの結果が読みづらいことです。
EXPLAINの読み方を説明している本やサイトはいくつもありますが、EXPLAINを使う機会が少ないため、読める人が限られた、職人芸に近い技術になっています。

なので、EXPLAINを読まなくてもインデックスを作れるツールをGoとSvelteで作りました。
https://github.com/mrasu/GravityR

GravityRを使うと、下のようにEXPLAINをタイムライン形式にした図やインデックスの効果を表示したHTMLが作成されます。

紹介

GravityRはHasura、PostgreSQL、MySQLに対応しています。

実行ファイルをgithubに置いているので、リリースページからダウンロードすればすぐに使えます。

Hasuraを使っているなら、コマンドラインでGraphQLを入力すれば、インデックスの効果を表示するHTMLが作成されます。
その結果を使って、良さそうなインデックスを作ればいい、というわけです。

具体的には、

  1. HasuraのURLとシークレットを環境変数に設定する
# 例:
export HASURA_URL="http://localhost:8081"
export HASURA_ADMIN_SECRET="myadminsecretkey"
  1. GravityRを実行する
    検証したいqueryとvariablesを入力する
# 例:
gr db suggest hasura postgres --with-examine -o "output.html" -q "query MyQuery(\$email: String) {
  tasks(where: {user: {email: {_eq: \$email}}}) {
    user {
      name
    }
    description
  }
}
" --json-variables '{"email": "test1112@example.com"}'
  1. HTMLの内容から追加するインデックスを決める
    HTMLを開くと下のような表があるので、効果のあるインデックスがすぐわかります。
    この例では、tasksテーブルのuser_id列にインデックスを追加すればかなり速くなることがわかります。
  2. 「Copy as Curl」の内容を実行してインデックスを追加する
    Hasuraの場合、表の右側にあるボタンをクリックするとインデックスを追加するためのqueryがコピーできるので、それを使うとインデックスが作れます。

以上のステップをやれば終わりです。

PostgreSQLかMySQLを使う場合は、graphqlのクエリではなくSQLを使えば大丈夫です。
例えばMySQLなら下のようにすると、htmlが作られます。後は同じです。

export DB_ADDRESS=127.0.0.1 DB_USERNAME=root DB_PASSWORD=root DB_DATABASE=gravityr
gr db suggest mysql --with-examine -o "output.html" -q "SELECT name, t.description FROM users INNER JOIN tasks AS t ON users.id = t.user_id WHERE users.name = 'foo'"

内部動作

GravityRは、有効なインデックスを探すために実際にインデックスをDBに作っています。
内部では次のステップがあります。

  1. EXPLAINの実行
  2. SQLの理解とインデックス候補の列挙
  3. インデックスの効果検証
  4. HTMLの作成

1. EXPLAINの実行

EXPLAINを見なくてもいいとはいっても、やはりEXPLAINは見たくなるので表示しています。
今の実装ではEXPLAINの結果を使って何か上手いことをやっているわけではないのですが、代わりにEXPLAINを見やすくしています。

EXPLAINをビジュアライゼーションする場合、大抵はツリー状に表示しますが、GravityRではトレーシングの結果のようにタイムラインで表示しています。
実行の詳細を把握する時にはツリーで表示するのが正確ですが、遅い部分がぱっと見でわかるのはタイムラインかなと思って、タイムラインを使っています。

2. SQLの理解とインデックス候補の列挙

SQLをパースして、参照しているテーブルとカラムを把握します。
良いパーサーを探していたところ、CockrockDBがPostgreSQL互換で、TiDBがMySQL互換だったので、彼らが作っているパーサーを使っています。
それを使って、テーブル名の省略やサブクエリなどに対応しながら、粛々と名前解決しています。

こうして判明したテーブルとカラムの組み合わせから既存のインデックスの分を除くと、「ここにインデックスを貼れば効果があるのでは?」というインデックスが導けます。

3. インデックスの効果検証

インデックス候補が出たら、実際にインデックスを追加してクエリの速度を比較します。
ひとつ作っては計測して消し、別のインデックスを作る・・の繰り返しです。

4. HTMLの作成

ここまでの作業結果をまとめてHTMLを作ります。
HTMLを作るときにはSvelteとTypescriptを使っていて、javascriptとcssを作った後にGoの中で一つのHTMLにまとめています。
Hasura、PostgreSQL、MySQLのどれでも、ほとんど同じ内容を出しますが、EXPLAINの形式など微妙に違う場所があるので、その部分は各部分にあわせてsvelteの中で分岐しています。

使ってください!

ということで、速度改善のためにGravityRを作ったという話でした。
この記事ではインデックスを自動的に作る方法について書きましたが、AWSのPerformanceInsightsから遅いクエリを見つけたり、EXPLAINだけをする機能もあります。

これからは、APMのデータやメトリクスを使ったり、過去と比較したりしてSQLだけでなくアプリケーションの色々なボトルネックを見つけられればな、と思っています。
https://github.com/mrasu/GravityR

Discussion