分子に1を足し、分母に2を足すだけで予測が良くなる話

コインを投げを観測し、コインの表になる確率を予測するとき、みなさんはどのように予測するでしょうか。 (コイン投げに限らず、表か裏のように二値になるような予測であれば、例えば、広告のクリック率や、単語の出現率、ナンパの成功率でもなんでもいいです。)

コインが表になる確率が0から1まで一様だ(まんべんなく出る)とすれば、n回投げてs回表を観測したら、平均であるs/nをその確率として予測するのではないでしょうか。

この方法をもっと複雑な言い方をすれば最尤推定(maximum likelihood; ML推定)とよびます。コインが表になる確率が一様という事前確率まで分かっている前提ならば、これは最大事後確率推定(maximum a posteriori estimation; MAP推定)ともいえます。なんか最強っぽいですよね。

他に方法があるのでしょうか。スムージングという方法もあります。スムージングの中でも最も単純なのが ラプラススムージング とよばれる方法です。最尤推定の式の分子に1を加算して、分母に2を加算する方法です。

シミュレーションで確認

ではどちらがいいのでしょうか? 実際にシミュレーションして確認してみましょう。

以下のプログラムではまず真の確率 p を求めておき、そのpの確率に基づきn回コイン投げをして、その観測に基づき、最尤推定とスムージングによる推定をするようになっています。そして真の確率 p と答え合わせをします。最尤推定の方が近かった場合には+1を、あいこだった場合には+0.5をします。このゲームをt回繰り返し、最終的に最尤推定の勝率を表示します。

import random

# --------------- 初期設定
t = 100000 # 確率あてゲーム回数
n = 3    # コイン投げ数
a = 0.01  # スムージングパラメータ(分子)  
b = 0.02  # スムージングパラメータ(分母)

# --------------- 最終結果用変数初期化
win_sm = 0   # ラプラススムージングが勝った回数
sum_ml = 0.0 # 最尤推定の誤差の合計
sum_sm = 0.0 # スムージングの誤差の推定

# --------------- 確率あてゲーム(t回繰り返し)
for i in range(t):
    p = random.random() # 真の表(おもて)の確率
    
    # ------------ コイン投げによる施行
    s = 0  # 表(おもて)の回数を初期化
    for j in range(n): 
        if p > random.random(): # 乱数が真の表の確率以下なら
            s += 1              # 表になる
    
    # ------------ 推定 
    ml = float(s)/n   # 最尤推定
    sm = (s+a)/(n+b)  # スムージングによる推定
    
    # ------------ 結果判定
    diff_ml = (p-ml)**2   # 最尤推定の二乗誤差
    diff_sm = (p-sm)**2   # スムージングの二乗誤差
    if diff_sm < diff_ml:    # ラプラススムージングの誤差のほうが小さければ、
        win_sm += 1          # ラプラススムージングの勝ち++
    elif diff_ml == diff_sm: # 同じならばあいこ
        win_sm += 0.5        # あいこのときは +0.5
    sum_ml += diff_ml # 最尤推定の二乗誤差を集計する
    sum_sm += diff_sm # スムージングの二乗誤差を集計する

    # ------------- 表示 (デバッグ用)
    if t <= 50:
        print "s/n: %d/%d, p: %f, ml: %f, sm: %f" % (s, n, p, ml, sm)

# ---------- 最終結果表示
win_sm_rate = float(win_sm)/t
diff_ratio = sum_sm/sum_ml
print "win sm rate: %f, sum_sm/sum_ml: %f" % (win_sm_rate, diff_ratio)

実行結果

s/n: 0/3, p: 0.046582, ml: 0.000000, sm: 0.200000
s/n: 2/3, p: 0.526964, ml: 0.666667, sm: 0.600000
s/n: 2/3, p: 0.420249, ml: 0.666667, sm: 0.600000
s/n: 2/3, p: 0.259805, ml: 0.666667, sm: 0.600000
s/n: 1/3, p: 0.387191, ml: 0.333333, sm: 0.400000
s/n: 1/3, p: 0.187301, ml: 0.333333, sm: 0.400000
s/n: 1/3, p: 0.530644, ml: 0.333333, sm: 0.400000
s/n: 1/3, p: 0.072210, ml: 0.333333, sm: 0.400000
s/n: 3/3, p: 0.968749, ml: 1.000000, sm: 0.800000
s/n: 0/3, p: 0.224703, ml: 0.000000, sm: 0.200000
s/n: 0/3, p: 0.053139, ml: 0.000000, sm: 0.200000
s/n: 0/3, p: 0.184094, ml: 0.000000, sm: 0.200000
s/n: 0/3, p: 0.108546, ml: 0.000000, sm: 0.200000
s/n: 2/3, p: 0.509613, ml: 0.666667, sm: 0.600000
s/n: 3/3, p: 0.284755, ml: 1.000000, sm: 0.800000
s/n: 0/3, p: 0.009353, ml: 0.000000, sm: 0.200000
s/n: 1/3, p: 0.500349, ml: 0.333333, sm: 0.400000
s/n: 1/3, p: 0.044538, ml: 0.333333, sm: 0.400000
s/n: 0/3, p: 0.080180, ml: 0.000000, sm: 0.200000
s/n: 0/3, p: 0.471813, ml: 0.000000, sm: 0.200000
win sm rate: 0.600000, sum_sm/sum_ml: 0.693820

