kakakakakku blog

Weekly Tech Blog: Keep on Learning!

マネジメントのポイントは "44 engineering management lessons" から学んだ

今年は「組織変革」「組織マネジメント」「育成」あたりをテーマにした発表をしたり,ポッドキャストで話をしているので,最近は「マネジメント関連」で相談や質問を受ける機会が増えている.一言で表現すると?みたいに聞かれたときは 「愛情のある無茶振り」 って答えてはいるけど,そもそも一言で表現できるようなものじゃない.

44 engineering management lessons

今まで紹介したことはなかったけど,マネジメントの参考にしている “44 engineering management lessons” という記事がある.本当に大好きな記事で,記事の存在を知った2年前ぐらいから定期的に読み直すようにしている.タイトルの通り,44種類の原則が紹介されていて,大きく7種類に分類されている.どの原則も参考になるけど,その中でも特に好きなものをコメントを添えて紹介したいと思う(翻訳するわけではない).

  • Do
  • Don’t
  • Motivation and culture
  • Emotions and people
  • Tiebreaking and conflict
  • Difficult conversations
  • Rough edge

www.defmacro.org

Do

メンバーの得意な部分を伸ばし,苦手な部分にも挑戦させることで,常に成長できるように意識している.そのときに明確な上下関係を作らず「お互いに高め合う」雰囲気になるように工夫している.また,メンバーと話して成長が鈍化している原因がある場合は,それを取り除いてあげられるようなアクションをする.

1 . Attract, nurture, coach, and retain talent. Talk to engineers to tease out concerns early, then fix them if you can.

ファシリテーターとして,議論を活性化させるのもマネージャーの役割かなと思っているけど,議論が白熱し,全員の合意が得られず収束してしまうような場合は,別の視点の質問をして収束を打破させるようにしている.それでも合意が得られない場合は,最終的な意思決定をする.それを記事では「タイブレーカー」と表現していた.そのためには議論を客観的に俯瞰するスキルも必要になるし,マネージャーが決めたなら信じよう!と思ってもらえるような信頼をメンバーから勝ち得ておく必要もある.

3 . Be the tiebreaker when the development team can’t reach consensus.

様々な情報のハブになることを意識している.特に人脈のハブになることを目指していて「この技術を導入するならこの人にアドバイスをもらおう」など,技術的な情報交換が迅速にできるように工夫している.まさにこれは「トランザクティブ・メモリー」だと思っていて,組織が「ガラパゴス化」しないためにも重要だと考えている.

4 . Be the information hub. Know what every engineer is working on, and help connect the dots that wouldn’t otherwise get connected.

Don’t

マネージャーだからということはなく,ポジションの問題だとは思うけど,マネージャーをメインで担当していると,コードを書く機会がどんどん減り,そこに危機感を持った結果,優先順位の低いまま残っているバグ修正をしてしまうという経験は誰しもあるのではないだろうか?絶対にダメだとは思わないけど,それでコードを書いた気になってしまうのも良くないので,僕は意識的に気を付けている.むしろ,コードを書かないとしても,品質を高く維持できるようなコードレビューをしたり,バグ修正に着手する前の実装デザインを一緒に議論したり,インパクトを残せるアクションを選ぶようにしている.

7 . Personally fix bugs and ship features. You have to write code to remain an effective tiebreaker, but that’s where your coding responsibilities end.

Motivation and culture

信頼残高を勝ち得るには,中長期的に成功し続ける必要がある.目の前のことに一喜一憂するのではなく,粘り強くアクションを起こすようにしている.

11 . Authority isn’t bestowed freely. It’s earned by making good decisions over time.

僕のチームは特に感じていると思うが,とにかく課題解決に対するアイデアは,メンバーに考えてもらうようにしている.考える機会はメンバー全員に平等に与えるべきだと思っているし,決定権もメンバーに与えている.そのときに個人的に意識しているのは「誰かアイデアありませんか?」と全員に問いかけるのではなく「○○さん,何かアイデアありませんか?」と期待値を込めて,名指しで聞くようにしている.そうすると,主体性を持ってアイデアを創出できるようになる.

