自己紹介
はじめまして、はる(@lemonade_37)と申します。
駆け出しエンジニアとして働き始めて約1ヶ月が経過しました🐣
概要
RailsアプリにSidekiq(redis)を導入し、メールの自動送信の機能を追加していきます。
この記事のために、メールを10分後に送信するだけのサンプルアプリを用意しました。
完成形のコードはこちらです。
環境
- Mac OS(Apple Silicon)
- Docker
- Ruby 3.2.3
- Rails 7.1.3
- Importmap
- Tailwindcss、daisyUI
- PostgreSQL
間違っている箇所や、紹介した方法よりも良い方法があるかもしれませんので、その際は教えて頂けると嬉しいです🙇
バックグラウンド処理とは?
Railsアプリケーションでは、ユーザーが特定の動作を行った際に「投稿する」「画面を表示させる」などのアクションを実行します。
バックグラウンド処理とは例えば、「毎日特定の時間にメールを送信する」などの処理です。
ユーザーの行う操作とは独立して、サーバー側で非同期に実行されるタスクのことを言います。
バックグラウンド処理を実装する方法について
バックグラウンド処理を実装する方法はさまざまな手段があります。
調べたもののみですが、それぞれの特徴についてまとめます✏️
cron
Linuxに標準で備わっているプログラムの一種で、設定すると処理を自動で定期的に実行することができます。1)
Railsアプリケーション内だけでなく、決まった時間にアプリ自体を立ち上げるなど、システムレベルでの実行も可能です。
whenever
cronジョブをRubyで扱いやすいように構文を提供してくれているRubyのgemです。
gem whenever
Sidekiq
バックグラウンド処理を行うためのRubyのgemです。
同時に複数のバックグラウンドジョブを実行するなどの実装ができます。
定期的なジョブをスケジュールする拡張機能を使用するための、sidekiq-cronや、sidekiq-schedulerなどの追加gemがあります。
gem sidekiq
Redis
多目的に利用されるインメモリデータストアです。
インメモリデータストアは、すべてのデータをメモリ上に展開し、データの読み込みや追加、変更、削除をすべてメモリ上で完結させることで、従来型の数百倍から数万倍も高速に処理を実行することができるデータベースです。2)
今回はセッション管理として利用しますが、他にも様々な用途で使用されます。
ActiveJob
Rails4.2からrailsのデフォルトで導入された、これまでSidekiqなどのGemを使って行っていたバックグラウンドジョブ系を扱える機能です。
ActiveJobの基礎
使用技術の選び方について
バックグラウンドでどんな処理を実行したいかという目的によって、上記の技術を必要に応じて組み合わせて使用します。
今回は、アプリサーバーは自動で落ちずに常に起動している状態で、決まったタイミングでメールを自動送信するという機能のみなので、
ActiveJob+Sidekiq(Redis) を使用することにしました。
ActiveJobを介してSidekiqを使うか、直接Sidekiqを使用するかに関しては、調べてみたところさまざまな価値観があるようでした。
私は下記の記事を参考に、この組み合わせにしました。
(他にも参考にした記事は参考記事参照)
実装開始!🕰
それではここから、メールを送信するボタンを押すとエンキューされ、10分後に自動的にメールが送信される機能を実装していきます。
基本的なメール送信機能はすでに実装されている状態からスタートします。
今回は、gem 'letter_opener_web'
を使用し、ローカルのみでメール送信の動作を行えるようにしています。
この時点でのコードについて確認したい方は以下を参照ください。
Sidekiq・Redisの導入
-
docker-compose.ymlにredisを追加します。
docker-compose.ymlredis: image: "redis:7.0-alpine" volumes: - redis_volume:/data command: redis-server --appendonly yes ports: - "6379:6379"
volumes:
にもredis_volume:
を追加します。docker-compose.ymlvolumes: postgres_volume: redis_volume:
-
ここでDokerHubのイメージを使用して、Redisのコンテナを作成します。
-
Dockerのボリュームについての説明は今回は割愛します。(公式ドキュメントを参照ください)
-
ボリュームを設定し、
command: redis-server --appendonly yes
と記載しAOF Redis を使用することで、
10分後送信予定のメールのキューがある状態でDockerが停止してしまっても、
実行予定で溜まっていたキューが削除されずに、
Dockerを再度立ち上げるとキューの続きから実行することができます。
-
-
docker-compose.ymlにsidekiqを追加します。
docker-compose.ymlsidekiq: build: . command: bundle exec sidekiq volumes: - .:/app depends_on: - db - redis
-
depends_on:
でdbコンテナとredisコンテナがSidekiqサービスより先に起動するように指定します。
-
-
docker compose build
コマンドで再ビルドします。
-
Gemfileに下記を記載し、
docker compose up
してから、docker compose exec web bundle install
実行します。Gemfile# Background Job gem 'sidekiq' gem 'sidekiq-scheduler'
-
config/application.rbに下記を追加します。
application.rbclass Application < Rails::Application # 〜〜省略〜〜 config.active_job.queue_adapter = :sidekiq # 〜〜省略〜〜 end
-
config/initializers/sidekiq.rbを作成し、下記のように記載します。
sidekiq.rbSidekiq.configure_server do |config| config.redis = { url: 'redis://redis:6379' } end Sidekiq.configure_client do |config| config.redis = { url: 'redis://redis:6379' } end
-
config/sidekiq.ymlを作成し、下記のように記載します。
sidekiq.yml:concurrency: 3 :queues: - default
-
:concurrency:
は同時並行で実施できるジョブの数の上限を指定しているので、任意の数値でOKです。
-
-
docker compose exec web bundle exec sidekiq
を実行して、下記のように起動されれば導入完了です🦶m, `$b .ss, $$: .,d$ `$$P,d$P' .,md$P"' ,$$$$$b/md$$$P^' .d$$$$$$/$$$P' $$^' `"/$$$' ____ _ _ _ _ $: ',$$: / ___|(_) __| | ___| | _(_) __ _ `b :$$ \___ \| |/ _` |/ _ \ |/ / |/ _` | $$: ___) | | (_| | __/ <| | (_| | $$ |____/|_|\__,_|\___|_|\_\_|\__, | .d$$ |_| 〜〜省略〜〜 INFO: Starting processing, hit Ctrl-C to stop
Sidekiqダッシュボードの設定
- config/routes.rbに下記を追加します。
routes.rb
require 'sidekiq/web' Rails.application.routes.draw do mount Sidekiq::Web, at: '/sidekiq' end
- Dockerを立ち上げ直し、http://localhost:3000/sidekiq/ にアクセスして、下記の画面が表示されればダッシュボードの設定が完了です。
-
※任意
ダッシュボードには一般ユーザーはアクセスできないようにBasic認証をつけるのが望ましいです。routes.rbrequire 'sidekiq/web' Rails.application.routes.draw do Sidekiq::Web.use(Rack::Auth::Basic) do |user_id, password| [user_id, password] == [ENV['SIDEKIQ_BASIC_ID'], ENV['SIDEKIQ_BASIC_PASSWORD']] end mount Sidekiq::Web, at: '/sidekiq' end
.envSIDEKIQ_BASIC_ID=hogehoge SIDEKIQ_BASIC_PASSWORD=fugafuga
10分後に自動送信されるメールのActiveJob作成
ここからは、メールの定期的な自動送信機能のジョブを作成します。
- ActiveJobのファイルを作成します。
docker compose exec web rails g job SendEmail
を実行し、app/jobs/send_email_job.rbを作成します。
ジョブの中でMail送信を実行するようにします。send_email_job.rbclass SendEmailJob < ApplicationJob queue_as :default def perform(email) SampleEmailMailer.sample_email(email).deliver_now end end
- コントローラーでJobを呼び出します。
現在時刻から10分後に実行するキューを追加する形とします。controllers/static_pages_controller.rbclass StaticPagesController < ApplicationController def new ; end def create email = params[:email] + SendEmailJob.set(wait_until: Time.zone.now + 10.minutes).perform_later(email) #10分後に実行するジョブをキューに追加 redirect_to new_static_page_path, notice: 'メールを送信のキューを追加しました' end end
- Railsコンソールを立ち上げて、ジョブ自体が実行できるか確認してみます。
ターミナル
% docker compose exec web rails c Loading development environment (Rails 7.1.3.2) irb(main):001> SendEmailJob.perform_now("a@example.com")
- ジョブが実行され、メールが届いていれば、ジョブ自体の実行はできています。
10分後に自動送信されるメールの動作確認
では、実際アプリ画面でメールを送信し、10分後に実行されるか確認します。
-
まずDockerを立ち上げた後忘れずに、Sidekiqを立ち上げます。
(これを忘れているとキューが実行されないので注意⚠️)ターミナルdocker compose exec web bundle exec sidekiq
指定した日時に自動送信されるように設定
10分後に送信する以外にも、「毎月の月初にメールを送信する」などの設定をすることもできます。
- config/sidekiq.ymlに下記の
:scheduler:
を追加します。sidekiq.yml:concurrency: 3 :queues: - default :scheduler: :schedule: send_email_job: cron: "0 12 1 * *" class: SendEmailJob queue: default
- sidekiqを立ち上げ直した際に、ターミナルに下記が表示されていれば、指定した日時にジョブを実行するスケジュールが設定されています🎉
ターミナル
2024-03-31T12:46:26.925Z pid=67 tid=2vv INFO: Scheduling send_email_job {"cron"=>"0 12 1 * *", "class"=>"SendEmailJob", "queue"=>"default"}
さいごに
業務の中でメールの自動送信の機能を作成する機会があり、Sidekiqを初めて使用しましたが、
これまで理解できていなかった部分だったため、大変勉強になりました!🕰️
まだまだ理解しきれていない部分も多いため、引き続き勉強していきます🙇
$\tiny{この記事の続きとして、EC2の本番環境でのSidekiqの導入についてもいつか記事にしたいと思います}$
参考・引用記事
引用記事
1)cronとは。crontabで定期実行を自動化する方法を解説
2)e-Words インメモリデータベース
参考記事
- Wikipedia バックグラウンドプロセス
- 【個人開発】スクール生活をより豊かにするためのアプリを開発しました🤖
- インメモリDBとは
- Rails: SidekiqはActive Jobを経由せずに直接使おう(翻訳)
- ActiveJob はまだちょっと使うには早いかも
- redisをdockerで起動してみた。
- docker-composeでredis環境をつくる
- RedisをDockerで実行する方法(+それをおすすめする理由)
- Redis の永続性
- 【Rails7】Redis+Sidekiq+Active Jobで指定した日時に非同期処理を行う
- Sidekiqの管理画面をアクセス制限する方法3選
- sidekiq + active jobで予定されたjobが実行状態にならない