「趣味プロジェクトで実装を書いたよ」というお話
説明しないこと
- HTTP status code 404 のチェック方法とその実装について
- 後述の Heroku Scheduler の定期実行タイミングから漏れるほどの処理の長さになった場合のことについて
状況
レコード全件(0 ~ 20,000 程度)が外部URL を持っていて、その中には HTTP status code 404 を返すものが混じっている(古くなったりして削除されている)
これを丁寧に全件舐めて、見つけ次第 削除したい
ただ、一度に全件のチェックをするとなると...
- チェック処理を実行するサーバの、当該処理実行中に他の処理が重くなる
- 実際にURL にアクセスしないとわからないので、当該ドメインに負荷を掛ける
- 最悪、アクセスがブロックされる...?!
...などが考えられるので、もう少し、お行儀の良い方法で処理を実装したい
アイディア
- 全レコードに対して適当な数...30件ごとでグループ分け
- グループに対して、順々に「現時刻より30秒後, 60秒後, 90秒後...」とグループごとに実行時間を指定する
実装
# Search 404 response URL and dispatch deletion class Dispatch404UrlDeletionJob < ApplicationJob queue_as :default DELAY_SECONDS = 30 PROCESS_GROUP_CHUNK = 30 def perform # split records into group by chunk size process_group = ReadLater.all.map(&:id).each_slice(0.step(ReadLater.count, PROCESS_GROUP_CHUNK).size).lazy # => [[2, 4, 8, ...], [128, 256, 512, ...], ...] delay_group = Array.new(process_group.size, nil).inject([]) { |ret, _| ret << ret.last.to_i + DELAY_SECONDS }.lazy # => [30, 60, 90, 120, ...] delay_group.zip(process_group).each do |delay, ids| # => [ # [30, [128, 256, 512, ...]], # [60, [2, 4, 8, ...]], # ..., # ] ids.each { |id| Delete404UrlJob.set(wait: delay.seconds).perform_later(id: id) } end end end
0.step(ReadLater.count, PROCESS_GROUP_CHUNK).size
ReadLater
レコード数に対してPROCESS_GROUP_CHUNK
で分割したときのサイズ
ReadLater.all.map(&:id).each_slice(...)
ReadLater
のすべてのレコードを ID だけの配列にしつつ、適当な数に分割...グループ分け
Array.new(process_group.size, nil).inject(.... }
Array.new
の最初の引数に数値を渡すと、その数だけ 2番目の引数で埋めた要素を持つ配列を生成するinject
で注入された配列の最後の要素を参照・計算しつつ その配列に要素を追加していく
delay_group.zip(process_group).each do...end
zip
することで配列を合成して、each do...end
の中で使える変数名を|delay, ids|
と宣言し易しくしている
ids.each { |id| Delete404UrlJob.set(wait: delay.seconds).perform_later(id: id) }
id
とdelay
を利用して、実際の処理をするDelete404UrlJob
ジョブに開始時間と実行に必要な値を渡している
(行数の割に結構な数の処理を詰め込んでしまった)
Heroku Scheduler
今回は Heroku 環境で定期実行させたい、ので適当な Rake タスクを新規に定義する
bundle exec rails generate task dispatch_404_url_deletion
desc "Split all ReadLater records to some chunks and dispatch find 404 URL and delete" task dispatch_404_url_deletion: :environment do DispatchUnshortenUrlJob.perform_now end
ドキュメント に沿って、スケジュールを追加する(Rake タスクなのでたとえば bundle exec rails dispatch_404_url_deletion
とする)