12 . Don’t make decisions unless you have to. Whenever possible, allow the team to explore ideas and make decisions on its own.

Emotions and people

とにかく重要なのは「雑談」だというのは僕も同意で,世間話のような雑談でも,技術的な雑談でも,日頃から何でも話せる雰囲気を作るのはマネージャーの役割だと思っている.雑談の良いところは,論理的に話す必要がなく,思っていることを何でも気軽に話せるところにあると思う.また1人と雑談するのと,複数人と雑談するのでも意図が違うので,うまく使い分けられると良さそう.

20 . Most people won’t easily share their emotions. Have frequent informal conversations, and tease out everything that might be wrong. Then fix it if you can.

組織的な課題(ボトルネック)を常に客観的に探し,勇気を持って伝えるようにしている.メンバーが誰かしら気付いているのに言いにくいような課題もあるだろうし,メンバーが誰も気付いていないような課題もあると思う.さらに,多くの課題は価値観の違いによって気付かれないことがあるため,前提となる価値観を伝えて認識を合わせるのも必要だと思う.

22 . You’re paid to discover and fix cultural problems your team may not be aware of. Have the courage to say what everyone should know but doesn’t.

Tiebreaking and conflict

これは非常に重要なことだと思う.同じ結論になるとしても,マネージャーが決めるのと,メンバーが提案したアイデアを採用して決めるのでは,大きな差がある.実際にはメンバーが提案する方がインパクトが大きくなる.そのためにも,別の視点の質問をして議論を盛り上げる必要がある.何でもすぐに決めて進めてしまうと,結局メンバーは主体性を持てなくなってしまう.

25 . Don’t judge too quickly; you’re right less often than you think. Even if you’re sure you’re right in any given case, wait until everyone’s opinion is heard.

正直に言って,今までに経験してきたどの組織でも「あの人は苦手だから,あの人のアイデアは全て反対だ」という状況を見たことがある.人間はそもそも感情的なので,完全になくすことは難しいと思うが,頻度が高くなると,組織として何も機能しなくなってしまうので,チームを分割したり,辞めてもらったり,何かしらのアクションを起こすように気を付けている.また,好き嫌いだけではなく「技術的な信頼残高」に依存することも多いと思う.「あの人はバグを多く出すし,適当な性格だから,このアイデアはダメなのでは?」など.マネージャーは常に冷静に本質を見抜く必要があると思う.

29 . When disagreement gets personal or people don’t accept well-reasoned decisions, it turns into conflict.

Difficult conversations

難しい話を後回しにしてしまうというのは非常に良くわかる.ただし,状況をさらに悪化させる可能性もあるため,可能な限り早めに話すようにしている.また,そういうときに Slack や GitHub を使うのではなく,口頭で話すようにしている.日頃から雑談をしていれば,実は容易なことだが,雑談ができていないと,あえて Slack で情報を流してしまったりすると思う.

32 . Have difficult conversations as soon as possible. Waiting will only make a bad situation worse.

Rough edge

“Disagree & Commit” と表現することもあるが,個人的には「意志を表明すること」を重要視していて,さらにもし自分が反対したアイデアが採用された場合も,チームとして合意したのであれば,フルコミットで進めて欲しいと伝えるようにしている.議論の参加者が多いと「発言なし = 賛成」となるのに,ミーティングが終わって席に戻りながら不満を漏らしているメンバーがいたりもする.それでは議論の意味が全くないため,反対ならちゃんと意志表明をしてもらいたいし,どうしても言えなさそうなら,雑談を活用する.

39 . A firm “I’m not ok with that” is usually enough.

これも「意志を表明する」と似ていると思うが,常にニコニコする必要はなく,言うときは言う,感情を表に出すことは良いことだという雰囲気を作るようにしている.空気に流される必要はないと思う.関連して,よくある勘違いとして「チームの意思決定は多数決で行われる」というものがある.議論が本質的ではないと思ったら,空気に流されず「それは違うのでは?」と切り込む勇気を大切にしたいと思っている.