最後の win sm rate: 0.600000 が最尤推定の勝率を表しています。ゲームの回数 t が20回というのはちょっと少ないので何度も実行するとこの勝率はかなり大きく変化します。そこでゲームの回数を思い切って t = 100000 くらいに設定してみましょう。

win sm rate: 0.597200, sum_sm/sum_ml: 0.595932

ラプラススムージングの勝率は6割程度もあるようですね。なんと、分子に1を足して、分母に2を足すというラプラススムージングの方が良いのです。

コイン投げを3回しか観測していないから悪いんじゃないか、という話もあります。ではコイン投げの回数nを20まで増やしてみましょう。

win sm rate: 0.529480, sum_sm/sum_ml: 0.911000

ラプラススムージングの勝率はかなり0.5に近づきましたが、まだ勝っていますね。では1000まで増やしてみましょう。(ちょっと時間がかかります)

win sm rate: 0.502880, sum_sm/sum_ml: 0.998269

ラプラススムージングの勝率はほとんど0.5に近づきましたが、まだ勝っていますね。実際1000回もチャンスをくれることというのもないかもしれませんが…。

分子に1を足して分母に2を足すだけで、予測が良くなってしまう、そんなお話でした。

なぜなの

最尤推定や最大事後確率推定では、最も尤度が高い確率を選んだにも関わらず、分子に1を足して分母に2を足すだけのラプラススムージングに負けてしまいました。この敗因は、最も高いところだけしか見ていないこと にあります。

次のグラフは、コイン投げで、表が1回、裏が4回のときのβ分布です。

f:id:nakano-tomofumi:20170407144144p:plain

最尤推定である、1/5 = 0.2 のところで最大となっていますが、面積的にはどうでしょうか? 0.2より大きい確率(右側)の方がずっと広いことがわかります。

尤度で重みを付けたpの期待値を求めてみましょう。

f:id:nakano-tomofumi:20170407152830p:plain

分母には重みである尤度の合計を持ってきています。分母と分子の二項係数は共にキャンセルされるので、消してあります。

これを maxima で解いてみましょう。

(%i1) beta_expand:true$

(%i2) integrate(p*p^s*(1-p)^(n-s),p,0,1)/integrate(p^s*(1-p)^(n-s),p,0,1);
Is s + 2 positive, negative or zero?

positive;
Is s - n - 1 positive, negative or zero?

negative;
WARNING: redefining MAXIMA::SIMP-UNIT-STEP in DEFUN
WARNING: redefining MAXIMA::SIMP-POCHHAMMER in DEFUN
Is s + 1 positive, negative or zero?

positive;
                                     s + 1
(%o2)                                -----
                                     n + 2

s や n に関して質問がありますが、なんと、ラプラススムージングが出てきました。ラプラススムージングは期待値だったのです。

まとめ

分子に1分母に2を足そう。

最も大きいところだけでなく、全体を見渡そう。

補足

事前確率について一様な場合について議論しました。一般的に事前知識に関して全く知識がない場合には無情報事前分布としてこのような一様分布を仮定します。

一方で、CTRって1%くらいだよね、とか、ナンパって全然成功しないよね、とか、今回の検証ケースではないにしても、他の情報にてある程度分かっている場合もあります。そのときには、共役事前分布を仮定して、経験ベイズ(エビデンス近似;第二種の最尤推定)といった方法で、ラプラススムージングにおける1と2に変わるパラメータ(超パラメータ;ハイパーパラメータ)を求めることを行います。二項分布に関して経験ベイズ法で超パラメータを抽出した話は下記の資料を御覧ください。

www.slideshare.net

このような技術を、fluct (SSP) ではアドネットワークを選択するバンディットアルゴリズムのパラメータに利用したり、fluct Direct Reach (DSP)では広告枠毎のCVRの予測のためのパラメータや、オーディエンスの属性推定などのパラメータとして利用しています。

まとまってない文章を晒すのに抵抗があったけど、メモを垂れ流したら仕事がうまく回りだした件について

Zucks Ad Networkでデータ解析をしています、@yuu_itoです。

気づいたら3月も半ばですね。花粉で目がしょぼしょぼします。

メモを取ることについて書いていきます。

きっかけ

技術調査のために論文を集めGoogle Docsにまとめていた時、 とりあえずまとめた後に共有しますねと連絡したら、 メモはGitHub Issueへのコメントで書いておいたら?というのが始まり。

f:id:u110:20170313092305p:plain

やってみて気づいたこと

まとまっていない状態の文章を晒すことに抵抗があったのですが、やってみると嬉しいことがありました。

1. Issueに気づいた人がコメントをくれる。

弊社ではGitHubとSlackを連携してIssueの更新をSlackに通知しています。 取り組んでいること、考えていることを書いていると

  • 「なんかデータの傾向がイメージと違った」→「それ~だからかも」
  • 「計測しているデータ、不要なものも含まれている」→ 「あ、それ実はこっちのテーブルが~」

のように、気づいた人からコメントをもらえました。 メモのひとつが小さいと何か言えそうな人のコメントもしやすいと思います。

