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

mixi engineer blog

ミクシィ・グループで、実際に開発に携わっているエンジニア達が執筆している公式ブログです。様々なサービスの開発や運用を行っていく際に得た技術情報から採用情報まで、有益な情報を幅広く取り扱っています。

ステージングサーバ予約アプリを自作したよって話

mixi

こんにちは。よういちろうです。今日はOpenSocialなどmixi Platformの話ではなく、最近開発した「あるWebアプリ」についての話をしてみようと思います。

いつの時代も予約って大変!?

このエントリを読んでいる方々の多くは、何らかのシステム開発に関わっている人が多いのではないかと思います。その規模には大小があり、エンタープライズ向け or コンシューマ向けがあり、最近ではWebアプリ or スマートフォンアプリといった区分けもあるでしょう。こういったシステム開発において、よく使われるテスト手法として「ステージングサーバの利用」があげられます。「本番サーバじゃないんだけど、開発機でもない中途半端なもので最終確認する」ためのサーバ、というものですが、一般的には限りなく本番環境に近い環境を準備して、環境の違いからくる不具合などを事前に解消、確認した上で本番環境にリリースする、という目的で実施されます。一発勝負!ダメだったらすぐに引っ込めればいいじゃん!という男らしい姿勢な開発ではなくちゃんと段階踏みましょうね、ということで弊社では現在ステージングサーバでの動作確認をリリース前に実施しています。

このステージングサーバという代物、奪い合いになったりしませんでしょうか?

クラウドや仮想化という分野の進化で、かなりサーバ自体は柔軟に増減をさせられるようになってきましたが、まだまだ多くの企業では限られたステージングサーバの台数内でうまく予定をつけて捌いている、という状況だと思います。弊社もそのうちの一つであり、「ステージングサーバの確保」はエンジニアにとってリリース日を予定通り迎えるための重要な儀式だったりします。サーバの用途によってステージングサーバの台数にばらつきがあって、比較的多く準備されているカテゴリであればいいのですが、少ない台数のカテゴリにおいては「うわ、その期間サーバ空いてないじゃん」とか起きうるわけです。そうなってしまうと「ごめんなさい、動作確認できないっす!リリース日を延期したいっす!」なんてことになってしまいます。決して良い状況とは言えません。

そして、サーバ台数が多いカテゴリになると、別の問題が生じます。どこの会社でも「○○月○○日の○○時から会議室空いてる?」「わかんねーっす!えっと、スケジューラの空き室検索を呼び出して・・・」というように、施設予約の容易さは日々の精神安定上大きな要因だったりしますよね。まだスケジューラというシステムで探せるだけマシかもしれません。これがExcel管理だったりすると・・・、いろんな人が発狂することでしょう。まだExcelならいいかもしれません。これがWikiの表で管理だったとすると・・・、考えたくもないですよね。

弊社は・・・残念!

ではmixiの開発においてステージングサーバの予約はどうだったのか・・・そう、Wikiだったんです。TracのWikiページで表をみんなでいじって、自分で空いてるサーバを目視で見つけて、行を追加していました。百人以上のエンジニアがそれをみんなやっていたんです。もちろん、予約が重複してることも多々あったし、間違えて他人の予約を消してしまうことも当たり前にありました。事故って当然の状況です。

だがしかし、人間ってそんな環境にも慣れてしまうんです。怖いです。入社してすぐに「ありえねー」と言ってた人も、数週間後には何も言わずにWikiを編集しています。大事なことなのでもう一度言います。怖いです。

そんなこんなで生み出しました

便利なアプリを作ってみんなで共有すれば幸せになれる、誰しもがそう考え、でも誰も手がけてきませんでした。そして、そんな状況下で良いアプリを作れば、その人は神になれるかもしれません。そこで、ちょうど作りたいものがなくてアイディアに飢えていたこともあり、自分で作ってみました。

機能的な特徴としては以下があげられると思ってます。

  • 予約したい期間を指定するだけで、空いているサーバを自動的に探して確保してくれる。
  • ダブルブッキングの心配なし。
  • QRコードの表示によって、ガラケーやスマフォでの動作確認をサクッと始められる。

このステージング予約アプリ、最初のバージョンはErlangで書き始めました。具体的には、以下の構成です。

  • Dojo toolkit 1.6
  • Erlang R15B
  • Mnesia (OTPに付属されてるデータベース)
  • mochiweb (Webアプリフレームワーク)
  • meck (モックライブラリ)
  • qrcode (QRコード生成ライブラリ)

せっかくなので、Githubにアップしました。

https://github.com/yoichiro/mixi-staging

ちょっと解説

Dojo toolkitは昔から使っている慣れ親しんだJavaScriptライブラリです。豊富なGUIコンポーネントが揃っていて、特にデザインを頑張らなくてもこういった用途のGUIがそれっぽく作れます。

Erlangには、処理系に最初からMnesiaデータベースが含まれていて、情報の永続化をすぐに始めることができるようになっています。これには、Erlangのレコード単位で情報を格納していくことができます。例えば、

-record(user, {
    id :: string(),
    name :: string(),
    email :: string(),
    password :: string(),
    irc :: string()}).

というuserレコードが定義されていたとすると、

NewUser = #user{
    id = '12345',
    name = 'Yoichiro Tanaka',
    email = 'a@b.com',
    password = 'hogehoge',
    irc = 'yoichiro'},
