無印吉澤

Site Reliability Engineering(SRE)、ソフトウェア開発、クラウドコンピューティングなどについて、吉澤が調べたり試したことを書いていくブログです。

LT で艦これアーケードと Admiral Stats の色々を話してきました

f:id:muziyoshiz:20170128164643p:plain

秋葉原IT戦略研究所という、アニメ x IT ネタで同人誌を出したり勉強会を開催しているサークルがあります。そのサークルの勉強会「ShangriLa Meetup」の LT で、Admiral Stats の話をしてきました。

akibalab.connpass.com

スライド

Admiral Stats は、私が趣味で作っている、「艦これアーケード」というアーケードゲームのプレイデータを可視化するツールです。公式サイトにはない「時系列でのデータ表示機能」、「他提督(プレイヤー)との比較機能」などを備えています。

スライドの章立てはこんな感じです。

  • 艦これアーケードってどういうゲーム?
  • Admiral Stats の紹介
  • Admiral Stats の中身の解説
  • Admiral Stats を公開してみた結果

艦これアーケードをプレイしている提督の方は、Admiral Stats を試してもらえると嬉しいです。未プレイの方は、公式サイトのプレイムービー あたりを見て、興味が湧いたらプレイしてみてもらえると(ゲームのファンとして)嬉しいです。

www.admiral-stats.com

信用できる社内 Wiki をつくるために守ってほしい、たったひとつのルール

f:id:muziyoshiz:20170109184841j:plain

このページについて

この記事は、以前書いた「社内Wikiに情報を書くときに守ってほしい、たったひとつのルール」の続編です。前回は、個々のページをどう書くべきかという話をしましたが、今回は社内 Wiki 全体を信用できるものにする方法について考えます。

muziyoshiz.hatenablog.com

想定する環境

この記事は、ソフトウェア開発プロジェクトに関する Wiki が社内にあって、そこに各人がドキュメント(仕様書や手順書など)を書けるようになっている環境を想定しています。

私自身、ソフトウェア開発のときしか Wiki を使わないので、具体例もそのような環境に寄っています。ただ、ある程度は社内 Wiki 全般に通じる話かと思います。

ルール:「更新され続ける」ページと「更新されない」ページをはっきり分ける

ここ1年ほど社内プロジェクトをいくつか渡り歩いていたのですが、個人的には、このルールが徹底されている Wiki ほど「信用できる」と感じました。

具体的な実現方法はいくつかありますが、例えば、Wiki のホーム画面の上半分を「更新され続ける」ページへのリンク、下半分を「更新されない」ページへのリンクにします。以下は、ホーム画面の例です。

# 更新され続けるページ

- [[用語一覧]]
- [[サービス仕様]]
- [[インフラ構成]]
- [[アプリケーション構成]]
- [[開発ルール]]
- [[手順書]]
- [[主要機能の解説]]

# 更新されないページ

- [[ミーティング議事録・資料]]
- [[ユーザーストーリーごと(または機能ごと)の設計]]
- [[リリース履歴]]
- [[運用作業履歴]]
- [[障害履歴]]
- [[技術検討メモ]]
- [[現在はメンテナンスされていないページ一覧]]

誤解を避けるために強調しておくと、これはWiki を「信用できる」ページと「信用できない」ページに分けよう、という話ではありません。信用できないことがはっきりしているなら、そのページは消したほうが良いでしょう。

そうではなくて、これは「完全に信用できる」少数のページと、「条件付きで参考にできる」多数のページに分けよう、という話です。以下では、説明のために、もう少し具体的な話をしてみます。

「信用できる」とは?

この記事で言う「信用できる」という形容詞は、「信用できる技術者」を指すときの「信用できる」と同じような意味だと思ってください。

私が知っている信用できる技術者を思い出すと、大抵以下のような長所を備えています。

  • 専門分野について正確で詳しい知識を持つ
  • わからないことは素直にわからないといい、必要な調査やテストを行って、あとからそれを補う

同様の状態を Wiki で実現したものが、この記事でいう「信用できる Wiki」になります。

