バッチ処理とは
リアルタイムでの処理が難しかったり、一気に大量のデータを処理する場合に使用します。
また、毎日、毎週、または特定の時間帯に定期的に実行することも多いです。
具体的には
- データベースのメンテナンス: 定期的なバックアップや不要なデータの削除
- メール送信: 定期的に大量のメールを送信する処理
- レポート生成: 大量のデータを集計してレポートを生成する
- データ移行: 異なるシステム間でデータを移行する
などが挙げられます。
実行方法に関しては色々ありますが、今回はRakeタスクを作成とwheneverを使用して定期的にバッチ処理を実行するまでをやってみました。
開発環境
- Rails 7.1.3
- Ruby 3.3.3
- Docker 24.0.7, build afdd53b
前提条件
今回はuser.rb
に必要なカラムとメソッド等を用意しています。
# id :bigint not null, primary key
# age(年齢) :integer
# birthday(誕生日) :date
# name(イニシャル) :string
# 本日誕生日のユーザーを絞り込む
scope :with_birthday_today, lambda {
today = Time.zone.today
where("EXTRACT(MONTH FROM birthday) = ? AND EXTRACT(DAY FROM birthday) = ?", today.month, today.day)
}
# 生年月日から年齢を計算してageにセットする
def set_age
self.age = (Time.zone.today.year - birthday.year) - (Time.zone.today.month < birthday.month || (Time.zone.today.month == birthday.month && Time.zone.today.day < birthday.day) ? 1 : 0)
end
実装内容
ユーザー(User)の誕生日(birthday)当日に年齢(age)を更新するバッチ処理を作成。
毎日0:00に上記バッチ処理を実行するよう設定する。
Rakeタスクの作成
下記コマンドを実行することで、lib/tasks/user.rake
が作成されます。
$ docker-compose run web rails g task user
デフォルトでは以下のような記載がされています。
namespace :user do
end
生成されたファイルを編集してみます。
namespace :user do
desc "今日が誕生日のユーザーの年齢を再計算して保存する"
task update_age: :environment do
# 本日誕生日のユーザーを取得
users = User.with_birthday_today
# 対象のユーザーが存在しない場合にログに出力
Rails.logger.info puts "今日が誕生日のユーザーは存在しませんでした。" if users.blank?
users.each do |user|
# ユーザーの年齢を再計算してセットする
user.set_age
# 成功/失敗に関わらずログを出力
if user.save
Rails.logger.info puts "#{user.name}の年齢の更新に成功しました"
else
Rails.logger.error puts "年齢の更新に失敗しました"
end
end
end
end
Rakeタスクを実行する
以下のコマンドで実行が出来ます。
# コマンド rails ファイル.定義したタスク名
$ docker-compose run web bundle exec rake user.update_age
定期的に実行するように設定する
今回は毎日0:00に実行するように設定していきます。
1. wheneverをインストール
wheneverというgemをGemfileに追加します。
gem 'whenever', require: false
Dockerコンテナ内でbundle install
を実行して、whenever
をインストールします。
$ docker-compose run web bundle install
2. wheneverの設定
wheneverize .
を実行して、config/schedule.rb
ファイルを生成します
$ docker-compose run web bundle exec wheneverize .
# 実行結果
[add] writing `./config/schedule.rb'
[done] wheneverized!
3. schedule.rb の編集
ログを保存するファイルを作成しておく(これは好み)
$ touch log/cron.log
config/schedule.rb
に次のように記述して、毎日0:00にタスク user:update_age
を実行するように設定します。
# 環境変数を cron ジョブに渡す(重要!)
ENV.each { |k, v| env(k, v) }
# ログを保存
set :output, "log/cron.log"
every 1.day, at: '12:00 am' do
rake 'user:update_age', environment: ENV['RAILS_ENV']
end
5. cronをインストール
Dockerイメージにcron
をインストールします。
Dockerfileに以下を追加します。
&& apt-get install -y cron nano \
その後、イメージを再ビルドします。
$ docker-compose build
cron
のインストール確認
docker compose run --rm --no-deps web which cron
# 実行結果
Starting periodic command scheduler: cron.
/usr/sbin/cron
6. cron関連の設定
entrypoint.sh
スクリプトを作成
$ touch entrypoint.sh
entrypoint.sh
を編集
#!/bin/bash
set -e
# cronサービスを起動
service cron start
# Railsサーバーを起動
exec "$@"
コンテナ内でcronサービスを起動する設定を追加します。
Dockerfileに以下を追加して、cronを起動するスクリプトを作成します。
# entrypoint.sh スクリプトをコンテナ内の /usr/bin/entrypoint.sh にコピー
COPY entrypoint.sh /usr/bin/entrypoint.sh
# entrypoint.sh に実行権限を与える (実行可能なスクリプトにする)
RUN chmod +x /usr/bin/entrypoint.sh
# コンテナ起動時に実行するコマンドとして entrypoint.sh を指定
CMD ["/entrypoint.sh"]
docker-compose.yml
に追記
services:
web:
# 追加
entrypoint: ["/usr/bin/entrypoint.sh"]
cron
がバックグラウンドで正しく起動しているか確認
$ docker-compose run web service cron status
# 実行結果
Starting periodic command scheduler: cron.
cron is running.
cronジョブの更新をする
$ docker-compose run web bundle exec whenever --update-crontab
# 実行結果
tarting periodic command scheduler: cron.
[write] crontab file updated
イメージを再ビルドします。
docker-compose build
現在登録されているcron
ジョブを確認
$ docker compose exec web crontab -l
# 実行結果
・
・
・
0 0 * * * /bin/bash -l -c 'cd /current_dir && RAILS_ENV=development bundle exec rake user:update_age --silent >> log/cron.log 2>&1'
# End Whenever generated tasks for: /current_dir/config/schedule.rb at: 2024-12-29 09:01:02 +0000
最後に
設定時間以降に
$ cat log/cron.log
を実行して、今回の場合であれば以下のログが残っていれば設定と動作が出来ています!
[ユーザー名]の年齢を[ユーザーの年齢]歳に更新しました"
or
今日が誕生日のユーザーは存在しませんでした。