Slide 1

Slide 1 text

デプロイで止まらない
 バッチ処理を求めて
 Heroku Meetup #27 "Heroku Vitamin!"
 2019-12-13 @masutaka


Slide 2

Slide 2 text

自己紹介
 ● 増田貴士(@masutaka)
 ● 株式会社フィードフォース
 ● EC Boosterの雑用係
 ● 今回説明するバッチ処理もそれなりに面倒を見ている
 ● 最近はPromoteを使ったブランチ戦略を考え中
 https://www.feedforce.jp/ 


Slide 3

Slide 3 text

Heroku Meetup登壇履歴
 ● Heroku Meetup #23 "Heroku Dynamite!!"
 ○ 『先々週 GA になった heroku.yml を使った Docker Deploy の紹介』 
 ● Heroku Meetup #25 "Heroku Ghost"
 ○ 『デプロイ元をCircleCIからHerokuに乗り換えた話。ヒヤリもあるよ』 
 ● Heroku Meetup #27 "Heroku Vitamin!" ← 今日!
 ○ 『デプロイで止まらないバッチ処理を求めて』 
 https://speakerdeck.com/masutaka 


Slide 4

Slide 4 text

EC Booster
 国内初のGoogleショッピング広告自動運用ツール
 https://ecbooster.jp/ 


Slide 5

Slide 5 text

EC Boosterの技術スタック
 Heroku App Web Dyno GraphQL Client (apollo-client) GraphQL Server (graphql-ruby on Rails) Worker Dyno Chrome (headless mode) chromedriver sidekiq Heroku Postgres Heroku Redis

Slide 6

Slide 6 text

前提


Slide 7

Slide 7 text

前提
 ● EC Boosterの全てのバッチ処理は冪等性を持たせており、 Dynoが再起動しても問題はない


Slide 8

Slide 8 text

課題


Slide 9

Slide 9 text

課題
 ● とは言え、なんとなく再起動して欲しくないバッチ処理もある。 デプロイのたびに少しドキドキ
 ○ 例: ChromeがHeadless modeで動作する、書き込みを伴うバッチ処理 
 ● 頻度が少ないバッチ処理のために、割と強いDynoを起動し続 けるコストも課題に感じていた


Slide 10

Slide 10 text

Dynoの再起動とは


Slide 11

Slide 11 text

Dynoの再起動とは
 ● 稼働中のDynoが破棄され、新しいDynoが起動する
 ● ローカルファイルシステムは破棄されることとなる
 https://devcenter.heroku.com/articles/dynos#restarting 


Slide 12

Slide 12 text

いつ再起動されるか?
 ● デプロイした時
 ● config vars(環境変数)を変更した時
 ● Add-onを変更した時
 ● 手動再起動($ heroku ps:restart)した時
 ● 以上が24時間(+ ランダムで216分)行われなかったら
 ○ Cyclingと呼ばれる
 ● その他、必要に応じて
 https://devcenter.heroku.com/articles/dynos#restarting 


Slide 13

Slide 13 text

再起動の流れ
 1. DynoにSIGTERMが送られる
 2. アプリケーションは30秒以内(できればもっと早く)に終了する 必要がある
 3. 終了しない場合、DynoにSIGKILLが送られ、強制停止させられ る
 https://devcenter.heroku.com/articles/dynos#shutdown 


Slide 14

Slide 14 text

今までのEC Boosterのバッチ処理


Slide 15

Slide 15 text

今までのEC Boosterのバッチ処理
 ● SidekiqがActive Job(Rails)のアダプタとして動作
 ● Sidekiqでジョブが起動し、バッチ処理が実行される


Slide 16

Slide 16 text

Heroku App Web Dyno GraphQL Client (apollo-client) GraphQL Server (graphql-ruby on Rails) Worker Dyno Chrome (headless mode) chromedriver sidekiq Heroku Postgres Heroku Redis

Slide 17

Slide 17 text

デプロイ時の動作
 1. デプロイすると、SidekiqはSIGTERMを受け取る
 2. 実行中のジョブは一旦キュー(Redis)に戻される
 3. 新しいDynoが起動したら、ジョブが再実行される


