25 Dec 2022

Rails6 + GCP CloudSQL MySQL で Query Insights を使う際の Yak Shaving

GCP の CloudSQL には Query Insights というクエリパフォーマンス分析ツールがある。その中の一機能に、クエリが発行されたコントローラーやアクション別にタグを振り、タグ別にパフォーマンスを集計してくれるというものがある。クエリへのタグ付けには sqlcommenter というツールが公式に準備されている。この sqlcommenter の Rails6 以前への対応がいまいちで、導入に一工夫が必要だった。

Query Insights とは

  • クエリパフォーマンスを分析できる便利なツール
  • 例えば AWS の RDS でいうところの Performance Insights のようなもの
Query Insights を使用してクエリのパフォーマンスを向上させる https://cloud.google.com/sql/docs/mysql/using-query-insights より引用
  • 当初は Postgresql にしか対応していなかったが 2022-09-29 に MySQL 対応GA になった
  • アプリケーション側に手を入れなくても利用できるが、クエリにタグを埋め込むとそのタグごとに分析ができるという便利な機能もある
    • 次のようにそのクエリが発行されたコントローラーやアクションを SQL コメントに書き出すと、タグごとに集計された統計を見ることができるようになる
SELECT * from USERS /*action='run+this',
controller='foo%3',
traceparent='00-01',
tracestate='rojo%2'*/
Query Insights を使用してクエリのパフォーマンスを向上させる https://cloud.google.com/sql/docs/mysql/using-query-insights より引用

sqlcommenter

  • アプリケーションからのクエリコメントへのタグ情報埋め込みには sqlcommenter というツールを使うよう 公式ドキュメントから案内されている
  • sqlcommenter は有名所の Web フレームワーク・ORM には 対応していて、Rails・ActiveRecord もサポートされている
  • Rails の場合 は、sqlcommenter_rails というパッケージを導入すればよい
  • この sqlcommenter_rails の対応状況が微妙で、すんなりと導入できなかった
    • なお後述するが Rails7 以降ではこのような苦労をする必要はない

問題点

  • 一言でいうと sqlcommenter_rails は rubygems に公開されていない
    • sqlcommenter_rails は marginalia 本家の 未マージの PR に依存しているためだと思われる
    • marginalia の 出力フォーマットsqlcommenter の spec に合致せず、またフォーマットのカスタマイズができない作りになっている
    • 上記の PR でフォーマットをカスタマイズ可能にしようとしている
  • また他の依存パッケージである marginalia-opencensus も rubygmens に公開されていない
    • こちらはやろうと思えば公開できそうだが、単体で公開しても意味は無いので未対応なのだと思う
  • そのせいか、sqlcommenter_railsmarginalia-opencensus も License が明示されておらず、商用利用するにあたって懸念が残る

とりあえずの解決

  • sqlcommenter_rails は使わず、marginalia を直接導入して、必要な部分だけ自分でパッチを当てるのが手っ取り早かった
  • config/initializers/marginalia.rb で出したいタグの種別 (components) を指定する
    • spec では key が辞書順になるよう規定されているので sort している
    • このサンプルは controller, action だけを指定しているが、Query Insights は他にも framework, route, application, db_driver というタグにも対応している
# config/initializers/marginalia.rb
Marginalia::Comment.components = [:controller, :action].sort
# config/initializers/marginalia_patch.rb など
module Marginalia
  module Comment
    def self.construct_comment
      ret = String.new
      is_job = !self.send(:job).nil?
      self.components.each do |c|
        component_value = self.send(c)
        component_value = self.send(:job) if is_job && c == :controller  # Query Insights に集計してもらうため job の場合 controller キーでもジョブ名を出力する
        if component_value.present?
          quoted = "'#{component_value.gsub("'", "\\\\'")}'"
          ret << "#{c}=#{quoted},"
        end
      end
      ret.chop!
      ret = self.escape_sql_comment(ret)
      ret
    end
  end
end

Rails7 対応

OpenTelemetry

  • ちなみに sqlcommenter プロジェクトはもともと Google がホストしていたものだったが、現在では CNCF の OpenTelemetry に譲渡されているらしい
  • よって sqlcommenter_rails 自体を改善したい場合はこちらのルートから進んだ方がおそらく良い
  • 前述のように Rails7 では対応済みなので、Rails ではない Ruby のプロジェクトでくらいしか用途がなさそうだが
  • 現状の OpenTelemetry 側の対応状況はちょっとよくわからないが、おそらく解決はしていなそうだった

PR

Observability Engineering (English Edition)
英語版 Charity Majors (著), Liz Fong-Jones (著), George Miranda (著) 形式: Kindle版