RailsのパフォーマンスをPrometheusで測定する(翻訳)

概要

原著者の許諾を得て翻訳・公開いたします。

RailsのパフォーマンスをPrometheusで測定する(翻訳)

Discourseで行っているRailsのinstrumentation(測定)方法を皆さまにご紹介します。


私をフォローいただいている方は以下のようなグラフが投稿されているのをたまに目にします。

rails_performance_graph_captured

この種のinstrumentationとグラフ化をNewRelicSkylightに任せていることがよくありますが、私たちほどの規模になると、instrumentationやグラフ化や監視をローカルで行うことには絶大なメリットがあります。私たちはホスティングをビジネスとして行っており、ホスティングは私たちの業務の中心を占める重要なものだからです。

測定値の収集や警告の通知の分野では、ここ数年Prometheusが主要な選択肢として台頭しています。その一方、Railsを使っている人々はみな大変な苦労をして測定値を抽出しています。

公式のRuby向けPrometheusクライアントのIssue #9は3年前にオープンされましたが、当分「solved」になる見込みはなさそうです。

このissueの根本的な問題は、GraphiteStatsdが測定値のプルを概念の中心に置いているのは逆に、Prometheusが測定値のプッシュを中心に置いていることです。

これはつまり、公開したいすべての測定値を集める単一のHTTPエンドポイントを自分で提供しなければならないということです。このため、UnicornやPumaやPassengerのように複数のプロセスにforkして使うことの多いWebサーバーでは特に複雑になってしまいます。セキュアな/metricsエンドポイントを単純にアプリに実装すると、forkしたプロセスのどれがリクエストを受け付けるのかをまったく保証できなくなってしまいます。「cross fork」で集約しなければ、単一プロセスの測定値をランダムに得ることしかできません。これはいくらなんでも不便です。

しかもPrometheusでは、どの測定値をどのようにコレクションすべきかを調べるのが難しく、自分が欲しい設定が判明するまで軽く数週間はかかることもあります。

この重大な問題をDiscourseで解決できたので、私はしばらく時間を使ってこれをパターンとして取り出しました。

prometheus_exporterを導入する

必要な機能は、Discourseのprometheus_exporter gemですべて手に入ります。

  1. 拡張可能な「コレクタ」を用いて、1台のコンピュータ上のマルチプロセスについても測定値をシングルプロセスで集約できる。
  2. ゲージ、カウンタ、サマリーの測定を実装している。
  3. デフォルトのinstrumentationをアプリに簡単に追加できる。
  4. forkしたプロセスとマスターコレクタの間のトランスポートチャンネルが非常に高効率かつ頑健。マスターコレクタがHTTP経由で測定値を収集するが、チャンクトエンコーディングでオーバーヘッドを低減したことで単一セッションで極めて膨大な測定値を収集できる。
  5. 測定値は専用ポート経由でPrometheusに公開されるので、HTTPエンドポイントを節約できる。
  6. 完全に拡張可能であり、すべての機能を使うことも、用途に応じてごく一部の機能だけを使うこともできる。

Railsアプリでの最小限の測定の実装例

Gemfileに以下を追加します。

gem 'prometheus_exporter'
# config/initializers/prometheus.rb
if Rails.env != "test"
  require 'prometheus_exporter/middleware'

  # HTTPステータスやタイミングなどをリクエストごとに統計をレポートする
  Rails.application.middleware.unshift PrometheusExporter::Middleware
end

この時点でWebのinstrumentationが行われ、あらゆるリクエストでSQL/Redis/Total timeがトラッキングされます(PostgreSQLを使うことが前提)。

rails_99th_percentile_captured

プロセスごとの統計も、たとえば次のように見ることができます。

rails_object_allocation_rate_captured

rails_ruby_heap_stats_captured

# config/initializers/prometheus.rb
if Rails.env != "test"
  require 'prometheus_exporter/instrumentation'

  # RSSやGC情報などの基本的なプロセス統計をレポートする
  # type: "master"はマスタープロセスを測定していることを示す
  PrometheusExporter::Instrumentation::Process.start(type: "master")
end
# Unicorn/Puma/Passengerでは、fork後に必ず新しいプロセスinstrumenterを実行すること
after_fork do
  require 'prometheus_exporter/instrumentation'
  PrometheusExporter::Instrumentation::Process.start(type:"web")
end

Sidekiqの統計を見たければ次のように表示できます。

image

Sidekiq.configure_server do |config|
   config.server_middleware do |chain|
      require 'prometheus_exporter/instrumentation'
      chain.add PrometheusExporter::Instrumentation::Sidekiq
   end
end

最後に、全プロセスに渡るグローバル統計もコレクションできます。

image

そのためには「type collector」を導入します。

# lib/global_type_collector.rb
unless defined? Rails
  require File.expand_path("../../config/environment", __FILE__)
end

require 'raindrops'

class GlobalPrometheusCollector < PrometheusExporter::Server::TypeCollector
  include PrometheusExporter::Metric

  def initialize
    @web_queued = Gauge.new("web_queued", "Number of queued web requests")
    @web_active = Gauge.new("web_active", "Number of active web requests")
  end

  def type
    "app_global"
  end

  def observe(obj)
    # 何もしない(測定値がアプリから転送されているかどうかのチェックにのみ利用)
  end

  def metrics
    path = "/var/www/my_app/tmp/sockets/unicorn.sock"
    info = Raindrops::Linux.unix_listener_stats([path])[path]
    @web_active.observe(info.active)
    @web_queued.observe(info.queued)

    [
      @web_queued,
      @web_active
    ]
  end
end

以上の準備が整ったら、(productionで監視するプロセス内で)コレクタを実行する必要があります。runitsupervisordsystemdなどどれでも使えます。ちなみに私のはrunitです。

bundle exec prometheus_exporter -t /var/www/my_app/lib/global_app_collector.rb

続いてさまざまなオンラインガイドを参考にPrometheusと、かの有能なGrafanaをセットアップすれば、素敵なグラフを表示できます。

参考までに本記事では、とある社内アプリで私が測定した(Gist)生測定値のフィードの様子の一部をサンプルとして使いました。

本記事がお役に立てば幸いです。皆さまのinstrumentationが成功しますように。

関連記事

PrometheusでDockerホスト + コンテナを監視してみた

デザインも頼めるシステム開発会社をお探しならBPS株式会社までどうぞ 開発エンジニア積極採用中です! Ruby on Rails の開発なら実績豊富なBPS

この記事の著者

hachi8833

Twitter: @hachi8833、GitHub: @hachi8833 コボラー、ITコンサル、ローカライズ業界、Rails開発を経てTechRachoの編集・記事作成を担当。 これまでにRuby on Rails チュートリアル第2版の半分ほど、Railsガイドの初期翻訳ではほぼすべてを翻訳。その後も折に触れてそれぞれ一部を翻訳。 かと思うと、正規表現の粋を尽くした日本語エラーチェックサービス enno.jpを運営。 実は最近Go言語が好き。 仕事に関係ないすっとこブログ「あけてくれ」は2000年頃から多少の中断をはさんで継続、現在はnote.muに移転。

hachi8833の書いた記事

週刊Railsウォッチ

インフラ

ActiveSupport探訪シリーズ