ISUCON6予選で負けたので反省します

ICFPCが楽しかったのでまたチーム戦しようという話になり、かねてから興味のあったISUCON6にチーム「イーグルジャンプ」として、id:pepsin_amylaseとmkutとともに参加しました。Python実装を選び、8時間かけて12830点しか取れず、社会的には完全に人権を喪失しました。以下、反省文です。

やったこと

  • Opsフローの策定 (osa_k)
    • 基本的にはpull-reqベースで開発を進める。masterは常にデプロイ可能な状態に保っておき、featureをマージするときにはmasterからfast-forwardできることを要求する。
    • 設定ファイル類はすべてリポジトリに入れておき、通常のデプロイ操作だけでnginxやMySQLの設定まで変更できるようにする。
    • デプロイ時に変な操作をして環境破壊することを防ぐため、Rundeckジョブを叩いてデプロイする。
  • 本番サーバの他にテスト用サーバを立てる (osa_k)
  • 秘伝のタレnginx.confとmy.cnfをサーバに置くが、たまたまアプリ実装のバグでアプリの起動に失敗し、原因を切り分けるため設定ファイルをロールバックしたまま忘れる (osa_k)
  • RundeckとSlackのインテグレーションを設定する (osa_k)
  • 静的ファイルをnginxで配信する (osa_k)
  • isutarをisudaに併合する (mkut)
  • ついでにスター処理をRedis化する (pepsin_amylase)
  • ログイン処理とキーワード一覧の取得をRedis化する (pepsin_amylase)
  • リンクのsha1をキャッシュする (pepsin_amylase)
  • htmlifyの結果をキャッシュする(別プロセスでひたすら最新30件のキャッシュを作りRedisに突っ込む) (osa_k)

やってよかったこと

情報共有の時間を取る

全員の作業のキリがよくなりそうなタイミングで小休止を挟み、現在自分が何をやっているか、何が分かったかという情報を共有する時間を設けることで、全員が全体の状況を把握できるようになるとともに、改善案を取捨選択して重要そうなものに注力することが可能になりました。相談せずにずっとコードを書いているとだんだんノッてきて書きたいものは増えていくのですが、すべて実装する時間はないのでアイデアの切り捨てが必要になります。強制的に休憩を取ってミーティングを行うことで、全員でアイデアの共有と吟味ができるため、大きな手戻りは防ぐことができました。1人でもそのへんは同じだと思いますが、チーム戦だと誰か1人が言い出せば良いので、アイデア吟味の時間を取り忘れる可能性が下がるのが良い点です。

開発フローを定める

今回はmasterを常にデプロイ可能な状態に保ち、各自がfeatureブランチをmasterから作ってfast-forwardな状態を保つことで全体の信頼性を確保する、いわゆるGithub FlowContinuous Deliveryと呼ばれている手法を使いました。ISUCONのようにあるタイミングで必ず動くものを提出しないといけないというコンテストでは、常にmasterがデプロイ可能という状態は安心感がありました。

ただし、featureの粒度を小さく保つのが難しく、あるfeatureで関係ない箇所のバグに気づいてfixを混ぜてしまい、他のfeatureでもそのfixが必要になったため、masterからではなくfeatureからブランチを生やすというケースが発生してしまいました。こうなるとそのfixを含んだfeatureがrejectされたときに大変面倒な状況になるため、運用には専用のgitコマンド群を用意するなど、ある程度のDevOps作業が必要になると感じました。

やるべきでなかったこと

Rundeckを小規模オペレーションに使う

これが(自分が担当した中では)一番の敗因でした。コンテスト中は、アプリのデプロイ手順はテスト環境でも本番環境でも変わらないという理由により、ひとつのRundeckジョブにテスト用と本番の両方のノードを登録して、ジョブを叩く人がデプロイ先を選ぶ形にしていました。しかし、そもそもRundeckはChefやCapistranoのように、複数のサーバで同じコマンドを同時に叩くというオペレーションが主眼に置かれており、本番とテストのデプロイ操作をひとつのRundeckジョブで共有しようというのは、かなり踏み込んだ自動化をしないと無謀です。実際、テスト用サーバで作業している最中に間違ってデプロイを叩かれ、作業中のデータが消えるという事件がありました。また、ノードのマシン名やログイン情報などは手書きでXMLを編集しないといけなかったり、ジョブ定義のUIが煩雑でTry & Errorを前提とした改修に向かなかったりと、設定作業が割と煩雑なため、どうしても使いたいのであれば、プロジェクトの生成から全部スクリプトで自動化するべきでした。練習中はRundeckジョブを叩いてくれるhubotを運用していたのですが、Rundeckを通してデプロイしていると開発のテンポが悪くなることと、そもそも本番で設定がうまく行かずに動いてくれなかったため、手動でRundeckを叩く運用にしたのも良くなかったです。

