Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails関連

Rails: Mission Control Jobs gem README(翻訳)

概要

MITライセンスに基づいて翻訳・公開いたします。

rails/mission_control-jobs - GitHub

本文のライブラリ名はmission_control-jobsで統一しました。

参考: 週刊Railsウォッチ20240402: solid_queueとmission_control-jobsがRailsのリポジトリに追加された

Rails: Mission Control Jobs gem README(翻訳)

mission_control-jobs gemは、Active JobアダプタにRailsベースのフロントエンドを提供します。現在はResqueSolid Queueをサポートしています。mission_control-jobsの機能は、アダプタ自体が提供する機能にもよりますが、少なくとも、ジョブキューや現在のキューで待機中のジョブを確認したり、失敗したジョブを調べて再試行または破棄したりできます。

🔗 インストール方法

Gemfileに以下の行を追加します。

gem "mission_control-jobs"

続いて以下を実行します。

$ bundle install

🔗 基本的な設定

routes.rbファイルに以下を記述することで、mission_control-jobsのエンジンをアプリからアクセス可能な場所にマウントできます。

Rails.application.routes.draw do
  # ...
  mount MissionControl::Jobs::Engine, at: "/jobs"

設定はこれでおしまいです。たったこれだけで、ブラウザからmission_control-jobsのUIにアクセスして、既存のキューや、保留中のジョブ、さまざまなステータスのジョブを表示したり、失敗したジョブの破棄や再試行を行ったりできるようになります。

🔗 認証とベースコントローラクラス

デフォルトでは、mission_control-jobsのコントローラがホストアプリのApplicationControllerextendします。認証が強制されていない場合は、ブラウザで/jobsにアクセスすることで誰でもUIを表示できます。
アプリに何らかの認証機能を実装する必要が生じた場合は、以下のように別のクラスをmission_control-jobsのコントローラのベースクラスとして指定することで、実装をやりやすくできます。

Rails.application.configure do
  MissionControl::Jobs.base_controller_class = "AdminController"
end

または、環境設定ファイルやapplication.rbで以下を指定することも可能です。

config.mission_control.jobs.base_controller_class = "AdminController"

🔗 その他の設定項目

MissionControl::Jobsconfig.mission_control.jobsでは、上述のbase_controller_classに加えて以下の項目も設定できます。

logger
mission_control-jobsで利用したいロガーを指定します。デフォルトはActiveSupport::Logger.new(nil)(ログ出力しない)です。なお、Active JobのロガーやActive Jobのバックエンドで設定されているロガーは、これと別物なのでご注意ください。
delay_between_bulk_operation_batches
ジョブのdiscard allretry all(デフォルト)のような一括処理の実行中、バッチとバッチの間の待ち時間を指定します。デフォルトは0です。
adapters
mission_control-jobsで利用・extendするアダプタのリストを指定します。デフォルトでは、active_job.queue_adapterで設定したアダプタが使われます。
internal_query_count_limit
アダプタでカウントクエリを制限する必要がある場合、カウントするレコード数の上限を指定します。この値を超えるとINFINITYが返されるようになります。これによりカウントクエリが高速化されます。デフォルトは500,000です。
scheduled_job_delay_threshold
スケジュールされたジョブが「遅延している」と判断するまでの時間を指定します。デフォルトは1.minuteです(この場合、scheduledステータスがスケジュールされた時刻から1分以上経過してもステータスが変わらない場合は、遅延しているとみなされます)。
show_console_help
コンソールヘルプを表示するかどうかを指定します(デフォルトはtrue)。コンソールのヘルプメッセージを表示したくない場合はfalseに設定します。

このライブラリは、クエリインターフェイスと以下の設定を用いてActive Jobを拡張します。

config.active_job.default_page_size
Active Jobが背後のアダプタにクエリを送信するときの内部バッチサイズと、上で定義した一括操作のバッチサイズを指定します(デフォルトは1000)。

🔗 指定可能なアダプタ

Resque
キューの一時停止は、プロジェクトにresque-pauseがインストール済みの場合にのみ可能です。
Solid Queue
必須バージョン: 0.9以上

🔗 高度な設定

私たちがmission_control-jobsを構築したときは、複数のアプリのバックエンドを1つのアプリから管理することで、監視やアラートなどアプリ関連のあらゆる作業を一元化することを構想していました。