経過がSlackに流れていることでチームメンバーが自分のやっていることを知っているので 次のアクションについて話しても説明が不要だったりしました。 (まとめるとはなんだったのか)

2. 経過がわかるので迷走したら振り返ることができる。

本当にこれはよくあることなのですが、のめり込みすぎて「結局何したかったんだっけ?」という状態に陥っても そのとき考えていること、疑問に思っていたことなども含めてメモに書いておくと 今までの作業の流れをはっきりを振り返ることができ、どの時点で問題があったのか気づくことができました。

f:id:u110:20170313092315p:plain

まとめ

  • 経過がわかるようにメモを書き出しておくと良いことがある。
  • 人に見えるとこに書いておくと、ヒントをもらいやすい。
  • 方針に迷ったり、悩んでるときこそ、考えていることを書き起こすと良い。

たまにSlackのチャンネルはメモがずっと並んでしまいますが、 自分の所属するチームでは許容してもらえてると認識しています。

インフラチームと開発チームの垣根をなくすためにAWSのCI環境を構築した話

こんにちは、VOYAGE GROUP システム本部の @s-tajima です。

PHPカンファレンス2016 の「老舗メディアが改善に取り組んでいる話」でもお話した通り、長年オンプレミス環境で稼働してきたECナビを、AWSに移転しようというプロジェクトが進行しています。

そしてなんと先日、約24時間のメンテナンスを経てECナビの本体(Webサーバ, 管理画面サーバの一部, データベースサーバ)がAWSに移転しました!

AWS移転において得た知見, 構築したシステム等は数多くありますが、今回はCloudFormationとTravis CIを用いて 生産的安全手軽 なAWSのCI環境を構築したお話です。

背景

ECナビは、500万人を超える会員を抱えたVOYAGE GROUPが運営している中でも特に大きなメディアの1つです。
今回、そんなECナビのインフラ調達期間の削減、検証環境構築の簡易化、レガシー環境からの効率的な脱却等、ユーザーさんに価値を届けるスピードを加速させるための施策の1つとしてAWSに移転することを決めました。

17年もの長い期間オンプレミスで稼働してきたシステムのため移転に必要な作業は数多くあり、その中の一作業であるAWS環境の構築 だけに それほど大きな手間を掛けるわけにはきません。

このような背景を考慮して、ECナビのAWS環境の構築は

  • ユーザーさんに価値を届けるスピードを加速させるという目的を阻害しない 生産的 な環境
  • 多くのユーザーさんを抱えるメディアとしてそれに見合った 安全 な環境
  • 大きな手間をかけず 手軽 に実現できる環境

を目指して進めようと考えました。

生産的な環境 とは

オンプレミス環境での運用時、よく見られた光景が

  • ECナビ開発チーム「新しいサーバー使いたいわ。インフラチームさん用意してー。」
  • インフラチーム「はいはい。でもちょっと忙しいから来週まで待ってね。」
  • ECナビ開発チーム「(う、サーバーがもらえないと先にすすめない。。)」

というものです。 ECナビ開発チームのメンバーからすると、 自分でコントロールのできない待ち時間となり、時間のロスが発生してしまっています。

このようなやり取りは、弊社以外でも多く見れるかと思います。
オンプレミス環境で運用していると、データセンターの運用や物理機器の調達等を特定のチームで担当したほうが効率がよい場面が多く、自然とこのような分業が進んでしまうのかと思います。

AWSに移転した後、さきほどのやりとりが

  • ECナビ開発チーム「新しいEC2インスタンス使いたいわ。インフラチームさん用意してー。」
  • インフラチーム「はいはい。でもちょっと忙しいから来週まで待ってね。」

となってしまうとオンプレミス時代のような時間のロスがそのまま残ってしまいます。
本来はECナビ開発チームのメンバーが新しいサーバーを使いたくなった時には、待ち時間なく自ら環境を作成できるようになるとよさそうです。

今回はこのような時間のロスをせずに済む生産的な環境を目指しました。

安全な環境 とは

では、前述のような生産的な環境を実現するために、単純にメンバーみんなに権限だけ渡して
各自の好きなようにAWSリソースの操作をしてもらうようにして終わりでよいでしょうか?

もちろんこのようなやり方でうまくいく場合もあるかと思いますが、 そのような環境では、

  • オペレーションミスによって本番用のEC2を誤ってターミネートしてしまった。
  • いつの間にか本番用のRDSがインターネットのどこからでもアクセスできるように設定されてしまっていた。
  • 設定した経緯がわからず、不要だと判断し削除したSecurityGroupの接続許可設定が、
    実は使われている頻度は少ないがとても重要なものだった。

といった事故が起きてしまう心配が残ります。

ここまで極端な例はなかなか起きないとしても、 多くの人が好き勝手にAWSリソースを操作でき、その変更内容の管理ができていないような環境では多くのユーザーさんを抱えるメディアとしてそれに見合った安全な環境を提供できてるとは言えないと考えました。

理想としては、

  • AWSリソースの操作はレビューを通してから行う
  • AWSリソースの操作があったタイミングで通知を行う
  • AWSリソースの操作の内容は記録に残し、後から履歴や経緯を追える

ということができるような安全な環境を目指しました。