Slide 18

Slide 18 text

今動いているバッチ処理は
 止めないでくれ!


Slide 19

Slide 19 text

改善しました!


Slide 20

Slide 20 text

現在のEC Boosterのバッチ処理


Slide 21

Slide 21 text

現在のEC Boosterのバッチ処理
 ● Sidekiqでジョブを動かすところまでは同じ
 ● そのジョブはバッチ処理を行わず、バッチ処理を実行する One-Off Dynoを起動する


Slide 22

Slide 22 text

Heroku Platform APIを使って、One-Off Dynoを作成
 Heroku App Worker Dyno sidekiq One-Off Dyno Rails runner 作成 https://devcenter.heroku.com/articles/platform-api-reference 
 ● One-Off Dyno上でバッチ処理を行うRails runnerが起動される 
 ● バッチ処理が終わると、One-Off Dynoは終了&破棄される 


Slide 23

Slide 23 text

Rubyでの実装例(platform-api gemを利用)
 dyno = ::PlatformAPI::Dyno.new( ::PlatformAPI.connect('{{Heroku API Key}}'), ) dyno.create( '{{Heroku App Name}}', attach: false, # (1) command: %!rails r "SampleCoreJob.perform_now(shop_id: '#{shop.id}', global_executions: #{global_executions})"!, force_no_tty: nil, # (2) size: 'performance-m', type: 'run', time_to_live: 1.hour, ) # (1) false: 標準出力/標準エラー出力をアプリログに流す # (2) "attach: true" の時に意味があるようだ https://rubygems.org/gems/platform-api 


Slide 24

Slide 24 text

良かったこと
 ● Dynoの寿命を自分でコントロール出来た
 ○ 最大24時間。もちろん勝手に再起動しない 
 ● 同時実行数はあまり気にしなくて良かった
 ○ Heroku App単位で100 Dynoまで同時起動できるとのこと。増やすことも可能らしい 
 ● 強いDynoを使っても、ほとんど料金が増えなかった
 ○ 2019/11は$1.60
 ○ もちろんバッチ処理の起動時間によります 


Slide 25

Slide 25 text

悪かったこと
 ● リトライが面倒
 ● リトライが面倒
 ● リトライが面倒


Slide 26

Slide 26 text

リトライが面倒だった(図だと簡単に見える...)
 Heroku App Worker Dyno sidekiq One-Off Dyno Rails runner 作成 リトライ

Slide 27

Slide 27 text

サンプルコード
 https://git.io/Je9HM


Slide 28

Slide 28 text

専用のリトライメソッドを作成した
 ● 今まではActive Jobのretry_on等で自身をリトライすればよかった
 ● そのままの実装だと、One-Shot Dynoではなくworker Dyno上でリトライ されてしまう
 ● 自身ではなく、自身をキックするジョブをリトライする必要がある
 ● 今回はretry_onを改造したretry_oneshot_onというメソッドを作った


Slide 29

Slide 29 text

状態を自前で記憶する必要があった
 ● 今まではActive Jobがインスタンスを引き継いでくれた
 ○ 何回目のリトライかも覚えていてくれる(executions)
 ● global_executionsという概念をでっち上げ、最初のジョブから丁寧に渡 すようにした
 ● おかげさまでActive Jobの実装に詳しくなれた


Slide 30

Slide 30 text

今後の展望
 ● 今よりも同時実行数が上がれば、Active JobやSidekiqを捨て て自前で実装するかも
 ○ 現在は1日10回未満の実行 
 ● Dynoの同時実行数100を目一杯使いつつ、超えないようなジョ ブキューを


Slide 31

Slide 31 text

まとめ
 ● 途中で止めたくないバッチ処理があったので、One-Off Dyno での実行に変えた
 ● メリットだらけで満足いく結果となった
 ● ただしリトライ処理の実装は面倒で、工夫が必要だった


Slide 32

Slide 32 text

ご清聴ありがとうございました