💼

既存のRails appにsolid_queueを入れてDBでJobを実行できるようにした

に公開

Rails8から導入されたsolid_*の1つであるsolid_queueを既存のRails appにいれることでActiveJobをRedisではなくDB(今回はSQLite)で実行できるようにします
https://github.com/rails/solid_queue

以下の状況でsolid_queueを導入してみました

Rails 8.0.1
Ruby 3.4.1
cssbundling-rails 1.4.1
jsbundling-rails 1.3.1
solid_queue 1.1.2

導入

まずはGemfileに記述してインストールします

Gemfile
gem 'solid_queue'

その後Gemのインストール、solid_queueの初期ファイルの作成をします。

$ bundle
$ bin/rails solid_queue:install
      create  config/queue.yml
      create  config/recurring.yml
      create  db/queue_schema.rb
      create  bin/jobs
        gsub  config/environments/production.rb

4つのファイルと1つのファイルの書き換えが実行されました。

ファイルの中身

config/queue.yml ではスレッド数やプロセス数などの設定を環境ごとに行うことが出来ます。
config/recurring.yml はデフォルトでコメントアウトされていますが環境ごとに定期的に実行するJobやコマンドを指定することが出来ます。Jobに対して引数やいつ実行するかを指定出来て便利だなと思いました。
db/queue_schema.rb でsolid_queueを実行するためのDBのschemaが記述されています。
以下のようにversion指定もあるので今後solid_queueがアップデートされてDBの構造が変わった際はここもバージョンアップされていくのかなと思います。

db/queue_schema.rb
+ ActiveRecord::Schema[7.1].define(version: 1) do
...
+ end

bin/jobs ではsolid_queueをcli経由で起動しています。内容的にはこのような内容です。

bin/jobs
#!/usr/bin/env ruby

require_relative "../config/environment"
require "solid_queue/cli"

SolidQueue::Cli.start(ARGV)

config/environments/production.rbでは以下のようにadapterなどの設定がsolid_queueに置き換わっています。

config/environments/production.rb
# Replace the default in-process and non-durable queuing backend for Active Job.
+  config.active_job.queue_adapter = :solid_queue
+  config.solid_queue.connects_to = { database: { writing: :queue } }

Procfileで別プロセスで起動

既存のアプリ内ではProcfileを用いて複数プロセスを起動するようにしていたのでローカルで実行できるようにProcfile.devに以下のように書き込んでプロセスで実行できるようにします。

Procfile.dev
+ job: bin/jobs

pumaにpluginとしてsolid_queueを追加してrails serverと一緒に起動することも出来るそうです。
READMEのこちらにサンプルが書いてあります。
https://github.com/rails/solid_queue/blob/main/README.md?plain=1#L108-L111

database.ymlの設定

今回は別のsqliteファイルで実行したいのでconfig/database.ymlにも追加で記述します。

config/database.yml
development:
+  primary:
+    <<: *default
+    database: storage/development.sqlite3
+  queue:
+    <<: *default
+    database: storage/development_queue.sqlite3
+    migrations_paths: db/queue_migrate

これで準備が完了したので動作テストをしていきます。

動作テスト

ひとまずのJobなので簡単に作成します。

$ bin/rails g job TestJob
      invoke  rspec
      create    spec/jobs/test_job_spec.rb
      create  app/jobs/test_job.rb

TestJobの中身はこんな感じでとりあえず書いておきます。

app/jobs/test_job.rb
class TestJob < ApplicationJob
  queue_as :default

  def perform(*args)
    puts "Hello, SolidQueue! Args: #{args}"
  end
end

このあとbin/jobsを起動しておきます。自分の場合はProcfile.devに書いたのでbin/devでプロセスを起動しておきます。

$ bin/dev

そしてconsoleを通してJobをQueueに登録します。

$ bin/rails c
Loading development environment (Rails 8.0.1)
hoge-app(dev)> TestJob.perform_later("Argument")

こうすることでプロセスを起動したログで以下のような記述が確認出来ます。

00:00:00 job.1  | Hello, SolidQueue! Args: ["Argument"]

無事にSQLiteを通してJobを実行することができました🎉

perform_laterで実行出来ることも確認します。同様にconsoleで以下のようにして1分後に実行するようにQueueを登録します。

hoge-app(dev)> TestJob.set(wait: 1.minute).perform_later("Delayed")

そうすると1分後に以下のログが出力されます。

00:00:00 job.1  | Hello, SolidQueue! Args: ["Delayed"]

無事に動作確認が出来ました!

最後に

個人開発やスモールサイズのアプリなどでRedisを入れてJobを実行するのはオーバースペック感があったのですがDBを利用してJob実行出来るようになったのはとても便利だなと感じました。
またSQLiteを使えばFly.ioやrenderなどにデプロイするときにも同一machineの中で完結することが出来るので料金も抑えられるのが魅力的だなと感じました。
SQLiteだと競合しやすくなってしまうかなと心配でしたが別ファイルで実行出来るのであればその心配もなくなるでしょうし、いい選択肢が増えたなと感じました。
まだローカルでのみでしか動かしていないのでプロダクション環境で動かしたりFly.ioなどにデプロイしたときに何につまずくかも確認したいと思います。

Discussion