読者です 読者をやめる 読者になる 読者になる

VOYAGE GROUPで利用されているプログラミング言語のトレンドを調べてみた

こんにちは。株式会社ZucksでZucks Ad Networkの開発に携わっている南大津です。

エンジニア採用の募集事項に「業務で使われている言語」がよくありますが、どの言語がどれくらい使われているかは、入社前にはなかなか把握し難い部分ではないでしょうか。
そこで今回は、VOYAGE GROUPで開発に利用されているプログラミング言語のトレンドについて調べてみたので、ご紹介できればと思います。

ソースコード管理について

VOYAGE GROUPでは多くの子会社、サービスが存在していますが、そのほとんどのソースコードはvoyagegroupという1つのGitHub Organizationアカウントで管理されており、現在約400個のリポジトリが作成されています。

私が入社した2009年頃には、ソースコード管理といえばSubversionがメインでした。
その後徐々にGitHubでソースコード管理を行うサービスが増えていったのですが、当初は主に子会社毎にOrganizationアカウントを作成して利用していました。子会社毎にOrganizationアカウントが分かれていると、グループ会社間で知見が広まりづらかったり、アカウント管理を個別に行う必要があったりするため、1つのOrganizationアカウントに統一する流れが起き、今の形に落ち着いています。

今回はこのvoyagegroup Organizationに登録されているソースコードから、VOYAGE GROUPにおけるプログラミング言語の利用状況について調査してみます。*1

最近のトレンドを調べてみる

GitHubにはリポジトリで利用されている言語の割合(バイト数による算出)を表示してくれる便利機能が存在しています。

f:id:smileeeen:20170515194305p:plain

上記のようにリポジトリのステータスバーに表示されているもので、クリックをすると言語ごとの割合が確認できるようになっています。

GitHubでこの情報の算出に使われているライブラリがLinguistという名前で公開されているので、こちらのコマンドラインツールを使ってみます。

github.com

$ git-linguist --help
    Linguist v5.0.10
    Detect language type and determine language breakdown for a given Git repository.

    Usage:
    git-linguist [OPTIONS] stats|breakdown|dump-cache|clear|disable"
    -f, --force                      Force a full rescan
    -c, --commit=COMMIT              Commit to index

git管理されているディレクトリ上で、 git-linguist stats コマンドに特定のコミットハッシュ値を渡すとその時点における言語別容量を返却してくれます。
現在と過去のある時点での言語別容量を比較すれば、最近よく使われている言語の傾向が見えないかなと考え、2017年になってからのvoyagegroup Orgnizationにおける言語別容量の増減を調べてみました。

GitHub APIの呼び出しにはphp-github-apiを利用させていただきました。

github.com

調査に利用したソースコードは下記になります。*2

<?php

require_once 'vendor/autoload.php';

$orgName        = 'voyagegroup';
$diffTargetDate = '2017-01-01T00:00:00Z';
$tmpDir         = '/tmp/techlog';

$client = new Github\Client();
$client->authenticate('【SET ACCESS TOKEN】', null, Github\Client::AUTH_HTTP_TOKEN);

$paginator = new Github\ResultPager($client);
$orgApi    = $client->api('organization');
$repoApi   = $client->api('repo');

$allRepoLangsDiff = [];

foreach ($paginator->fetchAll($orgApi, 'repositories', [$orgName]) as $repo) {
    if (!shouldAggregateRepo($repo)) {
        continue;
    }

    $repoName = $repo['name'];

    $headCommitHash = getLatestCommitHash($repoApi, $orgName, $repoName);
    $pastCommitHash = getLatestCommitHash($repoApi, $orgName, $repoName, ['until' => $diffTargetDate]);

    if ($headCommitHash === $pastCommitHash) {
        continue;
    }

    exec(sprintf(
        'cd %s && git clone --single-branch -n git@github.com:%s/%s.git',
        $tmpDir,
        $orgName,
        $repoName
    ));

    $repoLangsDiff = getRepoLangs($tmpDir, $repoName, $headCommitHash);

    if ($pastCommitHash) {
        $negPastRepoLangs = array_map(function ($v) {
            return -$v;
        }, getRepoLangs($tmpDir, $repoName, $pastCommitHash));

        $repoLangsDiff = sumRepoLangs($repoLangsDiff, $negPastRepoLangs);
    }

    exec(sprintf('rm -r %s/%s', $tmpDir, $repoName));

    $allRepoLangsDiff = sumRepoLangs($allRepoLangsDiff, $repoLangsDiff);
}