手軽に実現できる環境 とは

ここまでの要件を満たせるような環境は、サーバーを用意し、便利なツールを導入し、さらに必要であれば自らコードを書くことで(つまり時間さえかければ)いくらでも実現できるでしょう。

しかし、自前でこのような環境を用意しようとすると権限管理、バックアップ、冗長化、監視等、障害時の対応等、本当に必要だった要件に加えて考えなければいけないことが次から次へと出てきます。

このあたりを可能な限りマネージドサービスにまかせてしまうことで、手軽に環境が用意できることを目指しました。

実現方法

ここまでの要件を満たすために、今回はこのような環境を構築しました。

f:id:s_tajima:20170217133520p:plain

この構成では以下のような流れでAWSリソースを操作します。

  1. GitHubで管理されたCloudFormationのテンプレートを修正し、Pull Requestを作成 (作業者)
  2. Pull Requestの作成/更新をフックにビルドを実行 (Travis CI)
    • 2.1 ValidateTemplateを実行
    • 2.2 CreateChangeSetを実行
  3. Pull Requestの内容とChangeSetの内容をレビュー (レビュアー)
    • 問題がなければLGTM
    • 問題があれば指摘して2からやり直してもらう
  4. ExecuteChangeSetを実行 (作業者)
    • 4.1 CloudFormationがAWSリソースを操作
  5. AWSリソースの変更結果を通知 (CloudFormation -> Lambda -> Slack)
  6. 変更の結果を確認 (作業者 or/and レビュアー)
    • 問題がなければ次に進む
    • 問題があれば2からやり直し
  7. Pull Requestをマージ (作業者)

いくつかのポイントを説明します。

まず1つ目のポイントは、AWSリソースの操作にCloudFormationを使い、そのテンプレートをGitHubのリポジトリで管理している点です。 このリポジトリには、弊社のメンバーであれば誰でもPull Requestを送ることができます。 そのため、必要だと思ったメンバーが自らテンプレートを書き、それを適用することですぐに必要なリソースを構築することができるようになっています。

変更作業の前にPull RequestやChangeSetのレビューを受けることで、意図しない変更や経緯のわからない変更がされてしまうことを防ぎます。

ChangeSet(変更セット)についてはこちらに詳しく記載してあります。
https://aws.amazon.com/jp/blogs/news/new-change-sets-for-aws-cloudformation/

また、CloudFormation操作の通知はSlackに飛ばされるようになっていて、もちろんCloudTrailもAWS Configも有効にしてあります。
このように、 生産的安全 な環境を実現しました。

2つめのポイントは、Travis CIを利用している点についてです。 これは言うまでもなく 手軽 にCIの環境を手に入れるための手段です。 しかしそれだけではなく、Travis CIを 安全 に利用するための工夫をしています。

Travis CIに登録してあるIAMユーザーの権限は、 ValidateTemplate, CreateChangeSet に限定しています。
そのため、もし仮にTravis CIからIAMのクレデンシャル情報が漏れてしまっても、その情報だけでは ExecuteChangeSet を実行できないためAWSリソースを自由に操作することはできません。

また、テンプレートを編集できるメンバーは ExecuteChangeSet と必要なリソースの参照の権限しか持ちません。
CreateChangeSet の実行をTravis CIからのみに絞ることで、本CIフローから外れたAWSリソースの操作が行われてしまうのを防いでいます。

まとめ

以上、CloudFormationとTravis CIを用いた 生産的安全手軽 なAWSのCI環境の構築のお話でした。

冒頭の自己紹介の通り、僕はシステム本部所属のインフラチームのメンバーなのですが、PHPカンファレンスの資料にあるような交換留学の一環で現在はECナビの開発チームのメンバーと共にECナビの環境改善に全力を注いでいます。
今回の話もインフラチームと開発チームの垣根をなくすためのとてもよい施策になったと思っています。

お知らせ

現在、(ECナビを含む)メディア領域、システム本部ではそれぞれ一緒に働く仲間を募集しています。

どちらのチームにも、まだまだ解決したい課題がたくさんあります。
ご興味のある方はぜひお気軽にご連絡ください。

#再演 します。「エンジニアの技術力評価は難しい? - 5年間運用してきた技術力評価制度の改善の歴史 ‒」現役の評価者/被評価者も参加予定!

こんにちは、月日が経つのは早いものでCTO歴が6年半を越えたmakogaです。

ご縁があり、今年の1/12(木)にRegional SCRUM GATHERING Tokyo 2017で登壇しました。内容はエンジニアの技術力評価を5-6年掛けてどう改善してきたかです。

翌日スライドを公開したところ、多くの方に見てもらえ、はてブTwitterなどでたくさんのコメントもいただきました。

コメントを読むと、さまざまな考え方があると感じます。また、当日の懇親会や後日会った方との意見交換はとても楽しいものでした。今後も技術力評価会を改善していくにあたり、もっとたくさんの方と意見交換していきたいと思い、今回 #再演 イベントを開催することにしました。

また、当日の質疑応答では「うちでやろうとすると、評価するのを嫌がるエンジニアが出てくると思うのですが、そういうのはなかったですか?」というような質問がありました。それを社内のSlackにpostしたところ下記のようなやりとりがありました。