「更新され続けるページ」については、そこを読むだけで正確な情報を知ることができて、読み手はソースコードを読んだりする時間を節約できます。

一方、「更新されないページ」については、過去のある時点では事実だった(あるいはその書き手が事実だと信じていた)情報ですが、現在では正しいかわからない情報です。その場合も、読み手は、条件付きでその情報を参考にすることで、調査にかかる時間を節約できます。

逆に「信用できない」とは?

上で挙げた信用できる技術者の長所をひっくり返すと、以下のように、信用できない技術者になります。

  • 専門分野について、曖昧な知識しか持たない
  • わからないことについても「○○に違いない」と断言する

大抵の Wiki は放っておくと、信用できない技術者に似た、信用できない Wiki になってしまいます。例えば、こんな感じです。

  • 仕様ページに書いてある仕様が、その後の機能追加の際に更新されなかったために、現在の仕様と違う
  • ローカル開発環境の構築手順書が、ゼロから動作確認されなかったために、他の人が試すと動かない

こうなると、普通の人は「ソースコードを読むしかない」、「実際に試してみるしかない」と判断して、その Wiki を避けてしまいます。信用できない技術者に、相談する人はいないですよね。

その社内 Wiki には、実際には役に立つ情報もあったかもしれないのに、全体の信用を失ってしまったために役に立つ情報まで読まれなくなるとしたら、それはもったいないことです。

ルールを運用するためのヒント

このルールをチーム全体できちんと運用するためのヒントを、いくつか挙げておきます。

ヒント(1):「更新され続けるページ」は極力少なくする

このルールを採用する場合、何か機能をリリースするたびに、その機能に関連する「更新され続けるページ」はすぐ更新する必要があります。忙しい時期でもこのルールを守れるように、更新され続けるページは極力少なくしておいたほうがよいでしょう。

例えば、以下のような情報は、更新し続けるメリットが、更新作業のコストを上回ると思います。

  • 用語一覧
    • そのプロジェクト固有の用語一覧。主に、新メンバへの説明用。
  • サービス仕様
    • 顧客に提供するユーザーマニュアルや、サポート対象 OS・ブラウザの情報など。
  • インフラ構成
    • ネットワーク構成図や、システム構成図、IP アドレス一覧など。
  • アプリケーション構成
    • コンポーネント一覧、バッチ一覧、データベース定義書など。
  • 開発ルール
    • そのプロジェクトで採用する開発プロセス、開発用ツール、コーディング規約、テスト基準など。
  • 手順書
    • システム構築のための手順や、日常的な運用作業の手順など。
  • 主要機能の解説
    • そのプロジェクトで特に難しい機能や、わかりにくい機能の理解を助けるための解説記事。

ヒント(2):更新し続けられないと判断したら、「更新されないページ」に移す

理想的には、「更新され続けるページ」はできるだけ多いほうが良いです。ただ、開発が多忙になったり、逆に開発が一段落して開発者の人数が減ったりすると、更新にかけられる時間は減ります。

そういう場合は、どのページを残すかチーム内で議論して、更新しきれないページは「更新されないページ」に移しましょう。Wiki のページ数よりも、「その Wiki は信用できるかどうか」のほうを重視すべきです。

例えば、以下の階層にある「機能Aの解説」ページを最近更新できてない、と気付いたとします。

  • 更新され続けるページ
    • 主要機能の解説
      • 機能Aの解説

この場合、そのページを「更新されないページ」以下の階層に移す、という具合です。「機能Aの解説」ページの先頭(「このページについて」欄)に、更新できなくなった時期とその事情を書いておくと、読み手の助けになるでしょう。

  • 更新されないページ
    • 現在はメンテナンスされていないページ一覧
      • 機能Aの解説

ヒント(3):「更新されないページ」を「更新され続けるページ」に格上げしたいときは、必ず書き換える

