概要
CC BY 3.0ライセンス(Attribution 3.0 Unported)に基づいて翻訳・公開いたします。
- 英語記事: toptal/active-job-style-guide: This Background Jobs style guide is a list of best practices working with Ruby background jobs.
- 原文更新日: 2020/07/17
- 著者: Phil Pirozhkovほか
Rails: Active Jobスタイルガイド(翻訳)
本スタイルガイドは、バックエンドにSidekiqを用いたActive JobでRubyバックグランドジョブを扱うときのベストプラクティスを一覧できるようにしたものです。
一般に思われているのと異なり、このガイドラインに沿うことでかなりうまくやれるようになります。
SidekiqはActive Jobなしでも使えますが、Active Jobは透過性や有用なシリアライゼーション層を追加してくれます。
このスタイルガイドは、何の根拠もないところから現れたのではありません。編集者のプロ開発者としての経験、公式ドキュメント、そしてRubyコミュニティメンバーによる提案を下敷きにしています。
このガイドラインは、さまざまな落とし穴を避けるのに役立ちます。バックグラウンドジョブの使い方によって適しているガイドラインもあれば、そうでないものもあります。
このガイドのPDF版はAsciiDoctor PDFで生成できます。また、AsciiDoctorを使って以下のコマンドを実行すれば、HTML版も生成できます。
# README.pdfを生成する
asciidoctor-pdf -a allow-uri-read README.adoc
# README.htmlを生成する
asciidoctor
- ヒント
- 生成されたドキュメントにいい感じのシンタックスハイライトを付けたい場合は、
rouge
gemをインストールしてください。
gem install rouge
🔗 一般に推奨される方法
🔗 引数にはActive Recordモデルを渡すこと
引数にはActive Recordモデルを渡し、idを渡さないこと。Active JobはGlobalIDを用いて自動的にActive Recordモデルをシリアライズ/デシリアライズするので、モデルを手動でデシリアライズする必要はありません。
GlobalIDは、モデルクラスのミスマッチを正しく扱えます。
デシリアライズでエラーが発生すると、エラートラッキングに出力されます。
# bad: idで渡す
# デシリアライズエラーは出力されるが、このジョブはリトライ用にスケジュールされてしまう
class SomeJob < ApplicationJob
def perform(model_id)
model = Model.find(model_id)
do_something_with(model)
end
end
# bad: モデルのミスマッチ
class SomeJob < ApplicationJob
def perform(model_id)
Model.find(model_id)
# ...
end
end
# 別のモデルクラス(つまりユーザーのid)でModelをフェッチしようとする
SomeJob.perform_later(user.id)
# id渡しが容認される場合
# デシリアライズエラーは出力され、ジョブはリトライ用にスケジュール「されない」
class SomeJob < ApplicationJob
def perform(model_id)
model = Model.find(model_id)
do_something_with(model)
rescue ActiveRecord::RecordNotFound
Rollbar.warning('Not found')
end
end
# good: GlobalIDで渡す
# デシリアライズエラーは出力され、ジョブはリトライ用にスケジュール「されない」
class SomeJob < ApplicationJob
def perform(model)
do_something_with(model)
end
end
- 警告
- あるスタイルから別のスタイルにいきなり切り替えないこと。移行期間を設け、処理待ちとしてスケジュールされている、idを用いるジョブがすべて完了するまで待つこと。何らかのヘルパーを用いて、一時的に数値の引数とGlobalID引数を両方サポートすること。
class SomeJob < ApplicationJob
include TransitionHelper
def perform(model)
# TODO: 数値id引数を持つジョブがすべて処理完了したら削除する
model = fetch(model, Model)
do_something_with(model)
end
end
module TransitionHelper
def fetch(id_or_object, model_class)
case id_or_object
when Numeric
model_class.find(id_or_object)
when model_class
id_or_object
else
fail "Object type mismatch #{model_class}, #{id_or_object}"
end
end
end
🔗 キューの代入
ジョブのクラスで使われるキューを明示的に指定すること。そのキューが処理完了済みキューリストに乗っていることを確認すること。
多くのジョブをいっぺんにひとつの籠に押し込めると、緊急性の高いジョブが著しく遅延するリスクが生じます。実行に時間のかかるジョブと実行がすぐ終わるジョブを1つのキューに一緒に入れないこと。緊急性の高いジョブと、緊急性の低いジョブを1つのキューに一緒に入れないこと。
# bad: キューを指定してない
class SomeJob < ApplicationJob
def perform
# ...
end
end
# bad: 指定するキューが間違っている
class SomeJob < ApplicationJob
queue_as :hgh_prioriti # 存在しないキューを指定している
def perform
# ...
end
end
# good
class SomeJob < ApplicationJob
queue_as :high_priority
def perform
# ...
end
end
🔗 冪等性(idempotency)
理想としては、ジョブを冪等に作るべきです。つまりそのジョブが2回以上実行されても悪い副作用が生じないように作るべきです。Sidekiqは、ジョブが少なくとも1回実行されることだけを保証しますが、正確に1回きり実行することについては必ずしも保証しません。
仮にジョブがエラーで落ちないとしても、ローリングリリースでないデプロイの実行中に割り込みが入る可能性があります。
class UserNotificationJob < ApplicationJob
def perform(user)
send_email_to(user) unless already_notified?(user)
end
end
🔗 原子性(atomicity)
デプロイ中のジョブには、完了のための時間がデフォルトで25秒間与えられます。これを超えると、ワーカーは終了してジョブがキューに戻されます。これによって、作業の一部が2回実行される可能性もあります。
ジョブはアトミックに作ること。つまり「成功か何もしないか、そのどちらかしか起きない」ように作ります。
🔗 スレッド
ジョブの内部でスレッドを使わないこと。その代わりに、ジョブ内部から別のジョブを起動すること。ひとつのジョブ内でスレッドを立ち上げると、新しいデータベースコネクションがオープンされることになり、このコネクションはWebサーバーがダウンするまで増え続けて簡単に枯渇してしまいます。
# bad: コネクションがすべて消費されてしまう
class SomeJob < ApplicationJob
def perform
User.find_each |user|
Thread.new do
ExternalService.update(user)
end
end
end
end
# good
class SomeJob < ApplicationJob
def perform(user)
ExternalService.update(user)
end
end
User.find_each |user|
SomeJob.perform_later(user)
end
🔗 リトライ
Active Jobにビルトインされているretry_on
やActiveJob::Retry
(activejob-retry
gem)を使わないこと。Sidekiqのリトライ機能を使うこと(Sidekiq 6以降のリトライ機能はActive Jobからも利用可能)。
ジョブのリトライメカニズムを隠蔽したり切り出したりしないこと。リトライの指示はジョブの中で見えるようにしておくこと。
# bad: Rollbarに送信しないで3回リトライしている
# これは失敗し、Sidekiqのリトライに依存して
# リトライが何度も発生し、失敗のたびにRollbarに送信される
class SomeJob < ApplicationJob
retry_on ThirdParty::Api::Errors::SomeError, wait: 1.minute, attempts: 3
def perform(user)
# ...
end
end
# bad: ジョブがリトライかそうでないかがはっきりしない
class SomeJob < ApplicationJob
include ReliableJob
def perform(user)
# ...
end
end
# good: Sidekiqがリトライを担当している
class SomeJob < ApplicationJob
sidekiq_options retry: 3
def perform(user)
# ...
end
end
🔗 バッチ
バッチで実行されるジョブには必ずリトライ処理を付けること。さもないと、そのバッチは決して成功しません。
🔗 リトライを使おう
リトライのメカニズムを活用すること。ジョブをデッドジョブにしないこと。ジョブのリトライにSidekiqを使うようにし、手動でジョブを実行するために時間を消費しないこと。
🔗 トランザクションに配慮する
スケジュールされたジョブのバックグラウンド処理は、思ったより早い時期に始まる可能性があります。トランザクションのコミットが完了したジョブだけをスケジュールすること。
# bad: トランザクションがコミットされる前にジョブが始まる可能性がある
User.transaction do
users_params.each do |user_params|
user = User.create!(user_params)
NotifyUserJob.perform_later(user)
end
end
# good
users = User.transaction do
users_params.map do |user_params|
User.create!(user_params)
end
end
users.each { |user| NotifyUserJob.perform_later(user) }
🔗 ローカルでのパフォーマンステスト
Sidekiqのジョブは、Railsのオートリロード機能によって1つずつ実行されますが、この実行はパラレルではありません。これに惑わされる可能性があります。
Sidekiqは、eager_load
をtrue
にした環境で実行するか、以下のフラグを指定してこの振る舞いを回避した環境で実行すること。
EAGER_LOAD=true ALLOW_CONCURRENCY=true bundle exec sidekiq
🔗 重要なジョブ
バックグラウンドジョブの処理が(失敗したデプロイの最中や他のジョブがバーストしている最中などで)分単位で長引くと、ダウンすることがあります。
タイムクリティカルなジョブやミッションクリティカルなジョブは、インプロセスでの実行を検討すること。
🔗 ジョブ内のビジネスロジック
ビジネスロジックはジョブの中に置かず、外に切り出すこと。
# bad
class SendUserAgreementJob < ApplicationJob
# 不要なジョブがスケジューリングされることを回避するための
# 事前条件が満たされているかどうかをチェックする便利メソッド
def self.perform_later_if_applies(user)
job = new(user)
return unless job.satisfy_preconditions?
job.enqueue
end
def perform(user)
@user = user
return unless satisfy_preconditions?
agreement = agreement_for(user: user)
AgreementMailer.deliver_now(agreement)
end
def satisfy_preconditions?
legal_agreement_signed? &&
!user.removed? &&
!user.referral? &&
!(user.active? || user.pending?) &&
!user.has_flag?(:on_hold)
end
private
attr_reader :user
# business logic
end
# good: ビジネスロジックがジョブと癒着していない
class SendUserAgreementJob < ApplicationJob
def perform(user)
agreement = agreement_for(user: user)
AgreementMailer.deliver_now(agreement)
end
end
SendUserAgreementJob.perform_later(user) if satisfy_preconditions?
🔗 あるジョブから別のジョブへのスケジューリング
「ジョブから別のジョブをスケジュールすべきかどうか」あるいは「インプロセスで実行すべきかどうか」についてはそれぞれのメリットとデメリットを秤にかけること。考慮すべき要素:「そのジョブはリトライ可能か」「内側のジョブは失敗する可能性があるか」「ジョブは冪等か」「失敗する可能性のある親ジョブで何か他のことをやっているか」
# メリット:「エラーカーネル」パターン
# デメリット: 追加ジョブが起動される
class SomeJob < ApplicationJob
def perform
SomeMailer.some_notification.deliver_later
OtherJob.perform_later
end
end
# good: ジョブを追加しない
# bad: `OtherJob`が失敗すると`SomeMailer`もリトライで再実行される
class SomeJob < ApplicationJob
def perform
SomeMailer.some_notification.deliver_now
OtherJob.perform_now
end
end
🔗 ジョブがものすごく多い場合
多数のジョブを実行しなければならない場合は、スケジューリングするのも手です。
トレースしやすくするには、バッチの利用を検討すること。
また、ホスト(親)ジョブとサブジョブで同じキューを指定すること。
# 容認できる方法
def perform
batch = Sidekiq::Batch.new
batch.description = 'Send weekly reminders'
batch.jobs do
User.find_each do |user|
WeeklyReminderJob.perform_later(user)
end
end
end
🔗 ジョブをリネームする
ジョブクラスのリネームは慎重に行うこと(ジョブがスケジュールされたタイミングでジョブを処理するクラスが存在しなくなる状況に陥らないようにすること)。
- メモ
- これはメイラーで
deliver_later
を使うときにも関連します。
# good: 古いクラスが維持される
# TODO: 古いジョブが無事に終わったら数週間以内にこのエイリアスを削除すること
OldJob = NewJob
🔗 sleep
ジョブ内でKernel.sleep
を使わないこと。sleep
するとワーカースレッドがブロックされ、他のジョブが処理不能になってしまいます。そのジョブをリスケして後で実行するか、リミッターでカスタム例外を用いること。
# bad
class SomeJob < ApplicationJob
def perform(user)
attempts_number = 3
ThirdParty::Api::User.renew(user.external_id)
rescue ThirdParty::Api::Errors::TooManyRequestsError => error
sleep(error.retry_after)
attempts_number -= 1
retry unless attempts_number.zero?
raise
end
end
# good: ジョブを短期間リトライし、リトライ回数に上限がある
class SomeJob < ApplicationJob
sidekiq_options retry: 3
sidekiq_retry_in do |count, exception|
case exception
when ThirdParty::Api::Errors::TooManyRequestsError
count + 1 # i.e. 1s, 2s, 3s
end
end
def perform(user)
ThirdParty::Api::User.renew(user.external_id)
end
end
# good: ジョブ内でのAPI利用が細かく制御されている
class SomeJob < ApplicationJob
def perform(user)
LIMITER.within_limit do
ThirdParty::Api::User.renew(user.external_id)
end
end
end
# config/initializers/sidekiq.rb
Sidekiq::Limiter.configure do |config|
config.errors << ThirdParty::Api::Errors::TooManyRequestsError
end
🔗 インフラ
🔗 ワンコアあたりワンプロセス
マルチコアのコンピュータでは、コアを使い切るために必要に応じてSidekiqプロセスをできるだけ多数実行すること。ひとつのSidekiqプロセスはCPUコアを1つしか使いません。要するに、コアに空きがある限りプロセスをなるべく多数実行することです。
🔗 Redisのメモリ制約
Redisのデータベースサイズは、サーバーのメモリによって制限されます。maxmemory
を明示的に設定することを好む人もいますが、noeviction
ポリシーと組み合わせたときにジョブのスケジューリングでエラーが発生する可能性があります。
🔗 死んだジョブ
死んだジョブをそのままにしないこと。死んだジョブで拡張バックトレースが有効になっていると、死んだジョブ1個だけでデータベース内で20KBを専有することがあります。
死んだジョブの根本原因を修正してジョブを再実行するか、でなければジョブを削除すること。
🔗 多すぎる引数
ジョブに引数を渡しすぎないこと。
# bad
SomeJob.perform_later(user_name, user_status, user_url, user_info: huge_json)
# good
SomeJob.perform_later(user, user_url)
🔗 ジョブの大群
数百個〜数千個におよびジョブを同一時刻に同時起動するスケジュールにしないこと。パラメータなしのジョブ1個ですら0.5KBを消費します。ジョブに引数を渡したときの正確なフットプリントをジョブごとに測定すること。
🔗 監視
サーバーやストアでのメトリクスの移り変わりを監視すること。メトリクスを正しく設定することで、ジョブ処理のスループットを改善する正しい答えを得られます。
🔗 商用機能
スケールの規模によっては、有料の商用機能が採算に見合うこともあります。
こうした商用機能には、サードパーティ製アドオンの形で利用できるものもありますが、その多くは信頼性に疑問が残ります。
🔗 Sidekiq Batchesを利用する
ひとつのタスクに関連するジョブがいくつもある場合はSidekiq Batchesでグループ化しましょう。Sidekiq Batchesのjobs
メソッドはアトミックなので、すべてのジョブが「オールオアナッシング」形式でまとめてスケジューリングされます。
# bad
class BackfillMissingDataJob < ApplicationJob
def self.run_batch
Model.where(attribute: nil).find_each do |model|
perform_later(model)
end
end
def perform(model)
# do the job
end
end
# good
class BackfillMissingDataJob < ApplicationJob
def self.run_batch
batch = Sidekiq::Batch.new
batch.description = 'Backfill missing data'
batch.on(:success, BackfillComplete, to: SysAdmin.email)
batch.jobs do
Model.where(attribute: nil).find_each do |model|
perform_later(model)
end
end
end
def perform(model)
# ジョブを実行する
end
end
🔗 ジョブのセルフスケジューリング
実行時間の長いジョブでセルフスケジューリング機能を使うのは避けること。Sidekiq Batchesで負荷を分割するのが望ましい方法です。
# bad
class BackfillMissingDataJob < ApplicationJob
SIZE = 20
def perform(offset = 0)
models = Model.where(attribute: nil)
.order(:id).offset(offset).limit(SIZE)
return if models.empty?
models.each do |model|
model.update!(attribute: for(model))
end
self.class.perform_later(offset + SIZE)
end
end
# good
class BackfillMissingDataJob < ApplicationJob
def self.run_batch
Sidekiq::Batch.new.jobs do
Model.where(attribute: nil)
.find_in_batches(20) do |models|
BackfillMissingDataJob.perform_later(models)
end
end
end
def perform(models)
models.each do |model|
model.update!(attribute: for(model))
end
end
end
🔗 API操作に上限を設定する
ほとんどのサードパーティAPIには利用頻度の上限が設定されており、一定期間内の呼び出し回数がこの上限を超えると失敗します。こうした外部呼び出しを行うジョブでは呼び出し頻度に上限を設定すること。
実行されるジョブの数に決して依存しないようにすること。多数のジョブを実行時刻を変えてスケジューリングしたとしても、ジョブ処理の詰まりなどによって多数のジョブが一気に実行される可能性があります。SidekiqのEnterprise Rate Limiting機能を用いること。同機能の「コンカレント」「バケット」「ウィンドウ」戦略は、特定のAPIに対するほとんどの上限設定で通用します。
# bad
class UpdateExternalDataJob < ApplicationJob
def perform(user)
new_attribute = ThirdParty::Api.get_attribute(user.external_id)
user.update!(attribute: new_attribute)
end
end
User.where.not(external_id: nil)
.find_in_batches.with_index do |group_number, users|
users.each do |user|
UpdateExternalDataJob
.set(wait: group_number.minutes)
.perform_later(users)
end
end
# good
class UpdateExternalDataJob < ApplicationJob
LIMITER = Sidekiq::Limiter.window('third-party-attribute-update', 20, :minute, wait_timeout: 0)
def perform(user)
LIMITER.within_limit do
new_attribute = ThirdParty::Api.get_attribute(user.external_id)
user.update!(attribute: new_attribute)
end
end
end
# アプリケーションのコード
User.where.not(external_id: nil).find_each do |user|
UpdateExternalDataJob.perform_later(user)
end
# config/initializers/sidekiq.rb
Sidekiq::Limiter.configure do |config|
config.errors << ThirdParty::Api::Errors::TooManyRequestsError
end
🔗 Sidekiqのデフォルト「limited backoff」
Sidekiqのデフォルト「limited backoff」に依存しないこと。この機能を使うと、ジョブが5分以内にリスケされます。
DEFAULT_BACKOFF = ->(limiter, job) do
(300 * job['overrated']) + rand(300) + 1
end
この機能は、制約が速やかに解除される場合や、制約が数時間維持される場合には不向きです。以下のようにリミッターで設定するのが基本です。
Sidekiq::Limiter.configure do |config|
config.backoff = ->(limiter, job) do
case limiter.name
when 'daily-third-party-api-limit'
12.hours
else
(300 * job['overrated']) + rand(300) + 1 # fallback to default
end
end
end
リミッターの比較の仕組みを理解しておくこと。リミッター同士はオブジェクトではなく名前で比較すること。
Sidekiq::Limiter.bucket('custom-limiter', 1, :day) == Sidekiq::Limiter.bucket('custom-limiter', 1, :day) # => false
🔗 リミッターを再利用する
リミッターは起動中に1度作成しておき、それらを再利用すること。リミッターの設計はスレッドセーフで、かつ共有可能です。
リミッターごとにデフォルトでRedis内で114バイトを専有し、デフォルトのTTLは3か月です。共有されていないリミッターを使ってひと月あたり百万件のジョブを動かすと、Redis内で常に300MBを消費します。
# bad: ジョブ呼び出しのたびにリミッターが再作成される
class SomeJob < ApplicationJob
def perform(...)
limiter = Sidekiq::Limiter.concurrent('erp', 50, wait_timeout: 0, lock_timeout: 30)
limiter.within_limit do
# call ERP
end
end
end
# good
class SomeJob < ApplicationJob
ERP_LIMIT = Sidekiq::Limiter.concurrent('erp', 50, wait_timeout: 0, lock_timeout: 30)
def perform(...)
ERP_LIMIT.within_limit do
# call ERP
end
end
end
# 容認可能: 例外は「リミッターが特定目的だけに使われる場合」と
# 「リミッター名の中で識別キーとして使われる場合」
class SomeJob < ApplicationJob
def perform(user)
# Rate limiting is per user account
user_throttle = Sidekiq::Limiter.bucket("stripe-#{user.id}", 30, :second, wait_timeout: 0)
user_throttle.within_limit do
# call stripe with user's account creds
end
end
end
🔗 リミッターのオプション
リミッターオプションの利用方法を誤ると、振る舞いがおかしくなることがあります。
🔗 wait_timeout
wait_timeout
は、ゼロまたは十分小さな値に設定すること。さもないと、実行待ちのジョブがキューに入っていたとしてもアイドル状態のワーカーが量産されます。
backoff設定を必ずチェックして、ジョブをリトライするタイミングを注意深く選んでおくこと。
🔗 コンカレントリミッターのlock_timeout
lock_timeout
は、ジョブ実行時間よりも長くすること。さもないと、ロックの解放が早くなりすぎて予想を超える数のコンカレントジョブが実行されます。
🔗 グローバルなリミッターミドルウェア
Sidekiq::Limiter::OverLimit
例外はジョブによってrescue
される可能性があります(ローカルで定義されたリミッターからジョブ自身を廃棄する目的で)。グローバルなリミッターミドルウェアとローカルのジョブリミッターの干渉を避けるには、Sidekiq::Limiter::OverLimit
例外をミドルウェアでラップします。
# ミドルウェア
class SaturationLimiter
SaturationOverLimit = Class.new(StandardError)
def self.wrapper(job, block)
LIMITER.within_limit { block.call }
rescue Sidekiq::Limiter::OverLimit => e
limiter_name = e.limiter.name
# ジョブレベルで定義されたリミッターからの
# 「制限オーバー」例外の場合は再度raiseする
raise unless limiter_name == LIMITER.name
# Sidekiq::Limiterがジョブを後で実行するためにリスケするための
# カスタム例外を使うこと
# ただしジョブレベルで定義されたリミッターと衝突しないようにすること
raise SaturationOverLimit, limiter_name
end
end
# config/initializers/active_job.rb
ActiveJob::Base.around_perform(&SidekiqLimiter.method(:wrapper))
🔗 サードパーティサービスでOverLimit
を無視する
Sidekiq::Limiter::OverLimit
は内部メカニズムなので、トリガーされたときにこの例外を通知する意味はありません。
# config/initializers/rollbar.rb
Rollbar.configure do |config|
config.exception_level_filters.merge!('Sidekiq::Limiter::OverLimit' => 'ignore')
end
# config/newrelic.yml
production:
error_collector:
enabled: true
ignore_errors: "Sidekiq::Limiter::OverLimit"
🔗 ローリング再起動
SidekiqのEnterprise Rolling Restartsを使うこと。ローリング再起動することで、デプロイがダウンタイムの影響を受けずに済みますし、アトミックでないジョブや冪等でないジョブがデプロイ時に2回以上実行されることを防げます。
- 警告
- Capistranoスタイルのデプロイでは、コードや依存関係のストール防止のため必ず
--reexec-as
と--drop-env-var BUNDLE_GEMFILE
オプションを指定すること。
🔗 テスト
🔗 perform
job.perform
やjob_class.new.perform
は、Active Jobのシリアライズ/デシリアライズのステージをバイパスしてしまうので使わないこと。使うならjob_class.perform_now
にすること。暗黙で設定されるsubject
で.perform
は使わないことが推奨されます(既に正しく言及されているように、クラスではなくジョブインスタンスでのみ使えます)。
# bad: 暗黙で定義された`subject`で`perform`メソッドが直接呼ばれてしまう
RSpec.describe SomeJob do
# 暗黙で定義された`subject`は`SomeJob.new`
it 'updates user status' do
expect { subject.perform(user) }.to change { user.status }.to(:updated) }
end
end
# bad: `perform`メソッドがジョブインスタンスで直接呼ばれてしまう
RSpec.describe SomeJob do
it 'updates user status' do
expect { SomeJob.new.perform(user) }.to change { user.status }.to(:updated) }
end
end
# good
RSpec.describe SomeJob do
it 'updates user status' do
expect { SomeJob.perform_now(user) }.to change { user.status }.to(:updated) }
end
end
🔗 perform_later
ジョブのテストではperform_later
よりもperform_now
を優先的に使うこと。perform_later
はRedisと無関係です。
# bad: Redisとの不要な往復が発生する
RSpec.describe SomeJob do
it 'updates user status' do
expect do
SomeJob.perform_later(user)
perform_scheduled_jobs
end.to change { user.status }.to(:updated) }
end
end
# good
RSpec.describe SomeJob do
it 'updates user status' do
expect { SomeJob.perform_now(user) }.to change { user.status }.to(:updated) }
end
end
🔗 本ガイド作成の経緯
本ガイドは、ActiveJobでSidekiqを用いるときのベストプラクティスを企業内部向けにリスト化したものとして誕生し、別のバックグラウンドジョブ処理ツールからSidekiqに移行したときの、おびただしいコードレビューから発言を集めて編集しました。初期段階では、Phil Pirozhkovが同僚の力添えを得て作成し、Toptalがスポンサーになりました。
🔗 本ガイドへの貢献について
本ガイドの作成は現在も進行中です。皆さんにガイドを改善いただけると、Rubyコミュニティにとって大きな(そしてシンプルな)助けとなります!
本ガイドに書かれている内容は、石に刻んだような不動のものではありません。私たちは、バックグラウンドジョブを用いる業務のベストプラクティスを本ガイドに集約することに関心のあるすべての人々とともに力を合わせてガイドを良くしていきたいと望んでいます。目標は、Rubyコミュニティ全体にとって有益な情報源を作成することです。
issueのオープンや改善のためのプルリクエスト送信はお気軽にどうぞ。皆さまのご協力にあらかじめ感謝申し上げます。
🔗 貢献方法
貢献は難しくありません。以下のガイドラインに沿っていただくだけで結構です。
- GitHubで本リポジトリをforkする
- featureブランチで項目追加またはバグ修正を行う
- 変更内容をわかりやすく記述する
- 作成したfeatureブランチをpushする
- プルリクエストを送信する
🔗 ライセンス
This work is licensed under a Creative Commons Attribution 3.0 Unported License
🔗 本ガイドを広めましょう
コミュニティドリブンのスタイルガイドは、その存在がコミュニティに知られなければほとんど役に立ちません。本ガイドをツイートして、お友だちや同僚との共有をお願いします。本ガイドへのどんなコメント、提案、ご意見もガイドをささやかに改善するのに役立ちます。皆さんも、可能な限りベストなガイドがある方がよいですよね?