見出し画像

[Ruby on Rails]非同期処理の実行結果を待つAPIを作る

お疲れ様です、GroGroPHPです。
PHP最近やってないですが😅

さて、掲題の通りRuby on Railsで非同期処理(具体的にはAcrive Jobの処理)の実行結果を待って(ポーリングして)返却するAPIを作ります。
なぜ非同期処理なのにわざわざ実行結果を待つのかと言うと、並列処理するとデータ不整合が生じる処理があり、それをキューに渡して順番に処理させたかったためです。
※データは時間の枠内でユニークでなくてはならず、時間単位と枠数が変動するためDBにユニーク制約がかけられませんでした。
※データのINSERTで不整合が起きるため、テーブルロックしかかけられず、バグの温床になりそうだったのでロックも採用しませんでした。

SampleJobテーブルの用意

以下のテーブル(migrateファイル)を作ります。
このテーブルが必要な理由は、非同期実行されるJobの情報が取れないためで、Job内でレコードを作成することによって、このテーブルのレコードの状態を見てJobの状態がわかるようにしています。

class CreateSampleJobsTable < ActiveRecord::Migration[6.1]
  def change
    create_table :sample_jobs do |t|
      t.string :job_id, comment: 'ActiveJobID'
      t.string :error, comment: 'エラーメッセージ'
      t.string :error_status, comment: 'エラーステータス'

      t.timestamps

      t.index :job_id, unique: true
    end
  end
end

SampleWorkerの作成

非同期に処理する部分です。
sidekiq-limit_fetchというgemを使用して、キュー名sampleに対する同時実行数を1に制限します。
処理の最後に、SampleJobレコードを登録する処理を入れました。

class SampleWorker < ApplicationJob
  queue_as :sample

  Sidekiq::Queue['sample'].limit = 1

  def perform

...

    sample_job = SampleJob.new
    sample_job.job_id = @job_id
    sample_job.error = @error
    sample_job.error_status = @error_status
    sample_job.save
  end
end

非同期実行とポーリング

perform_laterで先ほどのSampleWorkerを非同期実行し、0.1秒ごとにSampleJobのレコードを確認しています。
レコードができたらbreakして、エラーを拾ってレスポンスとして返却します。

@sample_worker = SampleWorker.perform_later

100.times do |_|
  SampleJob.connection.clear_query_cache
  @sample_job = SampleJob.find_by(job_id: @sample_worker.job_id)
  break if @sample_job.present?

  sleep 0.1
end

@error = @sample_job.error if @sample_job.error.present?
@error_status = @sample_job.error_status.intern if @sample_job.error_status.present?

この際、 SampleJob.connection.clear_query_cache をしていますが、これをしないとSampleJobレコードの取得SQLがキャッシュされて、いつまで経ってもレコードが取得できないので注意してください。
ActiveRecord::Base.connection.clear_query_cache と書くこともできます。

まとめ

非同期処理なのに待つなんて、元も子もないようなことなので、なかなか情報が無くて困りました。
今回は内部のActive Jobの処理だったのですが、例えば外部の非同期APIで、実行時間が長くDBに結果を直接INSERTしてくるAPI(そんなの無いか!?)とかにも応用ができます。
本来はフロント側でポーリングしてAPI2本でやるのが筋かと思いますが、短時間でバックエンド側だけで対応できたので良かったです。
そもそものアーキテクチャが悪いとかあると思いますので、ご意見はコメントいただければと思います。
今後サービス規模が大きくなったときの課題もありますが、その時に考えるとします。


この記事が気に入ったらサポートをしてみませんか?