Created
December 11, 2019 15:54
-
-
Save masutaka/12943fb977b47b1a6c67a1cbdace4141 to your computer and use it in GitHub Desktop.
sample one-shot job on Heroku
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# @abstract One-shot 関連のジョブは必ずこの class を継承し、 | |
# キーワード引数に :global_executions を要求する #perform を実装すること | |
class OneshotBaseJob < ApplicationJob | |
class << self | |
# Retry the One-shot job due to the exception | |
# | |
# @param job [OneshotBaseJob] A One-shot job class to retry | |
# @return [void] | |
# | |
# @note See ActiveJob::Exceptions::ClassMethods#retry_on for the parameters except to `job` | |
def retry_oneshot_on(exception, job: self, wait: 3.seconds, attempts: 5, queue: nil, priority: nil) | |
rescue_from exception do |error| | |
if executions < attempts | |
global_executions = executions + 1 | |
determine_delay = forked_determine_delay(seconds_or_duration_or_algorithm: wait, executions: executions) | |
logger.error "Retrying #{job} in #{determine_delay} seconds (#{global_executions}/#{attempts}), due to a #{exception}. The original exception was #{error.cause.inspect}." | |
job.set( | |
wait: determine_delay, | |
queue: queue, | |
priority: priority, | |
).perform_later( | |
**arguments.first, | |
global_executions: global_executions, | |
) | |
else | |
logger.error "Stopped retrying #{job} due to a #{exception}, which reoccurred on #{executions} attempts. The original exception was #{error.cause.inspect}." | |
raise error | |
end | |
end | |
end | |
end | |
# One-shot(一回限り)で起動する関係で、executions(実行回数)が繰り越されない。 | |
# 永久にリトライすることがあるため、引数で global_executions を注入し、手動で繰り越す。 | |
before_perform do |job| | |
Rails.logger.info( | |
'%s is performed (release_version: %s, commit_hash: %s)' % [ | |
self.class, | |
Settings.heroku.release_version, | |
Settings.heroku.slug_commit, | |
], | |
) | |
shop_id = job.arguments.first[:shop_id] | |
job.executions = job.arguments.first[:global_executions] | |
end | |
# One-shot Job を実行する | |
# | |
# @param global_executions [Integer] 全体での実行回数。1 始まり | |
# @return [void] | |
# def perform(global_executions:, ...) | |
# ... | |
# end | |
private | |
# ActiveJob::Exceptions#determine_delay をそのまま持ってきた。 | |
# https://github.com/rails/rails/blob/v6.0.0/activejob/lib/active_job/exceptions.rb#L124-L140 | |
# | |
# やむを得ずそうした理由は以下のとおり。 | |
# * Rails 5.2.3 から 6.0.0 で引数が変わった。private method なので変更に気づき続けるのが難しい | |
# * OneshotJob では Active Job 以上のことをし始めており、このメソッドを使う必要がある | |
def forked_determine_delay(seconds_or_duration_or_algorithm:, executions:) | |
case seconds_or_duration_or_algorithm | |
when :exponentially_longer | |
(executions**4) + 2 | |
when ActiveSupport::Duration | |
duration = seconds_or_duration_or_algorithm | |
duration.to_i | |
when Integer | |
seconds = seconds_or_duration_or_algorithm | |
seconds | |
when Proc | |
algorithm = seconds_or_duration_or_algorithm | |
algorithm.call(executions) | |
else | |
raise "Couldn't determine a delay based on #{seconds_or_duration_or_algorithm.inspect}" | |
end | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class SampleCoreJob < OneshotBaseJob | |
# queue_as は敢えて設定しない。Oneshot Job として Heroku の One-off Dyno 上で動作し、 | |
# sidekiq の queue には溜まらないため。 | |
retry_oneshot_on(Sample1Error, job: SampleJob, wait: :exponentially_longer) | |
retry_oneshot_on(Sample2Error, job: SampleJob, wait: 5.minutes) | |
# サンプルジョブ | |
# | |
# @param shop_id [String] ショップの ID | |
# @param global_executions [Integer] 全体での実行回数。1 始まり | |
# @return [void] | |
def perform(shop_id:, global_executions:) | |
# ... | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class SampleJob < OneshotBaseJob | |
queue_as :default | |
# One-Off Dyno を作成し、SampleCoreJob を実行するリクエストを発行する | |
# | |
# @param shop_id [String] ショップの ID | |
# @param global_executions [Integer] 全体での実行回数。1 始まり | |
# @return [void] | |
def perform(shop_id:, global_executions:) | |
dyno = ::PlatformAPI::Dyno.new( | |
::PlatformAPI.connect(Settings.heroku.api_key), | |
) | |
dyno.create( | |
Settings.heroku.app_name, | |
attach: false, | |
command: %!rails r "SampleCoreJob.perform_now(shop_id: '#{shop_id}', global_executions: #{global_executions})"!, | |
force_no_tty: nil, | |
size: Settings.heroku.oneshot_dyno_size, | |
type: 'run', | |
time_to_live: 1.hour, | |
) | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
SampleJob.perform_later(shop_id: 12345, global_executions: 1)
のように使う。global_executions には必ず
1
を指定すること。リトライするとインクリメントされて、次に実行される SampleJob に引き継がれる。