更新されないページを書いているうちに、分量が多くなり、内容も充実したように思えるので、「更新され続けるページ」に格上げしたくなることがあります。例えば、以下のような場合です。

  • ある機能の設計に関するページが、その機能に関するよいまとめに思えるので、主要機能の解説ページに格上げしたい。
  • ある障害の原因を特定するためにソースコードを調査したときのメモが、ソースコードのよい解説に思えるので、主要機能の解説ページに格上げしたい。

しかし、元々これらのページを書き始めた目的は「主要機能の解説」ではないため、余分な情報が多く含まれている可能性が高いです。分量が多いほど更新が大変になるので、元のページとは別のページを作り、更新し続ける必要がある部分だけ残して、リライトしたほうがよいでしょう。

また、そもそも、有用な情報をすべて「更新され続けるページ」にする必要はありません。「更新されないページ」のなかに「技術メモ」のようなカテゴリを作り、そのなかに「2017年1月時点の技術メモ」のような形で残しておくという方法もあります。

ヒント(4):ルールを守るための責任者を立てる

上記のルールを徹底するためには、ある程度の強権が必要になります。

例えば、チームメンバが以下のようなことを始めてしまったときに、誰かがあるべき状態に戻す必要があります。このような行動は、各人の自主性に任せていてもなかなか防げません。意見の衝突が起こるからです。

  • 「自分がよく使うページだから」という理由で、ホーム画面にリンクを書き足す。
  • 「誰かが更新すべき情報だ(自分は更新しないけど)」という理由で、「更新され続けるページ」にページを足す。
  • 自分が必要だから更新し続けているページがあるが、責任を押し付けられたくないので、「更新されないページ」の中に残し続ける。

このような衝突を解決するために、ルールを守るための責任者をチーム内に立てて、その責任者は「このルールを守るためであればどのようにページを編集してもよい」と明言しておくのが良いと思います。メンバから異論がある場合はその責任者を中心に議論し、ときどき細かい運用を見直します。

この責任者は強権が必要で、かつ Wiki のほとんどすべての編集にざっと目を通す必要があるため、チームリーダが兼務するのが一番良いと思います。それが難しい場合は、簡潔な文章を書くのが得意な人を任命するのがよいでしょう。

まとめ

今回は、信用できる社内 Wiki をつくるために、私が個人的に実践している基本的なルールをご紹介しました。

このルールの目的は、「読み手が Wiki に期待するレベルを適切にコントロールすることで、読み手が Wiki に幻滅し、無視してしまうのを防ぐこと」です。Wiki のページを「完全に信用できる」少数のページと、「条件付きで参考にできる」多数のページに分けることで、読み手にとって信用できる Wiki をつくることができる、ということを具体例を挙げてお話ししました。

もしも、自分が後から参加したプロジェクトに「信用できない社内 Wiki」があったらどうするか? 元から使われている Wiki を大きく書き換えることもできないので、その場合は特定のページ以下を「信用できるサブ Wiki」として育てるのがよいと思います。このルールを色々アレンジしてみてください。

今回は社内 Wiki 全体の話をしましたが、個々のページを「信用できる」ように書く方法については、以前書いた以下の記事をご参考ください。

muziyoshiz.hatenablog.com

Phoenix Framework に関する有名なベンチマーク同士の関係

f:id:muziyoshiz:20161224204900p:plain

Phoenix Framework(以下、Phoenix)は、Elixir のための Web アプリケーションフレームワークです。

