#isucon 2014予選二日目を二位で通過した話

  • 21
    Like
  • 0
    Comment
More than 1 year has passed since last update.

チーム.datの@y_matsuwitterです。
普段はグノシーでiOSエンジニアだったりGoエンジニアだったりインフラエンジニアやってます。
今回全員初めて出場のisuconでは、風邪でふらふらしながら、主にGoの実装周りを担当しました。
ちなみに他のメンバーもec2リブート祭り+shellshockでふらふらしてました。

とりあえず異常がなければ予選二日目を三位で通過しているはずなのですが、受験終了直後の高校生ばりに不安です。
=> 二位に繰り上がって通過しました
当日までの準備やインフラでやったことの話については他のメンバーがまとめてくれるかと思うので、僕はGoで当日やったことをゆるくまとめていきます。

当日の流れ

10:00~12:00

とりあえず競技開始後はインフラセットアップを他のメンバーに任せて僕はひたすらコード読んでました。

10分くらいでデータのライフサイクル的なものを全て確認したのち、

  • ユーザーは途中で増えることはないからDBに入れる必要はない
  • ログインログのクエリが確実にボトルネック
  • 大事なのはログインログそのものより、カウントとIPやユーザーのBan処理

あたりの事実だけ把握して実作業に移りました。

martiniが使われてる、そして割と密にmartiniの機能を使ってるのを確認したので、

  1. さっさとmartiniを除去する
  2. templateパッケージを除去する

の2点を実行しました。martiniに関してはmiddlewareとして使われているsessionsを外すのが面倒だったのですが、gorilla/sessionsに切り替え、martiniのsessionsと同じインターフェイスを持つよう少しラッパーを書く、あとはnet/httpパッケージをそのまま使う方向で進めました。今回のシンプルなページのスコアを競う場合はちょっとした計算コストも馬鹿にならないのでmartiniやtemplateパッケージは外すべきだと判断した次第です。
templateの除去には、index_html.goみたいなファイルを作ってその中に直接文字列としてhtmlを書き込み、文字列連結する方向で対処しました。
この時点ではそれほどスコアは伸びず。

12:00~15:30

martiniなどを除去し終えたその次に、ベンチマーク中CPUを割と食っていた、かつIOで詰まってたmysqlをさっさとkillする作業に移りました。
業務でmysqlを使わないサーバをよく書いてるので特に悩まず、まずはRedisを使って楽に実装しようかなと思い実装を始めました。

ログイン失敗などの記録はRedisのincrコマンドを多用して実装してあります。incrコマンド、atomicですし返り値でincrの結果を渡してくれるので便利です。
このへんは同日一位だったfujiwara組も同じ戦略だったみたいですね。

また、この時間に他のメンバーに、ユーザーデータを全て、Goサーバ起動時にtsvからオンメモリキャッシュに移す作業をお願いしてあります。

これらの開発を終えた時点でとりあえず15:30ごろにベンチを取ったところ45000程度、暫定2位となっていたので、この段階でのソースコードを固めて、ひとまずの提出用としました。

15:30~17:00

この時間ではログインログを全てRedisからメモリ上に持ってくるように修正をかけ始めました。
ただ失敗だなと思ったのがこの時にsync/atomicパッケージの存在を失念していて、mutexで少し面倒な実装を組み始めたことですね。
この実装では最後まで/reportが上手く通らず17時時点で迷ったら健全な方に行くことにし、Redis版実装の整備にとりかかりました。

また、平行してGoからRedis、およびnginxとの接続をunixドメインソケットに切り替える作業も行っていました。ベンチの並列度を上げるに連れてローカルポートが枯渇していくので対処としてインフラ担当氏からのアドバイスで実施した次第です。

17:00~18:00

この時間ではもともと動いていたmysql, アクセスログ収集などで使ってたfluentdをKILLし、nginxはログを吐かないように修正しました。とにかくレスポンス時間を向上させるためです。極力、CPUをGoに使ってもらえるように計らいました。

ちなみにISUCONでは再起動後も問題なく動作することが要件に挙げられていたので、この1時間ではベンチマークを走らせる=>サーバを落として再起動する=>データの整合性 + 各ページの目視チェックを行い動作に問題ないことを重点的に確認しました。

この時間の処置でスコアは無難に50000を超えて提出が完了しました。

感想

最初から特に戦略を変えず突き進めたので初期にベンチマーク結果が伸びないとかは特に気にせずいられたことが、割と功を奏したと思います。思い切り大事。ただ、最初からRedisよりオンメモリ中心で進めていればまだまだスコア伸ばせていたのではないかというのが反省点です。
今回のmysqlを捨てるような対策は割と社内のサーバでもやってるようなことだったので、特に迷いなく一人でガンガン実装を進めていったのですが、後から話を聞いてみると、最後までmysqlを離れることとを避けているチームも多かったようで、この辺は自社サーバがガラパゴスな進化をしているのかなと思いました。

サーバチューニングにおいて計測は大事と言われるのですが、今回の取り組みでは自分は殆どそういったツールを使っていなくて、記憶している限りだとtopコマンドを数回とあとはbenchmarkスクリプトを走らせた程度です。時間が短いのと、全く最適化されてないWebサーバの実装でボトルネックになるのは大半IOなのでそれを潰し切るだけで充分スコアは叩き出せると思いましたし、実際スコアは出たので、多分正しいんだろうと思います。YDD(野生の勘駆動開発)万歳。

あと、コネクション数減らすのは基本中の基本なのでfujiwara組のCSS切る戦略は正しいと思いますし一本取られた感がありましたのでレギュレーションで弾かれてほしくない感じはあります。こういうのは経験が物を言うのでしょうか。

最近はiOSをちょっと書きつつのマネージャー業務ばかりだったので、久々にコードガッツリ書けて楽しかったです。
最後に運営の皆さん予選お疲れ様でした、決勝に進めた際はそちらでもよろしくおねがいします。

(追伸)
ちなみに、多分オンメモリのみで実装してベンチマークだけ整合性に問題ないようなサーバって今回結構あったんじゃないかなと思っていて、再起動に対する耐性、というかデータの永続化がどれくらいチェックされるのか気になるところです。