和訳：sidekiq-shceduler README

Ruby on Railsで定期的なバッチ処理を行うためのライブラリとして、sidekiq-schedulerというGemがあります。
これについて調べていたので、READMEの和訳を投稿します。

# sidekiq-scheduler
`sidekiq-scheduler`はcronを模倣したSidekiqの定期的なジョブ生成の拡張機能です。
## インストール

```
gem install sidekiq-scheduler
```

## 使用法

### Hello World

``` ruby:hello-sheduler.rb
require 'sidekiq-scheduler'

class HelloWorld
  include Sidekiq::Worker

  def perform
    puts 'Hello world'
  end
end
```

``` yaml:config/sidekiq.yml
:schedule:
  hello_world:
    cron: '0 * * * * *'   # Runs once per minute
    class: HelloWorld
```

sidekiqを実行します。

```
sidekiq -r ./hello-scheduler.rb
```

下記のように出力されるでしょう。

```
2016-12-10T11:53:08.561Z 6452 TID-ovouhwvm4 INFO: Loading Schedule
2016-12-10T11:53:08.561Z 6452 TID-ovouhwvm4 INFO: Scheduling HelloWorld {"cron"=>"0 * * * * *", "class"=>"HelloWorld"}
2016-12-10T11:53:08.562Z 6452 TID-ovouhwvm4 INFO: Schedules Loaded

2016-12-10T11:54:00.212Z 6452 TID-ovoulivew HelloWorld JID-b35f36a562733fcc5e58444d INFO: start
Hello world
2016-12-10T11:54:00.213Z 6452 TID-ovoulivew HelloWorld JID-b35f36a562733fcc5e58444d INFO: done: 0.001 sec

2016-12-10T11:55:00.287Z 6452 TID-ovoulist0 HelloWorld JID-b7e2b244c258f3cd153c2494 INFO: start
Hello world
2016-12-10T11:55:00.287Z 6452 TID-ovoulist0 HelloWorld JID-b7e2b244c258f3cd153c2494 INFO: done: 0.001 sec
```

## オプション設定

設定オプションはsideliq.yml内に記述します。
下記が使用できます。

```
:dynamic: <有効にするとアプリ実行中にスケジュールを変更できます(デフォルト無効)>
:dynamic_every: <dynamicが有効の場合、ここで指定の間隔でスケジュールが更新されます(デフォルトは5s>
:enabled: <有効にすると本機能のスケジュールが動作可能となります[デフォルト有効]>
:scheduler:
  :listened_queues_only: <キューがSidekiqにより監視されているジョブのみを生成します[デフォルト無効]>
```

## スケジューラ設定

実行スケジュールの設定はSidekiq設定ファイルの```:schedue```項目から行えます。

```yaml
:schedule:
  CancelAbandonedOrders:
    cron: '0 */5 * * * *'   # 0秒目から5分間隔で実行

  queue_documents_for_indexing:
    cron: '0 0 * * * *'   # 1時間毎に実行

    # デフォルトでは、ジョブ名はワーカークラス名からとられる。
    # クラス名と異なるジョブ名をつけたい場合は```class```オプションを使用する。
    class: QueueDocuments

    queue: slow
    args: ['*.pdf']
    description: "This job queues pdf content for indexing in solr"

    # ```perform```メソッドの最終引数としてスケジュールのメタデータを持つハッシュを渡すことを有効にする。デフォルトでは無効。
    # as the last argument of the `perform` method. `false` by default.
    include_metadata: true

    # 全ジョブの有効/無効を設定。 デフォルトでは全ジョブが有効。
    enabled: true
```

## スケジュールメタデータ

performメソッドの引数からスケジューリング処理のメタデータをワーカーに渡すことができます。
これを有効にするには、各ワーカークラス項目に下記を追加します。

```yaml
SampleWorker:
    include_metadata: true
```

performeメソッドの引数を追加します。

```ruby
  def perform(args, ..., metadata)
    # Do something with the metadata
  end
```

このメタデータハッシュは下記のキーを持地ます。

```
  metadata.keys =>
    [
      :scheduled_at # The epoch when the job was scheduled to run
    ]
```

## スケジュール型
サポートするスケジュール型は```cron, every, interval, at, in```です。
cron、every、intercal、は周期的にSidekiqにジョブを送ります。

cronでは秒単位でcronと同様のパターンで指定します。

```
:schedule:
  HelloWorld:
    cron: '0 * * * * *' # second = 0で実行
```

everyは指定の周期でトリガします。

```
    every: '45m'    # 45分間隔で実行
```

intervalはeveryと同様ですが、最後のジョブキューイングから指定の間隔が経過して次回の実行時刻をスケジューリングする点が異なります。

atとinは1回のみジョブ生成を行います。atは特定の時刻でスケジューリングします。

```
    at: '3001/01/01'
```

DateTime.parseとChronicを理解すると、どんな文字列を用いても指定できるようになります。Chronicを使用する場合は依存モジュールとして追加が必要です。

inは指定期間の経過後にトリガされます。

```
    in: 1h # 起動から1時間後にジョブを送信する
```

everyとcronでは配列を使用できます。

```
    every: ['30s', first_in: '120s']
```

詳細はhttps://github.com/jmettraux/rufus-schedulerをご参照ください。

## 別ファイルからのスケジュールロード

config/sidekiq.yml以外の個別ファイルにスケジュール設定を配置することもできます。