とは言えGUIでサーバを触れるようにするという方針は、実行ログや処理内容が一箇所に集約できて管理が楽になるという点で、間違っていなかったと思います。後述のように本番環境と開発環境の分離がうまくできず、Rundeckで管理する対象の大きさが不必要に膨れ上がってしまった点が問題なので、これは本当に集約管理しないといけないか?という切り分けをリソースごとにじっくり考えるべきだったと言えます。

環境設定をしていた時間のほとんどはRundeckの設定を調整していた時間になっていたので、この規模の用途でRundeckを使ったのは完全にオーバーキルであったと思います。使うにしても、設定を可能な限り自動化してやるべきでした。

開発用サーバをケチる

練習の段階では、ローカルで環境構築する手間を省くために個人ごとに作業用VMを立てようという話をしていたのですが、AzureのFree tierプランでは1リージョンに4コアまでしかマシンリソースを置けないという制約があり、面倒だったので開発用兼テスト用サーバの1台しか用意しませんでした。また、前述のようにRundeckの設定が煩雑だったため、ノード数を増やすと作業量が増えてしまうという問題もありました。結果として、3人チームなのに2人しかサーバ環境が使えず、1人はローカルで環境構築する必要があるという二度手間になってしまいました。

急な方針転換はオペレーションが混乱するため、可能な限り避けるべきです。設定ファイルのチューニング忘れも完全にこの辺の混乱が尾を引いてしまった形です。また、アプリの開発環境整備は、処理系のバージョンを揃えたり、DBの導入をする必要があったりと結構面倒な作業を(特にMacを使っていたりすると)含むため、なるべくなら自動化されたものをそのまま使う方が良いです。

開発環境とテスト・本番環境の分離をはっきりしない

開発用サーバとして用意したサーバは、実質テスト用サーバを兼ねており、作ったブランチを試したいときにはアプリをデプロイし、ベンチマークに投げるという運用がなされていました。しかし、当初はデプロイ処理の大部分がRundeckジョブとして記述されており、ちょっとしたテストをするにもブランチをpushしてどこかにデプロイするという作業が必要で、全体で1〜2分ほどかかっていたため、かなりまどろっこしい運用になってしまっていました。

後半ではデプロイ作業の大部分をdeploy.shとしてまとめ、Rundeckジョブではこのシェルスクリプトを叩くだけになりました。こうすることで、開発用サーバ上でも開発者がdeploy.shを叩くだけでデプロイできるようになったのですが、開発環境はコードの編集からデプロイまでの素早いイテレーションが求められ、テスト環境では可能な限り本番に近づけた運用が求められるということに気づかず、ごちゃ混ぜの運用をしていたのは大きなミスでした。そもそも今回は短時間コンテストな上、間違えて環境を破壊しても復帰が容易だったので、テスト環境という概念自体が不要だったと思います。

プロファイルを取らない

ボトルネックの特定は主にnginxのアクセスログの解析と目視のコード確認で行っており、トップページのレンダリングにかなり時間がかかっていることは比較的早い段階で分かっていたものの、キーワードやログイン情報のキャッシュなど、本質的でない箇所の高速化に時間を割いてしまい、htmlifyのキャッシュに取り掛かったのは終了2時間前でした。ちゃんとした経験か洞察力があれば、巨大な正規表現ボトルネックであると看過できたはずなのですが、そうでない以上は闇雲に最適化を進めるのではなく、行レベルのプロファイリングなどで実行時間を測定し、定量的にボトルネックを探すべきでした。

このあたりはサーバ設定が遅れたことでオペレーションが混乱し、データが欲しいときにすぐ得られる状況でなかったため、nginxのログの確認すらスムーズに進められなかったことも大きな原因になっていると思います。環境構築の遅れはとにかく致命的です。

まとめ

せっかくのチーム戦だし、普段から興味のあったオペレーション役をやりたいと思っていたのですが、過度に複雑なオペレーションを組んでしまって爆死しました。開発経験なさすぎですね……。ツールの使い方や最適な開発フローなど、自分1人の開発でも勉強できた点は多かったと思うので、個人プロジェクトでいろいろ試して来年のISUCONまでに経験値を積みます。