しかしアプリによっては2つ以上のデータセンターで実行され、それぞれが異なるRedis設定に沿ってさまざまなResqueインスタンスを実行することもありえます。このため、「複数アプリのサポート」と「1アプリで複数アダプタの利用のサポート」を両方追加してあります。

私たちはResqueからSolid Queueに移行したので、単一のデータセンターでキュー管理用アプリ内からmission_control-jobsを実行する場合であっても、mission_control-jobsがサポートする両方のアダプタを管理する必要があります。

mission_control-jobsの上述の基本設定に追加設定を行わない場合は、「1つのアプリ」と「active_job.queue_adapterで設定済みの1つのサーバー」という構成で設定されます。

複数アダプタをサポートしたい場合は、上述したadapters設定で以下のようにアダプタをmission_control-jobsに追加する必要があります。

config.mission_control.jobs.adapters = [ :resque, :solid_queue ]

続いて、複数アプリまたは複数サーバー向けの設定を行います。この設定は以下のようにイニシャライザファイルに書けます(これはテスト用のダミーアプリから取り出したものです)。

require "resque"
require "resque_pause_helper"

require "solid_queue"

Resque.redis = Redis::Namespace.new "#{Rails.env}", redis: Redis.new(host: "localhost", port: 6379)

SERVERS_BY_APP = {
  BC4: %w[ resque_ashburn resque_chicago ],
  HEY: %w[ resque solid_queue ]
}

def redis_connection_for(app, server)
  redis_namespace = Redis::Namespace.new "#{app}:#{server}", redis: Resque.redis.instance_variable_get("@redis")
  Resque::DataStore.new redis_namespace
end

SERVERS_BY_APP.each do |app, servers|
  queue_adapters_by_name = servers.collect do |server|
    queue_adapter = if server.start_with?("resque")
      ActiveJob::QueueAdapters::ResqueAdapter.new(redis_connection_for(app, server))
    else
      ActiveJob::QueueAdapters::SolidQueueAdapter.new
    end

    [ server, queue_adapter ]
  end.to_h

  MissionControl::Jobs.applications.add(app, queue_adapters_by_name)
end

この設定例は、BC4とHEYという2つの異なるアプリを対象としており、それぞれにサーバーが2つずつ存在しています。BC4アプリにはそれぞれ設定の異なるResqueサーバーが2つ、HEYアプリにはResqueサーバーが1つとSolid Queueサーバーが1つずつ存在しています。

