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

概要 原著者の許諾を得て翻訳・公開いたします。 英語記事: Instrumenting Rails with Prometheus 原文公開日: 2018/02/02 著者: Sam Saffron — Discourseの共同創業者であり、Stack Overflowでの開発経験もあります。 RailsのパフォーマンスをPrometheusで測定する(翻訳) Discourseで行っているRailsのinstrumentation(測定)方法を皆さまにご紹介します。 私をフォローいただいている方は以下のようなグラフが投稿されているのをたまに目にします。 この種のinstrumentationとグラフ化をNewRelicやSkylightに任せていることがよくありますが、私たちほどの規模になると、instrumentationやグラフ化や監視をローカルで行うことには絶大なメリットがあります。私たちはホスティングをビジネスとして行っており、ホスティングは私たちの業務の中心を占める重要なものだからです。 測定値の収集や警告の通知の分野では、ここ数年Prometheusが主要な選択肢として台頭しています。その一方、Railsを使っている人々はみな大変な苦労をして測定値を抽出しています。 公式のRuby向けPrometheusクライアントのIssue #9は3年前にオープンされましたが、当分「solved」になる見込みはなさそうです。 このissueの根本的な問題は、GraphiteやStatsdが測定値のプルを概念の中心に置いているのは逆に、Prometheusが測定値のプッシュを中心に置いていることです。 これはつまり、公開したいすべての測定値を集める単一のHTTPエンドポイントを自分で提供しなければならないということです。このため、UnicornやPumaやPassengerのように複数のプロセスにforkして使うことの多いWebサーバーでは特に複雑になってしまいます。セキュアな/metricsエンドポイントを単純にアプリに実装すると、forkしたプロセスのどれがリクエストを受け付けるのかをまったく保証できなくなってしまいます。「cross fork」で集約しなければ、単一プロセスの測定値をランダムに得ることしかできません。これはいくらなんでも不便です。 しかもPrometheusでは、どの測定値をどのようにコレクションすべきかを調べるのが難しく、自分が欲しい設定が判明するまで軽く数週間はかかることもあります。 この重大な問題をDiscourseで解決できたので、私はしばらく時間を使ってこれをパターンとして取り出しました。 prometheus_exporterを導入する 必要な機能は、Discourseのprometheus_exporter gemですべて手に入ります。 拡張可能な「コレクタ」を用いて、1台のコンピュータ上のマルチプロセスについても測定値をシングルプロセスで集約できる。 ゲージ、カウンタ、サマリーの測定を実装している。 デフォルトのinstrumentationをアプリに簡単に追加できる。 forkしたプロセスとマスターコレクタの間のトランスポートチャンネルが非常に高効率かつ頑健。マスターコレクタがHTTP経由で測定値を収集するが、チャンクトエンコーディングでオーバーヘッドを低減したことで単一セッションで極めて膨大な測定値を収集できる。 測定値は専用ポート経由でPrometheusに公開されるので、HTTPエンドポイントを節約できる。 完全に拡張可能であり、すべての機能を使うことも、用途に応じてごく一部の機能だけを使うこともできる。 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を使うことが前提)。 プロセスごとの統計も、たとえば次のように見ることができます。 # 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の統計を見たければ次のように表示できます。 Sidekiq.configure_server do |config| config.server_middleware do |chain| require ‘prometheus_exporter/instrumentation’ chain.add PrometheusExporter::Instrumentation::Sidekiq end end 最後に、全プロセスに渡るグローバル統計もコレクションできます。 そのためには「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で監視するプロセス内で)コレクタを実行する必要があります。runitやsupervisordやsystemdなどどれでも使えます。ちなみに私のはrunitです。 bundle exec prometheus_exporter -t /var/www/my_app/lib/global_app_collector.rb 続いてさまざまなオンラインガイドを参考にPrometheusと、かの有能なGrafanaをセットアップすれば、素敵なグラフを表示できます。 参考までに本記事では、とある社内アプリで私が測定した(Gist)生測定値のフィードの様子の一部をサンプルとして使いました。 本記事がお役に立てば幸いです。皆さまのinstrumentationが成功しますように。 関連記事 PrometheusでDockerホスト + コンテナを監視してみた