```yaml:sidekiq_scheduler.yml
clear_leaderboards_contributors:
  cron: '0 30 6 * * 1'
  class: ClearLeaderboards
  queue: low
  args: contributors
  description: 'This job resets the weekly leaderboard for contributions'
```

個別ファイルはscheduleルートキーを持ってないことに注意してください。
この場合ではスケジュールをロードするには下記が必要です。

```ruby
require 'sidekiq'
require 'sidekiq-scheduler'

Sidekiq.configure_server do |config|
  config.on(:startup) do
    Sidekiq.schedule = YAML.load_file(File.expand_path('../../sidekiq_scheduler.yml', __FILE__))
    SidekiqScheduler::Scheduler.instance.reload_schedule!
  end
end
```

上記のコードはアプリケーション起動時に実行されるイニシャライザ（```config/initializers```）に配置します。

## 動的スケジューリング

スケジュールは起動後に変更できます。スケジュールを追加/更新するには下記が必要です。

```
Sidekiq.set_schedule('heartbeat', { 'every' => ['1m'], 'class' => 'HeartbeatWorker' })
```

対象スケジュールが不在の場合は生成、既に存在していれば更新が行われます。

:dynamicフラグが有効の場合、スケジュール変更は5秒間毎にロードされます。異なる間隔にするには:dynamic_everyを使用します。

```config/sidekiq.yml
:dynamic: true
```

もし```:dynamic```フラグが無効なら、sidekiq側で明示的にスケジュールのリロードを記述する必要があります。

```ruby
SidekiqScheduler::Scheduler.instance.reload_schedule!
```

Sidekiq.get_scheduleの呼び出しにより現在のスケジュールを取得できます。

## タイムゾーン

cronの文法を使用する場合は、Railsの```config.time_zone```での指定ではなくサーバ側のタイムゾーンで解釈される点に注意してください。

使用するタイムゾーンは明示的に指定できます。

```
    cron: '0 30 6 * * 1 Europe/Stockholm'
```

また、Railsの```config.time_zone```で許容される「Stockholm」のような略称が使えない点にも注意してください。タイムゾーン設定を```config.time_zone```の値から行うコードを書く場合は、下記のように正しいフォーマットであることを保証してください。

```
ActiveSupport::TimeZone.find_tzinfo(Rails.configuration.time_zone).name
```

## コネクションプールに関する注意点

通常時、cronとatのジョブはsidekiq-schedulerを実行するインスタンス数とは無関係に生成されますが、これはホスト間の時刻差が24時間未満であることを前提としています。

指定のジョブを複数回生成してしまうような異常条件は、下記があげられます。
  - CPU高負荷+同時刻に100個など大量のジョブを生成。
  - ネットワーク/redisレーテンシ+28(```MAX_WORK_THREADS``` https://github.com/jmettraux/rufus-scheduler/blob/master/lib/rufus/scheduler.rb#L4を参照)以上のジョブがネットワークレーテンシウィンドウ以内の期間で発生。

  every、interval、inのジョブはホストあたり1回のみ生成されます。

## Sidekiq Webインテグレーション

sidekiq-schedulerはSidekiq Webインターフェースに```Recurring Jobs```ページを追加する拡張機能を持ちます。

```
# config.ru

require 'sidekiq/web'
require 'sidekiq-scheduler/web'

run Sidekiq::Web
```

## ActiveJobインテグレーション
sidekiq-schedulerをActiveJobで使用する場合、多くは```require```や```include```を用いず、```ApplicationJob```を継承します。scheulerのロードとワーカーモジュールのインクルードはRailsが行います。

```ruby
class HelloWorld < ApplicationJob
  def perform
    puts 'Hello world'
  end
end
```

## SpringプレローダとRailsコンソールでのイニシャライザのテスト
以前に示したイニシャライザでYMLファイルからスケジュールを取得する方法をとる場合、RailsとともにインクルードされるSpringプレローダがRailsコンソールでのテストと干渉する点に注意してください。

Springは変更がない限りイニシャライザをリロードしません。したがって、YMLスケジュールを変更して、それを確認するためにRailsコンソールをリロードしても、Springがそれを反映しないので変更前のように動作します。
変更後の動作を見るには、Railsコンソールの再起動の前にSpringの停止による再ロードを行ってください。

## Unicorn/Railsサーバからのタスク管理

sidekiq-schedulerをsidekiqからではなくUnicorn/Railsのみから起動する場合、イニシャライザを下記のように記述します。

```ruby:config/initializers/sidekiq_scheduler.rb
require 'sidekiq'
require 'sidekiq-scheduler'

puts "Sidekiq.server? is #{Sidekiq.server?.inspect}"
puts "defined?(Rails::Server) is #{defined?(Rails::Server).inspect}"
puts "defined?(Unicorn) is #{defined?(Unicorn).inspect}"

if Rails.env == 'production' && (defined?(Rails::Server) || defined?(Unicorn))
  Sidekiq.configure_server do |config|

    config.on(:startup) do
      Sidekiq.schedule = YAML.load_file(File.expand_path('../../scheduler.yml', __FILE__))
      SidekiqScheduler::Scheduler.instance.reload_schedule!
    end
  end
else
  SidekiqScheduler::Scheduler.instance.enabled = false
  puts "SidekiqScheduler::Scheduler.instance.enabled is #{SidekiqScheduler::Scheduler.instance.enabled.inspect}"
end
```

## ライセンス

MIT License

## コピーライト

Copyright 2013 - 2018 Moove-IT. Copyright 2012 Morton Jonuschat. Some parts copyright 2010 Ben VandenBos.