現時点でmission_control-jobsでサポートされているSolid Queue設定は1種類だけですが、今後複数のSolid Queueバックエンド(かつ異なるデータベースをサポート)を利用可能にする計画があります(#35)。

ResqueからSolid Queueへ、またはその逆の移行を行う場合は、以下のようにResqueとSolid Queueを同時に設定できます。

queue_adapters_by_name = {
  resque: ActiveJob::QueueAdapters.lookup(:resque).new, # これによってResque.redisをRedisクライアントとして使う
  solid_queue: ActiveJob::QueueAdapters.lookup(:solid_queue).new
}

MissionControl::Jobs.applications.add("hey", queue_adapters_by_name)

複数アプリや複数サーバーが設定されていれば、以下のようにメニューで切替可能になります。

🔗 基本的なUI利用法

既に述べたようにアダプタごとに機能が異なっているため、mission_control-jobsで利用できる機能も、利用するアダプタごとに異なります。
UI画面では、キューやジョブの表示や、失敗したジョブの破棄やリトライの他にも、さまざまなステータスの表示、キュー名やジョブクラス名を指定した絞り込み(今後フィルタを増やす構想もあります: #30)、キューの一時停止と再開、どのジョブがどのワーカーで実行されているかの確認、特定のジョブやワーカーのチェックといった、利用中のアダプタでサポートされている機能が使えます。

  • Queuesタブ(デフォルト)

  • In-proggress jobsタブ

  • Workersタブ

  • 単一ジョブの詳細表示

  • 単一ワーカーの詳細表示

🔗 コンソールヘルパー、スクリプティング、多数のジョブセットの処理

mission_control-jobsは、上述のWeb UIに加えて、アプリケーションとアダプタを切り替えられる軽量のコンソールヘルパーも提供されています。Web UIでは、破壊的になる可能性がある一部の操作(例: 失敗していないジョブの削除)は利用できないようになっています(ただし将来変更される可能性があります)。
コンソールでは、自分が行おうとしている操作を十分理解していれば、そうした操作をいつでも実行できます。

「Web UIでは管理しきれない膨大なジョブセットを扱う」「問題解決、クリーンアップ、データ移行用のスクリプトを書く」といった操作が必要になる場合があります。こういうときは、Active Jobを拡張したコンソールヘルパーとクエリAPIが役に立ちます。

まず、Railsコンソールに接続すると以下のような新しいメッセージが表示されます。

 bin/rails c


Type 'jobs_help' to see how to connect to the available job servers to manage jobs

jobs_helpと入力すると、アプリケーションとアダプタを切り替える方法がわかりやすく表示されます。

>> jobs_help
You can connect to a job server with
  connect_to "<app_id>:<server_id>"

Available job servers:
  * bc4:resque_ashburn
  * bc4:resque_chicago
  * hey:resque
  * hey:solid_queue

続いて以下を実行します。

>> connect_to "hey:solid_queue"
Connected to hey:solid_queue

これで、そのアダプタにあるジョブをクエリしたり操作したりする準備が整いました。クエリの例をいくつか示します。


# すべてのジョブを表示する ActiveJob.jobs # 失敗したジョブをすべて表示する ActiveJob.jobs.failed # some_queueにある保留中のジョブをすべて表示する ActiveJob.jobs.pending.where(queue_name: "some_queue") # 指定したジョブクラスの、失敗したジョブをすべて表示する ActiveJob.jobs.failed.where(job_class_name: "SomeJob") # 指定したジョブクラスにlimitとoffsetを指定して、保留中のジョブをすべて表示する ActiveJob.jobs.pending.where(job_class_name: "SomeJob").limit(10).offset(5) # (以下のステータスはアダプタでサポートされている前提) # 指定のジョブクラスで、「スケジュール済み」「実行中」「完了済み」ジョブをすべて表示する All scheduled/in-progress/finished jobs of a given class ActiveJob.jobs.scheduled.where(job_class_name: "SomeJob") ActiveJob.jobs.in_progress.where(job_class_name: "SomeJob") ActiveJob.jobs.finished.where(job_class_name: "SomeJob") # (ワーカーを指定してフィルタする機能がアダプタでサポートされている前提) # 指定のワーカーで実行中のジョブをすべて表示する ActiveJob.jobs.in_progress.where(worker_id: 42)

一括操作の例をいくつか示します。

# すべてのジョブをリトライする(失敗したジョブに対してのみ有効)
ActiveJob.jobs.failed.retry_all

# 指定のジョブクラスですべてのジョブをリトライする(失敗したジョブに対してのみ有効)
ActiveJob.jobs.failed.where(job_class_name: "SomeJob").retry_all

# 失敗したジョブをすべて破棄する
ActiveJob.jobs.failed.discard_all

# 指定のジョブクラスで保留中のジョブをすべて破棄する
ActiveJob.jobs.pending.where(job_class_name: "SomeJob").discard_all
# または指定のキューで保留中のジョブをすべて破棄する
ActiveJob.jobs.pending.where(queue_name: "some-queue").discard_all

これらの一括処理をコンソールで実行すると、バッチとバッチの間に2秒の待ち時間が発生します。この待ち時間は以下のようにdelay_between_bulk_operation_batchesで設定できます。

MissionControl::Jobs.delay_between_bulk_operation_batches = 5.seconds

🔗 貢献について

貢献に関心を持っていただきありがとうございます!このアプリをローカルで実行するには、以下のコマンドを実行するだけで済みます。

bin/setup

これによって、多数のジョブがseedとして読み込まれます。

mission_control-jobsには単体テスト、機能テスト、システムテストがあります。システムテストを実行するには、ChromeDriverをインストールしておく必要があります。
続いて以下のコマンドでテストを実行できます。

bin/rails test test/system

🔗 ライセンス

このgemは、MIT Licenseの条項に基づいてオープンソースとして利用可能です。

関連記事

Solid Queue README -- DBベースのActive Jobバックエンド(翻訳)


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。