f:id:voyagegroup_tech:20170214175119p:plain

Regional SCRUM GATHERING Tokyo 2017ではVOYAGE GROUPからの参加は私だけでしたが、今回の #再演 イベントでは現役の評価者/被評価者も参加予定です。「評価されるって実際どうなの?」「評価者大変じゃない?」など、参加者の方々から疑問を投げかけてもらえると盛り上がるのではないかと期待しています。

エンジニアの評価や育成、組織における制度設計・運用などに興味がある人たちとの交流を楽しみにしていますので、まずは気軽に下記connpassイベントをクリックし「このイベントに申し込む」をポチっと押してみませんか!

connpass.com

GitHubにおけるPull RequestのAssign/Mergeを自動化して開発を加速させる

皆さんこんにちは. 現在はfluctにてfluct DRという広告配信システムの開発を行っております, 大関です.

GitHub上でのチーム開発では, レビューの依頼や, CIが通ったことを確認した上でのPull Requestのマージといった複数の作業が発生しますが, これらはGitHubのUIを複数回クリックする必要があり, 非常にストレスフルな作業です.

本稿では, こうした定形作業を自動化するbotとしてpopukoを開発・導入することで, 我々開発者のストレスを軽減するとともに, より堅牢かつフィードバックの多い開発が実施できるようになった事例を紹介します.

GitHubでの開発はとてもクリック操作が多い

前段でも述べたように, GitHubを用いたチーム開発においては, 数多くの定形作業が存在します. コードレビューの可能な人を探してレビューを依頼する, 依頼の度に対象者をAssigneeに追加する, コードをレビューする, upstream(多くの場合においてmasterブランチ)とのコンフリクトが発生していないかを確認する, reviewerによる許可が出た後にマージボタンを押す, Pull Requestをupstreamにマージした後もCIがgreenのままでいるかを確認する, などなど. GitHubのデフォルトのUIだけでは, これらは手動で実施する必要があり, 非常にストレスフルな作業です.

また, それぞれのPull Requestの状態についてはラベルで管理していない限りは一覧した場合に判断しにくく, かといってラベル管理している場合はそれはそれでラベルを付け替える手間が発生してしまいます.

我々はソフトウェア開発者ですので, このような鬱屈とした作業に関しては直ちに自動化を行い, クリック作業を減らし, ストレスの多い生活から開放されなければなりません.

自動化したい

このようなGitHubにおけるPull Request作業の自動化という分野については, homuと呼ばれる高機能かつ素晴らしいbotが存在しています. homuはGitHubにおけるPull Requestのマージ処理の自動化やTravisCIとの連携など数多くの機能を持つbotです. かつてMozillaにてRust Languageのtech leadを務めていたGraydon Hoareの語った

The Not Rocket Science Rule Of Software Engineering: automatically maintain a repository of code that always passes all the tests

の思想に基づき開発されたborsの流れを組んでおり, Rust ProjectならびにServo Projectで使われています.

ですが, 本家側の更新は止まって久しく, MozillaにてforkされたバージョンはMozillaのビルドインフラやServoプロジェクトに合わせた変更がなされており, 他のプロジェクトが自前でホストする形式での利用を積極的に薦める状態にはありません.

自前でホストするのを諦めるのであれば, homu.ioを使用するという選択肢もあります. しかしながら, 使用するに際して, 運営元の不明瞭な外部サービスにプライベートリポジトリへのアクセスを認めることとなり, セキュリティ上の観点からは決して望ましいものではありません.

また, homuは各リポジトリごとのreviewerの設定を, 中央集権されたhomu向けの設定ファイルに記述することで管理しています. ですが, 私達は, このような設定に関しては各リポジトリごとに管理可能にし, それぞれのリポジトリ内でreviewerやbotの機能の設定を完結させたいという意志がありました.

これらの理由から私達は, 自分達の要求に足るだけの最小機能だけをpopukoとして再実装し, 私達のプロダクト開発に導入することとしました.

popuko

popukoは以下のモデルでPull Request駆動による開発を補助します

  • webhookを起点にして動作を行う
    • コメントの書き込みややpushイベントに反応して動作する
  • ラベルを用いた状態の可視化を行う
    • rebase必須, レビュー待ち, マージ待ち, など
  • 各リポジトリのトップレベルディレクトリに配置されたOWNERS.jsonファイルに書かれた設定に基づき動作する

この原則に基づきに, 現在, 以下の機能を実装しています.

  • Pull Requestに書き込まれたコメントに基づいて何かする
    • reviewerの割当とラベルを変更する
      • Pull Requestがapproveされたらラベルを変え, 一旦upstreamとマージした結果を試す仮ブランチを作り, CIがgreenになることが保証されたら正式にマージする.
  • upstream側が変更され, Pull Requestがマージできなくなった場合, push logとともに其の旨を当該Pull Requestに書き込む

ラベルによる状態の可視化

popukoは以下のラベルを用いてPull Requestの状態を管理します. このラベル名はServo Projectのラベル定義を参考にしています.

ラベル名 意味
S-awaiting-review レビュー待ち
S-needs-rebase conflictしてるのでrebaseが必要
S-awaiting-merge マージ待ち
S-fails-tests-with-upstream Auto-Mergeを用いてマージしようとしたらテストが失敗してしまった