F = fun() ->
        mnesia:write(NewUser)
    end,
case mnesia:transaction(F) of
    {atomic, ok} ->
        {ok, NewUser};
    Other ->
        {error, Other}
end.

という感じで1レコードを格納でき、

Q = qlc:q([X || X <- mnesia:table(user),
                X#user.id = '12345']),
F = fun() ->
        qlc:e(Q)
    end,
{atomic, [User12345 | _]} = mnesia:transaction(F).

という感じでリスト内包表記を使って1件取得を行うことができます。なんて美しさ。。。

Mnesiaデータベースは標準搭載なものなので「機能もそれなりなんでしょ?」と思うかもしれませんが、実際にはかなり高機能です。レプリケーション構成をしたければ、各ノードでMnesiaを起動しておいて「となりにMnesiaデータベースがいるよ」と関数を一つ叩けば、それだけでレプリケーションが開始されます。この設定はデータベース単位ではなく、レコード単位で可能です。さらに、特定のレコードについて、シャーディングも簡単です。「シャーディングして」と指示を関数呼び出しするだけで、複数ノードに分けて分散格納してくれます。取り出す際にそれを意識する必要はありません。

Mnesiaを使ったデータベース関連の処理は、db.erlにまとめてあります。行数がかなりいってますが、後半は単体テストのコードです。Mnesia関連の関数をmeckを使ってモックに差し替えて、各関数の動作を確認しています。

https://github.com/yoichiro/mixi-staging/blob/master/src/db.erl

OTPに標準で含まれているライブラリは非常に充実しているのですが、手軽にWebアプリケーションを書くために、いつもmochiwebを使っています。これはHTTPサーバの機能を有していて、各リクエストに対する処理を自分で追記していくことができます。例えば、

loop(Req, DocRoot) ->
    "/" ++ Path = Req:get(path),
    try
        case Req:get(method) of
            Method when Method =:= 'GET'; Method =:= 'HEAD' ->
                handle_get(Req, Path, DocRoot);
            'POST' ->
                handle_post(Req, Path, DocRoot);
            _ ->
                Req:respond({501, [], []})
        end
    catch
        ...
    end,
    ok.

という感じでHTTPメソッドにより処理分岐し、handle_post()関数の中で、

process_flag(trap_exit, true),
Params = Req:parse_post(),
"ajax/" ++ Command = Path,
Pid = spawn_link(list_to_atom(Command), execute, [self(), Params]),
receive
    {Pid, Status, Response} ->
        Encoder = mochijson2:encoder([{utf8, true}]),
        Req:respond({Status, [{"Content-Type", "application/json"}],
                     Encoder(Response)});
    Other ->
        io:format("Error in route_process: ~p~n", [Other]),
        ...
end.

という感じで、各リクエストに応じた子プロセスを作ってメッセージを投げて処理を委譲し、受信した返信内容をJSONにエンコードしてクライアントに返却する、ということをmochiwebで実現できます。上記では自身のErlangノード内にプロセスを生成していますが、管理下にある別マシンの別ノードを指定すれば、異なるサーバの資源を使って処理を行うことができます。

具体的な処理は、各モジュールに分けて記述しています。例えば認証処理であればauthenticate.erlに、ステージングサーバ予約処理であればreserve.erlに、それぞれ書いています。ただし、具体的な予約処理はデータベースに近いところ、つまりdb.erlに書いています。これは単体テストのしやすさを優先したためです。HTTPリクエストを受け取って結果を返す各モジュールの単体テストは、実際にプロセスを作ってメッセージを投げ、返送されてくるメッセージの内容が妥当かどうかを検証するようにしています。

https://github.com/yoichiro/mixi-staging/blob/master/src/authenticate.erl
https://github.com/yoichiro/mixi-staging/blob/master/src/reserve.erl
https://github.com/yoichiro/mixi-staging/blob/master/src/db.erl#L291

Mnesiaデータベースのレプリケーションと、複数ノード下でのプロセス分散生成、フロントにApache+mod_balancerを置いて分散、などしていけば、かなりスケールしやすい構成を低コストで得ることが可能です。イメージとしては以下の図のようになるでしょう。Erlangが提供してくれる透過的なメッセージパッシングモデルによって、プログラムはシンプルなままで副作用を意識することなくスケール可能な仕組みを作ることができます。

将来エンジニアが数万人になり、ステージングサーバの台数が4桁いったとしても、まったく問題なさそうですね!

現在は?

このステージングサーバ予約アプリですが、先週の16日から実際に社内で運用を開始しています。上述した最初のバージョンに比べて、現在は以下のような機能追加や変更をかけています。

  • 予約の用途を並べて、今どんな動作確認が社内で行われているかを一覧で俯瞰できるようにした
  • 1ヶ月のサーバ予約状況を見れるようにした
  • 自分が予約しているサーバの一覧を見れるようにした
  • 最近追加されたサーバの一覧を見れるようにした
  • 全体的な利用統計を表示するようにした
  • 好きな画像を背景に表示できるようにした
  • 諸事情により、ErlangではなくRuby on Rails+MySQLで書き換えた

すでにちょっとした機能追加を加えてくれる人も登場してきました。自分たちの道具は自分たちで作る、こういったことをサクッとできる環境、そして自分の実力をいつまでも保っていきたいと考えている37歳の春でした。