📝

Sidekiq について調べてみた

2024/11/28に公開

業務で必要に迫られて Sidekiq について調べてみたのでそのメモ。

公式ドキュメント

https://github.com/sidekiq/sidekiq

Simple, efficient background processing for Ruby.
Sidekiq uses threads to handle many jobs at the same time in the same process. It does not require Rails but will integrate tightly with Rails to make background processing dead simple.

Ruby のためのシンプルで効率的なバックグラウンドプロセスです。Sidekiq は同じプロセス内でスレッドを使用してたくさんのジョブを一度に処理することができます。Sidekiq は Rails を必要としませんが、バックグラウンドプロセスをとても簡単にするために、Rails と緊密に連携します。
という感じ。dead simple という言い回しは知らなかった。英語の勉強にもなった。

このあたりは読んだ。

頭の整理

バックグラウンドプロセス実行のために必要な 3 つのもの

The Basics から。

1. Client

バックグラウンドで処理するための job を作る。

MyWorker.perform_async("Alice")

job は JSON 文字列として Redis に enqueue される。引数はシンプルな JSON datatype にする。

2. Redis

job のデータストレージ。

3. Server

Redis から job を dequeue する。その job に基づいて worker クラスを初期化し perform メソッドを実行する。

job と worker

ChatGPT さまさまmm

job

  • 定義:
    Sidekiq で queue に登録されたタスクの単位。

    • enqueue されてから dequeue され実行されるまで、job として扱われる。
    • 実際には、job は Redis に保存されるデータ構造として存在する。
  • 構成要素:
    job には以下のような情報が含まれる:

    • worker クラス名: 実際にその job を処理する worker クラス。
    • 引数: job を実行する際に必要なデータ。
    • メタデータ: ジョブの状態管理や再試行に必要な情報。
      • 例: jid(ジョブID)、キュー名、リトライ情報、スケジュールされた実行時刻(遅延ジョブの場合)など。
  • :

    {
      "class": "MyWorker",
      "args": ["example_arg"],
      "jid": "123abc",
      "queue": "default",
      "retry": true,
      "created_at": 1672512345
    }
    

    これは Redis に格納される job の形式で、Sidekiq が処理時に使用する。

worker

  • 定義:
    worker は、job を実行するためのロジックを持つ Ruby クラス。

    • Sidekiq::Worker モジュールをインクルードして実装する。
    • 各 worker クラスは具体的なタスク(ビジネスロジック)を定義する。
  • 役割:
    workerクラスは以下を定義する:

    1. タスク(ビジネスロジック): performメソッド内に処理内容を記述する。
    2. キュー設定: 使用するキュー名やリトライの設定を指定できる。
  • :

    class MyWorker
      include Sidekiq::Worker
    
      sidekiq_options queue: 'custom_queue', retry: 5
    
      def perform(arg)
        puts "Processing: #{arg}"
      end
    end
    

違いを整理

項目 job worker
役割 キューに登録されるタスクデータ job を実行するためのロジックを定義
データ形式 JSON 形式で Redis に格納 Ruby クラスとしてコード内に定義
実行場所 Redis 上で管理される Sidekiq プロセス内で実行される
内容 worker 名、引数、メタデータ performメソッドで具体的な処理を記述
関係性 worker クラスを元に生成される job を処理するために動作するクラス

例で理解する: job と worker の流れ

  1. worker の定義:

    class MyWorker
      include Sidekiq::Worker
    
      def perform(name)
        puts "Hello, #{name}!"
      end
    end
    
  2. job をエンキュー:

    MyWorker.perform_async("Alice")
    
    • ここで、MyWorker を基にした job が作成され、Redis に以下のようなデータが登録される:
      {
        "class": "MyWorker",
        "args": ["Alice"],
        "queue": "default",
        "retry": true
      }
      
  3. job が dequeue され実行:

    • Sidekiq プロセス が job をデキューし、MyWorker#perform("Alice")を実行する。

補足 1: job は worker のインスタンスではない

  • job はあくまでworker の実行指示情報です。worker クラスそのものが job になるわけではない。
  • Redis に保存されたjob を Sidekiq が読み込み、対応する worker クラスを呼び出すことでタスクが実行される。

補足 2: Client と Server の両方で worker クラスを実装する必要がある

正確には Client では job を enqueue さえできればよく、上記でいうところの MyWorker#perform("Alice") が実行されることはないので、その perform メソッドの実装は空でも問題はない。ただし、job 作成のために worker クラスは必要。ここは Sidekiq を知った当初ピンとこなかった。

正確な用語

Best Practices にきたら、こんなことが書いてある。

Use those terms: job class, thread, process or job. Dump "worker" from your vocabulary.

worker という言葉は使わないで、と。worker を job class と読み替えれば良いのかな。

冪等性とトランザクション

同じく Best Practices から。

Just remember that Sidekiq will execute your job at least once, not exactly once. Even a job which has completed can be re-run. Redis can go down between the point where your job finished but before Sidekiq has acknowledged it in Redis. Sidekiq makes no exactly-once guarantee at all.

Sidekiq は job を正確に 1 回だけ実行するという保証はしていませんとのこと。複数回実行されることもあり得るので、そうなっても問題とならないように job class の実装をしないといけない。大事なことだけど見逃しそう...。

懸念

Client と Server の実行環境を分離する場合、そのふたつの間でどうにかして同じ job class を参照できるようにしないといけない。

  • Client: job の enqueue のため。
  • Server: job を dequeue して、その job class の performメソッドを実行するため。

それを実現するための案

  1. コードレプリケーション: Client と Server でコピペにより同じ Job class を持つ。
  2. 共有ライブラリ: Job class を Gem 化して Client と Server で使う。
  3. Client をバックグラウンドプロセスのトリガーとなるアプリケーションから分離する。
    そもそも Client と Server を分けたいという動機は、アプリケーションとバックグラウンドプロセスを分けることによるリソースの効率化だったり、Server の拡張性の担保だったりするはず。そうであれば分離の境界は アプリケーション + Client | Server ではなくてアプリケーション | Client + Serverで良いかもしれない。ただそうするとアプリケーションから Client に対して何かしらの方法で job の作成をリクエストできるようにしないといけない。
  4. Client と Server 両方に同じコードをデプロイする。Server では Sidekiq プロセスも起動する。

どれを選択するかは、それぞれの pros/cons からトレードオフを判断して決めるのが良いかな。

Discussion