具体的な機能

それでは, それぞれの機能について見ていきましょう.

reviewerをassignする

r? @<reviewer> の形式でPull Requestにコメントすることにより, <reviewer>をAssigneesに登録し, ラベルをS-awaiting-reviewに変更します

popukoがreviewerをAssigneesに登録してラベルを変更する例
f:id:saneyuki_s:20170210193146p:plain

masterとのconflictを検知する

masterブランチにpushが為されたことをwebhookのpushイベント経由で受取り, openなPull Requestを巡回します. 巡回したPull Requestのうち, conflictを起こしているものに対して以下を行います

  • ラベルをS-needs-rebaseに付与する
  • pushイベント経由で取得したchangeset urlをコメントし, コンフリクトの原因となっている可能性の高い変更をコメントする

popukoによるconflict通知の例
f:id:saneyuki_s:20170210193216p:plain

マージ可能であると知らせる

@<botname> r+もしくは@<botname> r=<reviewer>とコメントすることにより, 当該Pull Requestの最新のcommit hashと共にreviewerの名前をコメントに書き込み, ラベルをS-awaiting-mergeに変更します. 他の作業とは異なり, このコメントを書き込める(botが反応する)のは, OWNERS.jsonファイルによって予め指定されたユーザーだけです.

Auto-Merge

OWNERS.jsonauto_merge.enabledtrueに指定されている場合, popukoはPull Requestの自動マージを試みます. フローは以下の通り:

  1. reviewerが@<botname> r+とコメントする
  2. popukoはマージ先のブランチに対して仮ブランチ(autoと呼びます)を作成する
  3. 2で作成されたauto branchに対して, Pull Requestの内容を仮マージする
  4. auto branchに対してCIを実行する(TravisCIであれば, CI対象ブランチを制限していなければ自動的にCIが動き出す)
  5. CIが完了したら其の結果を確認し, greenであれば改めて正式にPull Requestをマージする

これにより, masterにマージされるPull Requestは, マージ後も含めて常にgreenとなることが自動的に保証された上でマージされるようになります.

popukoによるブランチの取扱の図
f:id:saneyuki_s:20170210190956j:plain

popukoによるauto mergeの例
f:id:saneyuki_s:20170210193120p:plain

OWNERS.json

各リポジトリごとのrootディレクトリに配置します.

ここに@<botname> r+を発行できるreviewerの名前を記載しておくことで, popukoはそれを用いて, 許可した人間のみがPull Requestにマージ可能である旨を示せるようにしています.

導入の結果, 何が変わったのか?

popukoの導入の結果, 当初の目的通り, 以下の点が達成されました.

  • 3~4クリックが1クリックに減った,
  • 常にmasterの状態がgreenのまま維持されるようになった

特に前者に関しては, SlackとGitHubの二重のレビュー依頼をしている同僚も多かったのですが, popukoにより, GitHubに書くだけで自動的にassignまで行われるようになったのでGitHubだけで完結させることのほうが合理的になりました. Slackでレビュー依頼が必要な場合は, 長期間放置されているものの催促や緊急性の高い内容に限られるようになり, Slack上での会話の濃度も上がりました.

後者についても, Continuous Delivery/Deploymentの観点から非常に役に立ちます. 「動かない(既存のテストケースで洗い出せる問題のある)コードをウッカリmasterブランチに対してlandしてしまう」という人為的ミスが起こらなくなるので, より堅実かつ問題の切り分けを行いやすいプロダクトリリースを実現するための礎となります.

また, 導入前は「これ本当に便利なの?」と懐疑的だった同僚も, 今では「無いと仕事にならない. 病み付きになる」と述べるようになるなど, 「無くても成立するが, 一度使ってみると離れがたい引力がある」種類の道具となりました.

併せて社内に#popukoという名前のbotのサポート・開発専用channelを用意し, popukoのリポジトリへの設定方法についても Popuko as a Service (PaaS) と銘打って文書化しているため, 使用したい社内のリポジトリについては各リポジトリの判断で自由に導入できるようにしています.

FAQ

popukoって名前の由来は?

popukoは Practical Organized Productive Unlimited Kabuki-nized Operation の略です

popukoに機能追加するくらいならhomuをforkしたほうが良かったのでは?

forkせずスクラッチしたのは出来心も多分にありますが, Goでスクラッチしたのは以下の技術的な判断です:

  • go-gihubという便利なGitHub APIラッパーがあった
  • GitHubのREST APIを大量に呼び出すという性質と, 数多くのリポジトリで同時多発的にトリガーとなるコメントが書き込まれ, それを捌くことを考えた場合, goroutineによる処理モデルは当botの扱う問題に適していると判断した
  • シングルバイナリを生成して適当な環境に放り込んで動くのは便利
    • ゲリラ的に正規導入するかわからないプロジェクトを始めるときは, Dockerコンテナとか作らずに, とにかく大雑把に動かせることが重要
    • アプリケーション性質的にはNode.jsでも良かったが, とにかく単一ファイルを放り込んで終わりにしたかったので, この点に見合わなかった.
  • 別にhomuの機能全部は要らなかった
    • 「こんなの直ぐに実装終わるだろ!」と思った