function shouldAggregateRepo($repo)
{
    return $repo['size'] > 0
        && !$repo['fork']
        && preg_match('/\A[-.\w]+\z/', $repo['name']) === 1;
}

function getLatestCommitHash($repoApi, $orgName, $repoName, $condition = [])
{
    $commits = $repoApi->commits()->all($orgName, $repoName, $condition);
    return $commits ? $commits[0]['sha'] : '';
}

function getRepoLangs($tmpDir, $repoName, $commitHash)
{
    exec(
        sprintf(
            'cd %s/%s && git-linguist -c %s stats',
            $tmpDir,
            $repoName,
            $commitHash
        ),
        $linguistOutput
    );
    return $linguistOutput ? json_decode($linguistOutput[0], true) : [];
}

function sumRepoLangs($langs, $addLangs)
{
    return array_map(function ($row) {
        return array_sum((array)$row);
    }, array_merge_recursive($langs, $addLangs));
}

// check $allRepoLangsDiff

結果は以下のようになりました。*3

増加している言語
rank 言語 容量変化(MB)
1 Jupyter Notebook 16.2
2 Python 4.0
3 C# 3.5
4 C 2.4
5 C++ 1.3
6 Ruby 1.0
7 Objective-C 0.7
8 CoffeeScript 0.5
9 Go 0.4
10 Scala 0.3
11 ShaderLab 0.3
12 Smarty 0.3
13 TypeScript 0.3
14 Shell 0.3
15 Makefile 0.2
16 HCL 0.2
17 Puppet 0.1

1位のJupyter Notebookに関してはZucks Ad Networkの解析チームで活用しており、純粋なコード量が多いというよりは、Base64エンコードされた画像ファイルもソースコードの容量として含まれている場合があるため、割合が多くなっていると考えられます。
最近ではPythonが人気のようですね。
3位にランクインしたC#はVR室で開発しているUnity製のアプリがメインでした。

減少している言語
rank 言語 容量変化(MB)
1 PHP -8.2
2 HTML -5.3
3 Perl -5.2
4 JavaScript -3.7
5 CSS -3.0
6 Perl6 -0.4
7 Java -0.1

PHPが減っているのはちょっと意外だったので詳しく見てみると、一部のリポジトリで他リポジトリに分割済みのソースコードの削除作業が行われていました。
このリポジトリの増減を除くと、全体で減少していると判定されていた言語の増減は下記のようになりました。

言語 容量変化(MB)
JavaScript 4.0
PHP 1.1
CSS 0.4
Perl6 0.0
HTML -0.6
Perl -0.5
Java -0.1

JavaScriptは増加の2位タイ、PHPは6位というのが実態により近い値のようです。

プログラミング言語別割合の算出

折角なので、GitHub APIのList Languages APIを使ってVOYAGE GROUP全体でのプログラミング言語別割合も集計してみます。

調査に用いたソースコードは下記になります。

<?php

require_once 'vendor/autoload.php';

$orgName = 'voyagegroup';

$client = new Github\Client();
$client->authenticate('【SET ACCESS TOKEN】', null, Github\Client::AUTH_HTTP_TOKEN);

$paginator = new Github\ResultPager($client);
$orgApi    = $client->api('organization');

$totalLangs = [];