40 . Don’t laugh things off if you don’t feel like laughing them off. Have the courage to show your true emotions.

まとめ

“44 engineering management lessons” は,本当に参考になるマネジメントのバイブルで,定期的に読み直すようにしている.全部で44種類の原則があり,今回紹介しなかったものも多いため,是非一度読んでみてもらいたい.この記事はもっともっと有名になるべきだと思うし,僕の記事をキッカケに少しでも多くの人に届けば嬉しい.

(再掲する)

www.defmacro.org

関連記事

前の部署で僕のメンターをして頂いた @waysaku の記事もとにかく最高で,定期的に読んで「あー,ここ全然できてないなー」と反省をしつつ,改善をしている.特に「喋りすぎなマネージャー」と「条件反射」は今もまだ完全に直せてなくて,司会をメンバーに任せたり,突っ込みたくなるのを5秒我慢したり工夫はしているけど,まだまだという感じ.頑張る!

waysaku.hatenablog.com

S3 の Static Website Hosting でドメイン間のリダイレクトを実現する

ドメイン間でリダイレクトをする必要があり,nginx で頑張るのもなぁ…と思って,もっとお手軽で運用も楽そうな方法を探していたら,以下の記事を発見して,S3 の機能で実現できそうだったので試してみた.記事では別のドメインにリダイレクトをする方法として紹介されているけど,別のサブドメインにリダイレクトをする方法としても使えた.

構成図

ザッと以下のようになる.

f:id:kakku22:20170829202837j:plain

リダイレクト前のドメイン名と S3 バケット名を一致させる

適当に S3 バケットを作ってしまうとダメで,リダイレクト前のドメイン名と S3 バケット名を一致させる必要がある.例として aaa.example.com から zzz.example.com にリダイレクトさせる場合は,S3 バケット名は aaa.example.com になる.S3 バケット名はユニークである必要があるため,既に取得されていたら無理だという点には注意する必要がある.記事にも注釈が入っていた.

Note: S3 bucket names must be globally unique. If the bucket name you need is already in use, this solution cannot be used.

なお,この制約は公式ドキュメントにも記載されていた.

リソースレコードセット名は、Amazon S3 バケット名と一致する必要があります。たとえば、Amazon S3 バケット名が [acme.example.com] である場合、このリソースレコードセット名も [acme.example.com] である必要があります。

docs.aws.amazon.com

リダイレクト設定をする

管理コンソール上の Static website hosting で「リクエストをリダイレクトする」にチェックを入れて,ドメインとプロトコルを設定すると,すぐにエンドポイントからリダイレクトが可能になる.静的ファイルをホスティングするのは今までも使ったことがあったけど,リダイレクトをできることは知らなくて,勉強になった.また「ログ記録」も設定しておくと,アクセス数を把握できるため,基本的には設定しておくべきだと思う.

f:id:kakku22:20170829202908p:plain

Route 53 で S3 にドメインを設定する

記事に書いてある通り,以下のポイントに注意して設定をする.

  • A レコードを選択する
  • Alias は Yes にする
  • ターゲットに表示される S3 website endpoints から作成済の S3 バケットを選択する
    • ちなみに S3 バケットを作ってから,10分間ほど待たないとターゲットに表示されなかったので,時間差があるかも…?
  • Routing Policy は Simple にする

f:id:kakku22:20170829202930p:plain

まとめ

  • Route 53 + S3 を使ってドメイン間のリダイレクトを実現できた
  • ドメイン名と S3 バケット名を一致させるところは注意する

July Tech Festa 2017 で「急成長するサービスを支える DevOps 戦略と組織変革へのアプローチ」という発表をしてきた