類似のhomu再実装は他にもいくつか存在しますが, 以下の理由からそれらの採用は行っていません.

  • mgattozzi/thearesia
    • これは現在でも未完成なので, 自然と対象外になった.
  • gullintanni/gullintanni
    • r? @<reviewer>のようなhighfive相当の機能が実装されておらず, highfiveのデプロイも必要な事を考えると, homu + highfiveの構成をそのまま用意するのと大差は無かった.
    • reviewerに相当する設定をリポジトリ単位にコードとして管理させる機能も見当たらなかった
  • rultor
    • GitHub botのためにJVMを動かしたくはなかった(JVMを多用しているチームであれば, 有効だったかもしれない)
    • コマンド体系が(私は)良いとは思えなかった
    • highfive相当の機能も実装されていない

そもそもとして「 botと対話するインターフェースによって自動化がなされる 」というのが重要な点です. それを実現する実装は必ずしも重要ではありません. 今後, popukoへの機能追加が見合わないと判断した場合, 改めてhomuをforkすることや類似のbotに乗り換えることも勿論有りえます.

SlackからGitHub botを操作するほうが良いのでは?

私は良いとは思っていません. GitHubはコードを取り扱うサービスであり, コードおよびGitの操作に関してはGitHub上で完結させるべきだと考えています。merge botのヘルスチェックや再起動はSlackからの操作でも良いと思いますが、Gitの操作に関してはGitHub上でbotへの指示がなされるべきです.

masterとのconflictを検知する」とあるけれども, popukoはmaster以外に向けたPull Requestに対しては検知できないの?

現在のところは検知できる実装にはなっていません.

根本的な前提として, 我々のチームについてはmaster以外のブランチに向けてPull Requestを送る開発習慣が存在せず, masterブランチをtrunkと見做す方式でのTrunk駆動開発に基づいてプロダクトの開発をしています. feature branchを切ることや, release branch専用のPull Requestを用意することは頻度として稀です.

また, 設定を各リポジトリ毎に分散して持たせている以上, masterに相当するブランチは各リポジトリごとに持たせるべきではあります. このdesign上の制約により, masterブランチ以外も自由にtrunkとして設定可能にすると, pushイベントが発生するたびに設定ファイル取得のためにGitHub APIを消費してしまうという問題が有ります.

こうした点を考慮し, 課題であるとは認識していますが, 私達の実用上の観点から直ちに修正するべき課題であるとは考えていません.

まとめ

以下がまとめとなります

  • 面倒くさいことは自動化しましょう
    • 便利なのでGitHubにmerge botを導入しよう
  • masterブランチ(trunk)が常にgreenであることが機械的に保証される安心感はとても良い

お知らせ