foreach ($paginator->fetchAll($orgApi, 'repositories', [$orgName]) as $repository) {
    $repoLangs  = $client->api('repo')->languages($orgName, $repository['name']);
    $totalLangs = array_map(function ($row) {
        return array_sum((array)$row);
    }, array_merge_recursive($totalLangs, $repoLangs));
}

// check $totalLangs

上記を用いて、voyagegroup Orgnizationに存在する全リポジトリの情報を合算してまとめたものが下記になります。*4

rank 言語 容量(MB) 割合(%)
1 PHP 219.9 27.84%
2 HTML 92.1 11.66%
3 Jupyter Notebook 89.2 11.29%
4 Perl 64.7 8.20%
5 JavaScript 64.0 8.10%
6 Python 59.7 7.56%
7 Java 29.4 3.72%
8 CSS 28.6 3.62%
9 Makefile 25.9 3.28%
10 Objective-C 23.3 2.94%
11 Ruby 18.1 2.29%
12 C 16.6 2.10%
13 C++ 16.1 2.03%
14 Smarty 6.7 0.85%
15 ActionScript 6.7 0.85%
16 C# 5.6 0.71%
17 Scala 3.5 0.44%
18 Tcl 2.5 0.31%
19 Perl6 2.3 0.29%
20 Shell 2.0 0.26%
21 Swift 1.9 0.24%
22 Go 1.7 0.22%
23 TypeScript 1.3 0.17%
24 Erlang 0.9 0.12%
25 R 0.8 0.10%
26 Roff 0.8 0.10%
27 CoffeeScript 0.7 0.09%
28 GLSL 0.7 0.09%
29 PLSQL 0.7 0.09%
30 Objective-C++ 0.5 0.07%
31 Kotlin 0.5 0.07%
32 ApacheConf 0.3 0.04%
33 Scheme 0.3 0.04%
34 Protocol Buffer 0.3 0.04%
35 Puppet 0.3 0.03%
36 HCL 0.2 0.03%
37 XS 0.2 0.02%
38 XSLT 0.2 0.02%
39 Groovy 0.1 0.02%
40 Vue 0.1 0.01%

色々な言語が使われていますが、結果としてはPHPが断トツの割合を占めていました。
調査前、私もPHPが20%くらいで一番多いかなと想像していたのですが、予想を上回る27%超えとなりました。
10年以上続いている弊社のメディアECナビがメインでPHPを利用しているなど、PHPを利用しているサービスが多いためだと考えられます。

3位のJupyter Notebookの容量が大きくなりがちなのは前述の通りです。