今日は July Tech Festa 2017 ( #JTF2017 ) で発表をしてきたので,さっそく参加レポートをまとめた.運営の皆さま,お疲れさまでした!

発表資料

感想 / 補足など

今回は「急成長するサービスを支える DevOps 戦略と組織変革へのアプローチ」というタイトルで発表をした.タイトルが良かったのかもしれないけど,会場はほぼ満席で嬉しかった(空席が目立つ会場もチラホラあったため).

発表内容としては大きく3点で,DevOps の話と組織変革の話をバランス良く混ぜることを意識した.特に「組織変革」の部分が重要で,組織課題が残っている状態でどんなに頑張ってもサービスは伸びないと思っている.「DevOps は文化である」ことはよく知られているのに,文化として根付かせるために変化を実践できている人は案外少ないのでは?

  • 急成長を支える ( ≒ 守る ) ための DevOps 戦略
  • 急成長を支える ( ≒ 攻める ) ための DevOps 戦略
  • DevOps 戦略に必須だった組織変革へのアプローチ

また,今回は発表が45分間と長く,参加者とシンクロしながら発表したいなと思っていたため,最初にアイスブレイクとして「ノンバーバルなコミュニケーション」の例として「うなずく練習」をしてもらったりもした.ミーティングの場でも重要なスキルなので,是非意識してもらえると良いのでは!

関連記事

モニタリング + AWS Well-Architected Framework の話は,以下に記事を公開している.

developers.cyberagent.co.jp

「Aurora 事例祭り」で話した Aurora 移行の詳細は,以下の記事を公開している.

kakakakakku.hatenablog.com

経営層を巻き込んだ話のインタビュー記事も公開している.

developers.cyberagent.co.jp

コミュニケーションに悩んでいる人には以下の記事が参考になるはず.

kakakakakku.hatenablog.com

関連書籍

発表資料の中で大量の本を紹介した.特に厳選すると以下かな!

「構成ドリフト」と「スノーフレークサーバ」の話は Infrastructure as Code 本に載っている.

Infrastructure as Code ―クラウドにおけるサーバ管理の原則とプラクティス

Infrastructure as Code ―クラウドにおけるサーバ管理の原則とプラクティス

組織変革なら Fearless Change をオススメする!質疑応答のときに Fearless Change を監訳された川口さんとお話できてすごく嬉しかった!(ただ質疑応答はテンパって変な回答になってしまった...あああw)

Fearless Change アジャイルに効く アイデアを組織に広めるための48のパターン

Fearless Change アジャイルに効く アイデアを組織に広めるための48のパターン

  • 作者: Mary Lynn Manns,Linda Rising,川口恭伸,木村卓央,高江洲睦,高橋一貴,中込大祐,安井力,山口鉄平,角征典
  • 出版社/メーカー: 丸善出版
  • 発売日: 2014/01/30
  • メディア: 単行本(ソフトカバー)
  • この商品を含むブログ (16件) を見る

第1章にまとまってる「ラストマイル」の話は必読だと思う.

ThoughtWorksアンソロジー ―アジャイルとオブジェクト指向によるソフトウェアイノベーション

ThoughtWorksアンソロジー ―アジャイルとオブジェクト指向によるソフトウェアイノベーション

  • 作者: ThoughtWorks Inc.,株式会社オージス総研オブジェクトの広場編集部
  • 出版社/メーカー: オライリージャパン
  • 発売日: 2008/12/27
  • メディア: 単行本(ソフトカバー)
  • 購入: 14人 クリック: 323回
  • この商品を含むブログ (81件) を見る

写真

f:id:kakku22:20170827140819j:plain

A00 : 「ITエンジニアリングの本質」を考える / @enakai00

ネットワークの話は難しい部分も多かったが,直面する課題に対して考え続けるという思想こそが,エンジニアリングの本質なんだろうなと感じることができ,素晴らしかった.特に資料の最後にある「技術的制約に対する洞察力」があるからこそ,現在の常識を壊すことができているんだと思う.

www.slideshare.net

B10 : Web サービスの信頼性を守るための取り組み / @rrreeeyyy

  • SRE : サイトの信頼性の全てに責任範囲を持つ
    • Software Engineering
      • 自動化スクリプト / ツール実装 / プロダクションコードも直す
    • Systems Engineering
      • 1回の作業で永続的な改善を生む作業
      • アーキテクチャ設計
    • Toil
      • 繰り返し行われる手動オペレーション
      • トイルを無くすことが重要
    • Overhead
      • 管理的な作業
      • 採用/ 評価 / トレーニング
  • プロジェクトごとに,1人 SRE がアサインされて,設計相談やレビューをする
  • アプリケーションの負荷テストを実施する
    • そもそも,エンジニアが負荷テストを実施している時点で,Toil なのでは?
  • ロードバランサの改善

日本語訳の SRE 本は買ってあるけど,まだちゃんと読めていなかったため,理論的な部分と実践的な部分の話が聞けて,個人的には1番面白かった.エラーバジェットも実践しているという点も興味深かった.また,定義上は Overhead にあたり新卒研修も,改めて理解を整理できるだろうし,担当する SRE 側にも大きなメリットがあるだろうなと感じた.

C20 : 若手エンジニアの私が MBA(経営学修士)を取りに行った話 / @nari_ex

  • 上司の薦めで,経営大学院に入学することになった
  • 組織行動 (OB) と 人的資源管理 (HRM)
  • 理論と実践は違う − フィードバックをもらったり,ストレングスファインダーを試して,自分を知る工夫をした

自己過信によって失敗が続き,自分を見つめ直す中で,マネジメントに興味が出て,上司の薦めで経営大学院に入学したという話だった.個人的には,話を聞いていて全体的に甘いなという印象を受けた.マネジメントに限った話ではないけど,理論武装しているだけではダメで,実践してこそ価値があると思っている.そもそもメンバーからの信頼が無ければマネジメントなんてできないため,まずはメンバーから信頼されるためにどうしたら良いか?を考えてみるのが良いのではないかなと感じた.あと結局のところ,ビジネスの成功や,メンバーの成長など,目に見える成果が出ていなければ,マネジメントは成功したとは言えない点も重要だと思う.MBA は非常に興味あるなー!

A40 : Kubernetes でクラスタリングして解決したことと新たに出てきた課題 / @koudaiii

  • 新規サービスの立ち上げを早くするために Kubernetes を導入した
  • rails c を実行するコンテナ,rake db:migrate を実行するコンテナなどもある
  • 80個以上もあったチェック項目が,簡単になった
  • Kubernetes によって,スケールも容易になった

僕のところだと ECS を使っているため,Kubernetes は全然詳しくないが,大規模な Kubernetes 導入事例として非常に面白かった.特にサービス数が 9 → 60 に増えているという伸びが,まさに Kubernetes 導入が成功だったことを示していると思う.Kubernetes のマスターも3台構成にして,SPOF を回避しているとのことだった.

まとめ

  • July Tech Festa 初参加だった(なぜ7月開催じゃないのに July なんだろう...w)
  • 発表できて良い経験になった
    • LT と違って45分間もあると幅広く話せるなーと感じられた

「みんなのGo言語」には現場で使える実践テクニックが本当に書いてあった

発売されたのは約1年前だけど,最近仕事で Golang を書いているので,実践的なテクニックを学ぶために今さらながら「みんなのGo言語」を読んだ.WEB+DB PRESS のようなカジュアルさのある本で,1日でサクッと読むことができた.読み終わった後に,学んだことを実際に試してみたりもして,とにかく即戦力のある1冊だと感じた.

みんなのGo言語【現場で使える実践テクニック】

みんなのGo言語【現場で使える実践テクニック】

golint -min_confidence

今までも golint は使っていたけど,各項目に Confidence というレベル設定があって -min_confidence オプションで閾値を決められるというのは知らなかった.golint の README にも書いてなかったけど,実際に golint.go を読んだら実装があり,デフォルトは 0.8 になっていた.もっと厳しくても良いのではないかなと思って,さっそく 0.6 に変更した.0.8 → 0.6 に変更したことによって追加で検知できたのは以下で,エラーメッセージの1文字目も小文字にするのがお作法なのかぁー,という気持ちだった.

error strings should not be capitalized or end with punctuation or a newline

golint -set_exit_status

デフォルトのまま golint を実行すると,コマンドの終了ステータスが 0 で返ってきてしまうため,今までは CircleCI で以下のようなワークアラウンド実装をしていた.

test:
  override:
    - test -z "$(golint $(glide novendor) | tee /dev/stderr)"

しかし -set_exit_status オプションを使うと,指摘が出た場合に 1 が返ると本書に書いてあった.よって,以下のように書き換えることができて非常に助かった.

test:
  override:
    - golint -set_exit_status $(glide novendor)

Makefile

最初は golint と go vet を circle.yml に直接書いていたけど,僕のチームに Golang のアドバイザーとして入ってもらっているメンバーに「Makefile で書きましょう」と教えてもらって,今は Makefile を使っている.まさに同じことが本書にも書かれていて,参考になった.なお make help の部分は本書で紹介されていた make2help は使ってなくて,以下の記事で紹介されていた方法を使っている.

postd.cc

ザッとこのような感じになり,CircleCI でも make lint を実行するだけで良いという状態になっている.

help:
    @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'

setup: ## Setup some tools
    go get -u github.com/Masterminds/glide
    go get -u github.com/golang/lint/golint

lint: ## Run the Golint in all directories without /vendor
    go vet $$(glide novendor)
    golint -min_confidence 0.6 -set_exit_status $$(glide novendor)

goroutine の停止

goroutine をどのように停止するかという話があり,まさに似たような実装を書く予定があって,チャネルをクローズするイメージで考えていたので,Go 1.7 以降だと context を使うのが推奨であると知れたのは非常に良かった.

実際に本書に載っているコードを写経して動作確認をしてみて,これだけだとあまりチャネルをクローズする実装と変わらないと思ったけど,最後に context.WithTimeout を使えば,タイムアウトの考慮もできるようになるメリットがあるという紹介があった.

とは言え,本書だけだと記載が少なく概要レベルの紹介だったため,以下の記事を読んで,より理解を深められたような気がする.

ベンチマーク

テストの章では,ベンチマークの機能を知った.実際に写経をして試したりもした.

cat.go

package cat

import "bytes"

func cat(ss ...string) string {
    var r string
    for _, s := range ss {
        r += s
    }
    return r
}

func buf(ss ...string) string {
    var b bytes.Buffer
    for _, s := range ss {
        b.WriteString(s)
    }
    return b.String()
}

cat_test.go

package cat

import "testing"

func seed(n int) []string {
    s := make([]string, 0, n)
    for i := 0; i < n; i++ {
        s = append(s, "a")
    }
    return s
}

func bench(b *testing.B, n int, f func(...string) string) {
    b.ReportAllocs()
    for i := 0; i < b.N; i++ {
        f(seed(n)...)
    }
}

func BenchmarkConcatenate(b *testing.B) {
    benchCases := []struct {
        name string
        n    int
        f    func(...string) string
    }{
        {"Cat", 3, cat},
        {"Buf", 3, buf},
        {"Cat", 100, cat},
        {"Buf", 100, buf},
        {"Cat", 10000, cat},
        {"Buf", 10000, buf},
    }
    for _, c := range benchCases {
        b.Run(fmt.Sprintf("%s%d", c.name, c.n),
            func(b *testing.B) { bench(b, c.n, c.f) })
    }
}

サブベンチマークという機能を使って書くことで,テーブルドリブンに書けるのは良かった.以下のような結果となり,文字列結合をする要素数が多くなればなるほど bytes.Buffer を使った方がパフォーマンスが良いということを確認することができた.すぐに仕事に活かせる知見だった.

$ go test -bench .
BenchmarkConcatenate/Cat3-8            10000000          164 ns/op         54 B/op         3 allocs/op
BenchmarkConcatenate/Buf3-8            10000000          181 ns/op        163 B/op         3 allocs/op
BenchmarkConcatenate/Cat100-8            300000         5794 ns/op       7520 B/op       100 allocs/op
BenchmarkConcatenate/Buf100-8           1000000         1938 ns/op       2160 B/op         4 allocs/op
BenchmarkConcatenate/Cat10000-8             200      7414202 ns/op   53327801 B/op     10000 allocs/op
BenchmarkConcatenate/Buf10000-8           10000       188879 ns/op     211424 B/op        11 allocs/op
PASS

その他

Windows の考慮,リフレクションの話などは,一通り読んだけど,実際に使う場面がありそうなときに改めて読み直そうかなと思っている.CLI の話は前に Togoo を実装したときに学んだ内容が多く含まれていて,復習の意味も兼ねて知識を整理することができた.

読んでいて少し気になったこと

初版だからある程度はしょうがないとは思うけど,少し誤植が多いなとは思った.また,誤植ではないものの,文字間隔が広くなっているのが気になる行もあった.これは組版の問題だとは思うけども.誤植一覧は以下に公開されていた.

まとめ

  • 仕事でさっそく使えるような,実践的なテクニックが学べる1冊だった
  • 実装のテクニックだけではなく,OSS として公開されているパッケージが多く紹介されているのも良かった

関連記事

紹介されていた「プログラミング言語Go」も購入したので,さっそく読むぞ!

suzuken.hatenablog.jp

Golang でジョブのスケジューリング実行ができる JobRunner を試した

最近 Golang でジョブを非同期にスケジューリング実行するような仕組みを実装していて,要件に合っていた JobRunner を使った.特徴としては,様々なスケジューリングを定義できる点と,API を起動してプロセスを常駐させる点にある.

github.com

基本実装

最初に動く実装を載せておく.ザッと書いてみると,このようになる.5秒間隔で MyJob を実行していて,標準出力にログを吐いている.また Gin を起動してプロセスを常駐させている.さらに JobRunner のステータスを API から取得できるようにもしている.

package main

import (
    "fmt"
    "net/http"

    "github.com/bamzi/jobrunner"
    "github.com/gin-gonic/gin"
)

// MyJob ...
type MyJob struct {
}

func main() {
    jobrunner.Start()
    jobrunner.Schedule("@every 5s", MyJob{})

    gin.SetMode(gin.ReleaseMode)
    r := gin.Default()
    r.GET("/jobrunner/status", JobJSON)
    r.Run(":8080")
}

// JobJSON ...
func JobJSON(c *gin.Context) {
    c.JSON(http.StatusOK, jobrunner.StatusJson())
}

// Run ...
func (e MyJob) Run() {
    fmt.Println("Run MyJob!")
}

実行すると,以下のように5秒間隔でログが出力できる.

$ go run main.go
[JobRunner] 2017/08/13 - 11:10:00 Started...
Run MyJob!
Run MyJob!
Run MyJob!
Run MyJob!
Run MyJob!
Run MyJob!

ステータスは以下のように取得できる.

$ curl -s http://localhost:8080/jobrunner/status | jq .
{
  "jobrunner": [
    {
      "Id": 1,
      "JobRunner": {
        "Name": "MyJob",
        "Status": "IDLE",
        "Latency": "77.204µs"
      },
      "Next": "2017-08-13T11:15:14+09:00",
      "Prev": "2017-08-13T11:15:09+09:00"
    }
  ]
}

スケジューリング

JobRunner では,様々なスケジューリングを定義できる.内部的には gopkg.in/robfig/cron.v2 を利用しているため,cron 形式でスケジューリングを定義することができたり,シンプルに一定間隔で実行することもできる.例えば,以下のように定義することができる.

// Schedule : 5秒間隔で実行する
jobrunner.Schedule("@every 5s", MyJob{})

// Schedule : 1時間間隔で実行する
jobrunner.Schedule("@hourly", MyJob{})

// Schedule : 2分間隔で実行する (cron 形式)
jobrunner.Schedule("* */2 * * * *", MyJob{})

// Every : 10分間隔で実行する
jobrunner.Every(10*time.Minute, MyJob{})

// In : 10秒後に実行する
jobrunner.In(10*time.Second, MyJob{})

// Now : 即時実行する
jobrunner.Now(MyJob{})

詳しくは cron.v2 のドキュメントに載っている.

godoc.org

API

今回は Gin で実装をしたが,JobRunner では README に書かれている通り,特定の API に依存することはなく,好きなものを選択することができる.

  • Gin
  • Echo
  • Martini
  • Beego

などなど.

並列実行

README にあまり詳しく書かれていないが,JobRunner は並列実行をサポートしている.init.go を読んで,実際に動作確認をして,やっと理解できた.

func Start(v ...int) {
    MainCron = cron.New()

    if len(v) > 0 {
        if v[0] > 0 {
            workPermits = make(chan struct{}, v[0])
        } else {
            workPermits = make(chan struct{}, DEFAULT_JOB_POOL_SIZE)
        }
    }

    if len(v) > 1 {
        if v[1] > 0 {
            selfConcurrent = true
        } else {
            selfConcurrent = false
        }
    }

    MainCron.Start()

    fmt.Printf("%s[JobRunner] %v Started... %s \n",
        magenta, time.Now().Format("2006/01/02 - 15:04:05"), reset)

}

まず jobrunner.Start() の引数は int の可変長引数になっている.結局のところ,第二引数を指定しない限りは selfConcurrent = false となるため,並列実行はされないようになっていた.

  • 第一引数 : プールサイズ(デフォルト : 10)
  • 第二引数 : 並列実行数(デフォルト : 並列実行なし)

検証 : jobrunner.Start(10)

まず,ログ出力を拡張して,30秒待機するようにしてみた.

package main

import (
    "fmt"
    "net/http"
    "time"

    "github.com/bamzi/jobrunner"
    "github.com/gin-gonic/gin"
)

// MyJob ...
type MyJob struct {
}

func main() {
    jobrunner.Start(10)
    jobrunner.Schedule("@every 5s", MyJob{})

    gin.SetMode(gin.ReleaseMode)
    r := gin.Default()
    r.GET("/jobrunner/status", JobJSON)
    r.Run(":8080")
}

// JobJSON ...
func JobJSON(c *gin.Context) {
    c.JSON(http.StatusOK, jobrunner.StatusJson())
}

// Run ...
func (e MyJob) Run() {
    fmt.Println("[Start] Run MyJob!")
    time.Sleep(30 * time.Second)
    fmt.Println("[End] Run MyJob!")
}

この状態で jobrunner.Start(10) を実行すると,以下のようになった.30秒の待機が優先されて,並列実行はされなかった.

$ go run main.go
[Start] Run MyJob!
[End] Run MyJob!
[Start] Run MyJob!
[End] Run MyJob!
[Start] Run MyJob!
[End] Run MyJob!
[Start] Run MyJob!
[End] Run MyJob!

検証 : jobrunner.Start(10, 5)

次に jobrunner.Start(10, 5) と書いて,並列実行数を指定してみた.

func main() {
    jobrunner.Start(10, 5)
}

すると,並列実行となった.JobRunner を使うときには,理解しておくべきポイントだと思う.

$ go run main.go
[Start] Run MyJob!
[Start] Run MyJob!
[Start] Run MyJob!
[Start] Run MyJob!
[Start] Run MyJob!
[Start] Run MyJob!
[End] Run MyJob!
[Start] Run MyJob!
[Start] Run MyJob!
[End] Run MyJob!
[Start] Run MyJob!
[End] Run MyJob!

まとめ

  • JobRunner を使うと柔軟なスケジューリング定義でジョブを実行することができる
  • Gin や Echo など,任意の API を起動してプロセスを常駐させる仕組みになっている
  • 並列実行をする場合は jobrunner.Start() の引数を意識する