弊社には AJITO という社内バーがあり, 毎夜のようにエンジニアが現れ, 酒を嗜み, 軽食をつまみつつ, エンジニアリング談義を行っています( #ajitingで既にご存知の方も多いと思います). 「私も一緒に(こういう)エンジニアリング談義をしたい!」という方がいらっしゃいましたら, 是非とも弊社エンジニア or @tech_voyageに, お声掛けいただければと思います.

また, fluctを含むVOYAGE GROUPアドテクユニットでは, 一緒に働いてくださる仲間を募集しております. VOYAGE GROUPや広告配信に興味を持たれましたら, お気軽にご連絡ください.

2016年を締め括るROCK FESの話

こんにちは。@kanufyです。
みなさん、2016年はいかがでしたか?
楽しめましたか?エンジニアリングしましたか?ROCKしましたか?

VOYAGE GROUP Advent Calender 2016
最終日はROCKの話をしたいと思います。

会社でROCKするんだよ、それがROCKだ

弊社にはサークル活動 (※1)があります。
色々なサークルがあるのですが、音楽好きが集まり、なんかよくわかんないけどいろんな楽器やってワイワイしようぜ!っていうサークルがあります。
その名もVOYAROCKサークル!

今日は、先日12/22にVOYAROCKのコンサートが開催されたのでその様子をお伝えしようかと思います。

エンジニアも営業も法務も役職に限らず

VOYAROCKに限らず、弊社の様々なサークルメンバーは職種に限らず多種多様であり、サブメンバーを含めるとエンジニアから法務といった様々の職種の方が所属しています。
そのため、普段関わらなかった人とも関われる機会となり、サークル活動は社内の情報交換の場としても使われていることが多いです。

VOYAROCKランチコンサート

VOYAROCKでは、定期的にコンサートを開催しており、前回は8月末にゲーム音楽をテーマとして開催されました。
12/22に行われたVOYAROCKコンサートでは、映画音楽をテーマとしてセットリストが組まれました。

  • 戦場のメリークリスマス
  • Goodbyedays
  • 桜流し
  • なんでもないや

昔から最新の映画音楽ですね!
弊社のバースペースであるAJITOにて開催されました。

f:id:kanufy:20161224014433j:plain:w500

f:id:kanufy:20161224014452j:plain:w500

f:id:kanufy:20161224014512j:plain:w500

VOYAROCK FES

同日の夜には場所を変えて大会議室にて夜の部が開催されました!
セットリストは以下になります。

  • なんでもないや~Band ver.~
  • Stand by me
  • Shape of My Heart
  • Thinking Out Loud
  • 月のしずく
  • swallowtail butterfly ~あいのうた~

映画音楽じゃないものもありますが、お酒を飲みつつROCKに浸る。
これぞフェスですね!

f:id:kanufy:20161224225817j:plain:w500

f:id:kanufy:20161224081846j:plain:w500

フェスのあとにはおかわりタイムということで、各自やりたい曲をやったり即興でセッションが行われたり
アンコールをしたりと楽しい時間を過ごしました。
VOYAROCKではどんどん機材も充実してきており、PAのスキルアップもできたりしますよ(笑)

なお、この記事を書いているのは雪山から帰還中の新幹線の中です。
わたし実はスキースノボサークルの部長やってます。そう、サークル活動で合宿中です。ROCKですね。
サークル活動にクリスマスイブとか関係ないですよね!

それでは2017年もVOYAGE GROUPをよろしくお願いします。

企業ブースで実コード公開をした話 #phpcon2016

この記事はVOYAGE GROUP techlog / Advent Calendar 2016の記事として書いています。

こんにちは、@pro_shunsukeです。

VOYAGE GROUPはPHPカンファレンス2016にスポンサーとして協賛させていただきました。PHPカンファレンス2016に関してはこのブログの中で事前告知発表のあとがき、また企業ブースを出して学んだことなどを記事として掲載しています。今回でPHPカンファレンス2016に関する記事は4つ目という事になります。

さて、上記の記事にも詳しく書かれているのですが、弊社ではスポンサーとしてプレゼン発表や企業ブースを出展させていただきました。どちらもとても多くの方に興味を持っていただきました。今回はその中でも特に興味を持っていただいた 企業ブースでの実コード公開 をした際の知見ついて書こうと思います。実コードを見せるに至った経緯や当日の反応、また全体を通しての反省点を記事として残していこうと思います。

なぜ実コードを見せる事になったのか?

カンファレンス当日は弊社の@dkkomaによる「老舗メディアが改善に取り組んでいる話」という発表が行われました。

speakerdeck.com

17年間運営しているECナビというサービスについて、歴史を踏まえてどのように改善に取り組んできたのかについて発表しました。 しかしやはり発表だけではどうしても綺麗事のように聞こえてしまう部分があり、より具体的な取り組みが分からないのでは?という事になりました。そこで 実際に改善したコードの具体例を紹介 することで、見に来ていただいた人に1つ1つの地道な改善を実感していただきたいと思いました。

どのようなコードを選んだのか?

実際にプロダクトで使われているGithubの Pull Request(PR) を3つ選び、差分を見ていただきながら改善の様子を紹介したり、レビューの様子を紹介しました。3つのPRは、発表の中でもありましたKAIZEN会*1で出たコードを選びました。また、 ドメイン知識の必要のない外向けに分かりやすい内容のもの を選びました。

当日紹介した実コードの例

f:id:pro_shunsuke:20161217154120p:plain

当日の反応

とても興味をもっていただいた人が多く、さまざまな反応をいただきました。

反応していただいた意見

以下のような意見を多くいただきました。

  • 「分かる。大変だよね」
  • 「どうやったらうちの組織でも改善出来る体制になるだろう?」

地道な改善作業に対する共感の声がありました。

また、改善するのは大切だけれどなかなか手が出せない、どうしたらそのような体制づくりが出来るだろう?という相談も多かったです。難しい相談ですが、1つは長くサービスを続けていく上では継続的な改善が必要だという事に対するプロデューサーの理解があった事が大きかったと思います。

どのような人に興味を持っていただいたか?

興味をもっていただいた人は30代, 40代くらいのベテラン感のある人が多く、若者にはあまり響かなかったようでした。やはり「改善」に焦点を当てていたこともあって、業界の経験が豊富な人により響いたようでした。

全体としての反省

基本的にはとてもよかったのですが、いくつか反省点もありました。

体制について

予想を上回る反応をいただいた事はとても嬉しい事でしたが、 当初想定していた体制では興味を持っていただいた人全員には説明しきれなかった という事がありました。

最初は説明者1人とモニター1台で望みましたが、徐々に来場者が多くなるに連れて説明が間に合わなくなりました。当日は臨時で説明者を増やし 3人体制 にしたことで、ピークのランチタイムにはちょうど良くなりました。

お客さんの要望について

例えば以下のような要望がありました。

  • 「プロジェクト全体のディレクトリ構造はどうなっているの?」

当初はPRだけを見せるつもりでいたので、ディレクトリ構造までは見せることが出来ませんでした。しかし後になって考えてみたところ、ディレクトリ構造くらいなら見せても良かったのでは、ということになりました。 どこまでを見せてよく、どこまでは見せてはいけないのかは最初にバチッと決めておいた方がよかった です。

最後に

反省点はあったものの、当日は予想を上回る人に興味をもっていただきとても嬉しかったです!次回同じような機会がありましたらこの反省を活かして、より良いブースを出展出来たらと思っています。

また、発表やブースを見に来ていただいた方々に 少しでも改善の文化が伝わっていただけたら と思っています。

それでは明日の記事もお楽しみに!

*1:KAIZEN会についてはこちらの記事でも詳しく紹介しています。 ECナビ KAIZEN会を実施しました