その他ではPerl、JavaScript、Python、Javaといった辺りがVOYAGE GROUPでは人気があるようです。最近のトレンドから考えるとそろそろPythonがPerlを抜き去りそうですね。
個人的にはMakefileが3%を超えてトップ10入りしているのも、VOYAGE GROUPらしいのかなと感じるところでした。(参考:プロビジョニングツールはMakeで決まりだろ // Speaker Deck

おわりに

最近のトレンドに関しては差分確認期間中に削除されているコードも存在するため、正確には分かりづらい部分がありましたが、少しはVOYAGE GROUPで利用されているプログラミング言語の実態を感じていただけたでしょうか。また1年後には状況が大きく変化しているかもしれません。

皆さんの周りではどういった言語が使われているでしょうか。
この言語をVOYAGE GROUPで広めていきたい!といった想いの方がいらっしゃいましたら是非採用エントリーをお待ちしています。 voyagegroup.com

*1:一部の古くからあるサービスなど、現在でもSubversionを利用しているものや、子会社ごとのGitHub Organizationアカウントで管理されているものもわずかに存在しますが、今回は調査の対象外とします

*2:GitHub APIとgit-linguistコマンドでは、言語判定が若干異なるケースがあるようだったので、直近の言語別容量もgit-linguistを利用して取得しています。

*3:容量変化が±0.1MB未満のものは表から除外しています

*4:容量が0.1MB未満のものは表から除外しています。サードパーティ製のソースコードは一般的なパスにあれば除外されているようですが、含まれているものもあるかもしれません

ErgoDox EZのキースイッチが家にあるもので何とか交換できた話

どうも世界60億超のErgoDoxファンの皆さんこんにちは。@tamakiii です。

ErgoDox EZを使い始めてはや一年。これまで何の不調もなく健康なプログラミングライフを助けてくれてきたキーボードですが、ここに来てちょっとした不調に遭遇しました。今回はそのときの出来事について書こうと思います。

ちなみに、VOYAGE GROUPには好きなキーボードを経費精算できる制度があります。HHKBが人気みたいですが、Sculpt Ergonomic KeyboardMistel Barocco なんかを使っている人もいるようです。

f:id:tamakiii:20170419185254p:plain

tl;dr

  • ある日突然 “b” キーがなかなか打てなくなった
  • キーリピートも効かない
  • 基盤の問題ではなかった
  • 使用頻度の低いキーのキースイッチと交換した
  • 本体を分解しなくてもなんとかなった
  • 専用の器具がなくてもなんとかなった

出来事

問題は上に書いたような感じです。前日まで一切兆候がなかったのでビビりました。 ”b” キーは使用頻度が高いので非常に困りました。

当然、他のPCに繋いだりケーブルを替えてみたりしましたがどれもダメ。どうやらキーそのものに問題がありそうだという事で検索してみたところ、こちらのページを発見しました。

ErgoDox の壊れたキースイッチを交換する - Okapies' Archive http://okapies.hateblo.jp/entry/2016/12/03/010105

通常、キースイッチの交換には専用の工具が必要らしいのですが、こちらのページではバインダークリップを使った方法が紹介されていました。 が、残念ながら家にあったバインダークリップはどれも大きめで、上手く行きませんでした。そこで家にあったもので何とかならないか思考錯誤してみましたら、なんとかなったのでした。

用意するもの

f:id:tamakiii:20170419132433p:plain

  • 毛抜き(キーキャップを外す用)
  • ピンセット(キースイッチを外す用)
  • 適当な細い棒(作業補助用/クリップやドライバーなど)

やること

使用頻度の低いキーとキースイッチを交換(暫定措置) f:id:tamakiii:20170419132913p:plain

作業開始

(言うまでもありませんが、以下の作業は自己責任でお願いします)

f:id:tamakiii:20170419014104j:plain 用意した毛抜きでキーキャップを外して行きます。専用の引き抜き工具がなくても割と何とかなります。作業の邪魔になるので、対象の左右のキーキャップも外します。

f:id:tamakiii:20170419014209j:plain 左右も外した様子。四つ角に穴が空いているのが見えます

f:id:tamakiii:20170419014237j:plain 四つ角の穴のひとつにこんな風にピンセットを差し込んで、外側に押し込んでやるとツメが外れます。写真は左ふたつのツメを外したところ

f:id:tamakiii:20170419014301j:plain このまま右側ふたつを外そうとすると、左側のツメが元に戻ってしまうので、適当な細い棒を左側に差し込んでおきます

f:id:tamakiii:20170419014334j:plain 差し込んだ棒を抑えながら、右側のツメを外すと

f:id:tamakiii:20170419014444j:plain このように外れました   (゚∀゚)ラヴィ!!

f:id:tamakiii:20170419014504j:plain 中はこのようになっていて、金色の電極部分は基盤にはんだ付けされているそうなので、これを壊さないように注意が必要です

f:id:tamakiii:20170419014545j:plain 外したキースイッチはこんな感じ

f:id:tamakiii:20170419014602j:plain 挿げ替え対象も同様に外して、あとは戻すだけで作業完了です

おわりに

f:id:tamakiii:20170419014858j:plain あまり需要のない記事でしたが、私自身けっこう困ったので、そのうち同じような状況になってしまった人の役に立てたら何よりです。

では。

分子に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や広告配信に興味を持たれましたら, お気軽にご連絡ください.