Phoenix の開発者 José Valim 氏は Ruby on Rails のコミッタだったため(Rails Contributors - #5 José Valim を見る限り、2014年まで?)、使い勝手はとても Rails に似ています。そのため、Phoenix は Rails とよく比較され、「Rails よりも10倍近く速い」という評判を時々目にします。

ただ、その評判の出処になったベンチマークについて、僕は具体的な内容を知りませんでした。また、記事によってベンチマークのリンク先がまちまちで、それぞれの関係がよくわかりませんでした。自分で Phoenix を使ってみるにあたり、このあたりを少し調べてみたので、その結果をメモしておきます。

最初のベンチマーク

Phoenix と Rails に関する最初のベンチマークは、Chris McCord(Programming Phoenix: Productive, Reliable, Fast の著者)による2014年7月のブログ記事のようです。

littlelines.com

この記事は「Phoenix は Rails より10.63倍速い」というベンチマーク結果を示しています。ちなみに、これは Elixir vs Ruby Showdown というシリーズの2番目のポストで、最初のポスト(Elixir vs Ruby Showdown - Part One)では「Ruby の i18n gem と比較して Elixir 版は73倍速い」という結果を出してます。

このベンチマークの内容は以下のようなものです。この特徴は、後述する他のベンチマークにも引き継がれています。

  • URL ‘/:title’ の :title 部分をコントローラに渡す
  • コントローラは、モデルの返り値を想定したマップ(固定値)の配列をビューに渡す
  • ビューは、受け取った配列を回して HTML を生成し、クライアントに返す
  • データベースへのアクセスは行わない
  • ログ出力は行わない
  • ENV = production で動作させる
  • ベンチマークツールには wrk を使って、30秒間テストする

具体的なコードの説明も載っているので、後述するベンチマークを読むにしても、まずこのブログ記事は読んでおいた方が良いです。

ベンチマーク対象の拡大

その後、このベンチマークに触発されて、ベンチマーク対象を他のフレームワークに広げたものが Matthew Rosenberg 氏により公開されました。

github.com

Phoenix は、Rails よりむしろ Sinatra に似た軽量フレームワークではないか、との理由から、比較対象は主に Sinatra にインスパイヤされたフレームワークから選ばれています。また、Phoenix の実装の基盤である Plug というライブラリを素で使った場合も追加されています。

  • Phoenix (Elixir)
  • Plug (Elixir)
  • Rails (Ruby)
  • Sinatra (Ruby)
  • Express, Express Cluster (JavaScript)
  • Martini (Go)
  • Gin (Go)
  • Play Framework (Java)

このテストは、Phoenix のバージョンアップに合わせて、何度か実施されています。

Round 1 と 2-3 はスペックが違うので単純に比較できません。

  • Round 1: 3.4GHZ Core i7 (quad core), 12GB RAM
  • Round 2-3: 4.0GHZ Core i7 (quad core), 32GB RAM

Round 1 はスペックが低いので、単純比較できるのは Round 2 と 3 のみです。Phoenix の結果だけ比較すると、Round 3 は 2 よりも若干遅くなっています。Plug の結果はほとんど変わっていないので、Phoenix に機能が増えたからでしょうか?

Round 3 の結果を見ると、Phoenix は Play より Consistency は優れているものの、Throughput は Play の約 0.47 倍となっています。Elixir は stop-the-world GC がないので Java より高速、と思っていたので、最初にこの結果を見たときはかなりがっかりしました。

高スペックなベアメタルサーバ上での結果

2015年7月に、上記の phoenix-showdown を Rackspace のベアメタルサーバ(CPU Dual 2.8 Ghz, 10 core, RAM 128 GB)で実行した結果が公開されました。

Comparative Benchmark Numbers @ Rackspace

スペックが高いほど Elixir の真価が発揮されるのか、このベンチマークでは、Phoenix の throughput は Play とほぼ同じか若干上で、Consistency では引き続き上回っています。Rails との差は更に開いて、Phoenix の throughput は Rails の15倍になっています。

感想(と職場ブログの宣伝)

素の状態では、Phoenix は Rails よりも10倍以上高速なのは確かなようです。ただ、他のコンパイル言語(Java や Go)と比較すると、throughput については決定的な差は無さそうです。

また、これらのベンチマークはデータベースアクセスやログ出力といった、普通のアプリには絶対にある機能を省いているので、周辺ライブラリの充実度によっては、開発者の多い Java & Play Framework で実装した方が、総合的には速くなるのかもしれません。Rails 開発者が多い環境なら Phoenix に飛びついてもよさそうですが、Java 開発者が多い環境では Play のほうが速い、ということもあるのかもしれません。

そのあたりが気になって、Elixir (その2)とPhoenix Advent Calendar 2016 の24日目として、Phoenix で書いた簡単なアプリケーションサーバに、HBase アクセスや、ファイルへのログ出力を足したら、性能はどう変わるか、という話を書きました。時間の都合で Play との比較まではできていませんが、今後気が向いたらそこまで試してみたいと思っています。

recruit.gmo.jp

記事の公開後に教えてもらったページ

togetter.com

Exrm を使った Phoenix アプリケーションのデプロイ方法を ansible-elixir-stack から学ぶ

f:id:muziyoshiz:20161122004038p:plain

これまでのあらすじ

Elixir の世界には、Ruby での Ruby on Rails に相当する "Phoenix" という Web アプリケーションフレームワークがあります。しかし、Capistrano に相当するものは無くて、デプロイの考え方は Rails とはだいぶ違いそうです。

前回は Elixir の世界のビルドツール Elixir Release Manager (Exrm) で作った tarball をデプロイする方法について紹介しました。ただし、upgrade コマンドで無停止アップグレードしたいなら、ビルド環境には最新のソースコードだけでなく、アップグレードする前のバージョンのビルド結果も置いておかなければいけない(!)という話をしました。

muziyoshiz.hatenablog.com

今回は、この Exrm を使ったビルド方法の話の続きです。

Phoenix アプリケーションの情報に関するネット上の情報

Phoenix アプリケーションのデプロイについてネット上の情報を探したところ、(検索ヒット数はかなり少なかったのですが)ansible-elixir-stack という Ansible role と、その紹介記事が見つかりました。この Ansible role のコードを読んでみたところ、デプロイ方法がやっと理解できたので今回ご紹介します。

ansible-elixir-stack の使い方

github.com

ansible-elixir-stack は ansible-galaxy からインストールできます。

$ ansible-galaxy install HashNuke.elixir-stack

Phoenix アプリケーションの mix.exs に exrm を追加してから、

$ curl -L http://git.io/ansible-elixir-stack.sh | bash

を実行すると、その Phoenix アプリケーションのディレクトリ内に、Ansible の実行に必要なファイル(playbook や inventory など)が自動生成されます。自動生成された playbook は ansible-elixir-stack を呼び出して、inventory ファイルに記載されたサーバに Phoenix アプリケーションをデプロイします。

この ansible-elixir-stack という role は基本的にオールインワンなので、Nginx サーバなども自動的にインストールしてしまいます。開発環境の構築に使うならこのままでいいかもしれませんが、本番環境を構築する場合、これを参考に独自の playbook を書く必要がありそうです。

ansible-elixir-stack を実行するための手順については、以下のブログ記事で詳しく紹介されています。そのため、この記事では特に触れません。

https://blog.johanwarlander.com/2015/07/30/deploying-a-phoenix-application-using-ansible-elixir-stackblog.johanwarlander.com

ansible-elixir-stack は自動アップグレードをどうやって実現しているのか?

deploy_type 変数での動作の切り替え

ansible-elixir-stack を普通に使うと、前回のブログ記事に書いた「方法1. ソースコードをサーバに置いて、mix phoenix.server で起動」と同じように、サーバを1回停止して、再起動します。

ただし、deploy_type という変数に "upgrade" という値をセットしておくことで、無停止アップグレードの動作に切り替わります。この動作の切り替えについては Hot code-reloading のページ に記載されていました。今回はこちらの動作を解説します。

role 内部で実行されるコマンド

ansible-elixir-stack では、初回デプロイ時の playbook(setup.yml)と、2回目以降のデプロイ時の playbook(deploy.yml)が分かれています。ただ、いずれの場合も project.yml の以下の部分で git clone を実行し、サーバ上に Phoenix アプリケーションのソースコード一式をダウンロードします。

- name: "clone project"
  git:
    repo: "{{ repo_url }}"
    version: "{{ git_ref }}"
    dest: "{{ project_path }}"
    accept_hostkey: True
    force: True
    remote_user: "{{ deployer }}"

デフォルトでは project_path は /home/deployer/projects/{{ app_name }} です。

そして、release.yml の以下の部分で、Phoenix アプリケーションをビルドします。ちなみに、mix は ~/.mix 以下にインストールされたファイルを使うため、bash -lc の指定は必須です。

- name: "compile and release"
  command: bash -lc 'SERVER=1 mix do compile, release' chdir="{{ project_path }}"
  remote_user: "{{ deployer }}"
  environment:
    MIX_ENV: "{{ mix_env }}"
    PORT: "{{ app_port }}"

そして、最後の部分で、「git clone した最新バージョンのバージョン番号取得」、および「upgrade コマンドの実行」を行います。

- when: deploy_type == "upgrade"
  name: get app version
  command: bash -lc "mix run -e 'IO.puts Mix.Project.config[:version]'" chdir="{{ project_path }}"
  remote_user: "{{ deployer }}"
  register: app_version

- when: deploy_type == "upgrade"
  name: set upgrade command
  set_fact: upgrade_command='rel/{{ app_name }}/bin/{{ app_name }} upgrade "{{ app_version.stdout }}"'

- when: deploy_type == "upgrade"
  name: upgrade app
  command: bash -lc "{{ upgrade_command }}" chdir="{{ project_path }}"
  remote_user: "{{ deployer }}"
  environment:
    MIX_ENV: "{{ mix_env }}"
    PORT: "{{ app_port }}"

上記の upgrade コマンドの実行は、仮にアプリケーション名を sample_app、バージョン番号を 0.0.2 とすると、以下と同じ意味になります*1

$ cd /home/deployer/projects/sample_app
$ SERVER=1 mix do compile, release
$ rel/sample_app/bin/sample_app upgrade 0.0.2

デプロイ作業を常にこの playbook で実行しているなら、デプロイ先サーバの以下のディレクトリには、前回のバージョンのビルド結果が残っているはずです。

/home/deployer/projects/sample_app/rel/sample_app/release/0.0.1

そのため、前回のブログ記事で問題に挙げた「ビルド環境に、アップグレードする前のバージョンのビルド結果も置いておかなければいけない」という条件はクリアされます。

しかし、そう考えると新しく追加したサーバにいきなりバージョン 0.0.2 をデプロイする、という場合にはどうなるのか?が気になります。そういうケースでは、git clone しても上記の 0.0.1 ディレクトリは作られません。ただ、その場合はアプリはまだ動いていないため、単にバージョン 0.0.2 のアプリが新たに起動されるだけで、特に問題は起こらないようです。

この方法の懸念点

サーバごとのコードの状態の違い

Capistrano の場合は、通常はデプロイするたびに新しいディレクトリで git clone が実行されます。そのため、すべてのサーバで、余計なファイルがない環境で最新のコードが実行されるという安心感があります。

一方、上記の方法では、force=Yes で git clone が実行されるとはいえ、すべてのサーバ上のファイルが同じにはならない可能性があるのが気になります。まあ、そんなことを気にするなら、無停止アップグレードは諦めて Docker でも使え、という話かもしれません。

バージョン番号を上げるのを忘れそう

この方法に限らず、Exrm の upgrade コマンドを使う場合に共通した問題ですが、デプロイのたびに毎回必ず mix.exs 内のバージョン番号を上げて、git push する必要があります。ちょっとした更新のたびにこれを実行するのはかなり面倒です。

def project do
  [app: :hello_phoenix,
   version: "1.4.1",
   elixir: "~> 1.0",
   ...

この問題への対応として、ansible-elixir-stack の作者は Hot code-reloading のページ にて「バージョン番号の末尾に Git のコミットハッシュ値を自動的につける」という方法を提案しています。

def project do
  {result, _exit_code} = System.cmd("git", ["rev-parse", "HEAD"])

  # We'll truncate the commit SHA to 7 chars. Feel free to change
  git_sha = String.slice(result, 0, 7)

  [app: :hello_phoenix,
   version: "1.4.1-#{git_sha}",
   elixir: "~> 1.0",
   ...

もしこの方法を採用するなら、この対策は絶対に入れておいた方がよさそうです。

*1:SERVER=1 というのは ansible-elixir-stack が勝手に作ったフラグなので気にしないで OK です。