君の瞳はまるでルビー - Ruby 関連まとめサイト

Heroku で clockwork 等を使い長時間動作する定期処理を実装する場合の注意点

最終更新: 2015-04-16 (木) 18:54:45 (1616d)

概要

Heroku で比較的短い時間で動作する定期処理を実装するのは簡単です。

しかし、長時間動作する定期処理を書く場合には注意が必要です。注意が必要となる Heroku の仕様に関する記述を以下に示します。

Dynos are also restarted at least once per day

1日1回は再起動するよ!

When the dyno manager restarts a dyno, the dyno manager will request that your processes shut down gracefully by sending them a SIGTERM signal.

再起動するとき SIGTERM を送るからよろしく!

If any processes remain after ten seconds, the dyno manager will terminate them forcefully with SIGKILL.

10 秒経っても生き残ってたら SIGKILL で息の根を止めるね♪

1日1回の再起動は避けることができず、時間が決まっているわけではなく予測が立たないためスケジュールで避けるといったことができません(たぶん)。

解決法

長時間動作する定期処理を書く場合は、以下の点に考慮する必要があります。

  • 中断および再開を可能にする。
    • 処理の進捗を保存しなければならない。
    • 処理の進捗に応じて再開できるようにしなければならない。
  • 任意のタイミングで 10 秒以内の途中中断を可能にする。
    • 外部リソースへの出力に関する処理に特に注意が必要である。データベースやファイルサーバなど。
      • 例えばデータベースアクセスではトランザクションを利用し、中断時ロールバックなどを行う。
      • 複数外部リソースへ出力している場合は、一貫性を崩さない考慮が必要となる。
    • 中断の可能性が常にあるため、進捗の保存単位が大きくなりすぎないようにしなければならない。

実装例

例えば以下のような形で対処することになるのではと考えています。

コンセプトを示すものであって、実際に動作するものではありません。

#coding: UTF-8

require 'clockwork'

running = true

trap('TERM') do
  finish_work
end

class WorkInterruptionException < StandardException; end

module Clockwork
  handler do |job|
    progress = get_work_progress()
    while running
      begin
        case progress
        when 0
          short_work
        when 1
          long_work
        # ... 他にもたくさんの作業があるとする ...
        when 10  # 最後の作業だったとする
          last_work
          finish_wok
        end
        progress = progress + 1
        save_work_progress(progress)
        commit_work
      rescue WorkInterruptionException => e
        rollback_work
        break
      end
    end
  end

  every(1.day, 'midnight.job', :at => '00:00')
end

def get_work_progress
  # 現在の作業の進捗をデータベースなどから取得して返す。
  # 初回起動だった場合は 0 を返す。
  # それ以外の場合は 0, 1, 2, 3 と作業が進むにつ入れて値が 1 ずつ増えていくものとする。
end

def save_work_progress(progress)
  # 現在の作業の進捗をデータベースに保存する
end

def finish_work
  running = false
end

def rollback_work
  # 作業単位のトランザクションを全てロールバックする
end

def commit_work
  # 作業単位のトランザクションを全てコミットする
end

def short_work
  # 1つ目の作業を行う。10 秒以内に終わる程度の作業とする
end

def long_work
  # 2つ目の作業を行う。処理対象が多数あり、10 秒を超える可能性がある。
  # 処理対象は targets 配列に格納されていると想定する。
  # 全ての処理は1つのトランザクションで完結していなければならない。
  targets.each do |target|
    raise WorkInterruptionException.new if !running

    # target に対する処理を行う
  end
end

再開の考慮とかまだ足りてないですね…。

関連

コメント

本ページの内容に関して何かコメントがある方は、以下に記入してください。

コメントはありません。 コメント/heroku/clockwork/long_period

お名前: