9/27(日)に開催されたISUCON5のオンライン予選に参加しました。

僕はアプリケーション側の改善、他の二人はインフラ寄りの対応をするように事前に役割分担をしていました。

“ISUCON”とは

ISUCONは「Iikanjini Speed Up Contest」の略で、LINE株式会社 (昔はLivedoor) が主催する、アプリケーションやインフラのパフォーマンスチューニングを行ってそのスコアを競うイベントです。2〜3人のチームを作って参加します。

優勝賞金100万円！今年もやります ISUCON5 開催と日程のお知らせ #isucon : ISUCON公式Blog

この週末の2日間にオンライン予選が行われました。

チームビルディング

ISUCONというイベント自体は知っていたのですが、どうも自分には縁遠いものだと思っていました。まさか参加することになろうとは。

というのも、ISUCONではチューニング対象の環境 (アプリケーション)が出題者側から与えられます。その実装がPerlやRuby、Python、Goなどで、始めに好きな実装を選びます。

出題者から提供される参照実装に「Common Lisp」はありません。

僕は普段からCommon Lispアプリケーションしか書いていないし、インフラのチューニングも業務でやるようなこともないので、出場したところでなぁと思っていたわけです。

そんな時、κeenさんに「ISUCON出ませんか」と誘われました。

でもCommon Lispは無いんでしょう、と言うと、「ISUCONはベンチマークスコアが良ければアプリケーションにいくらでも手を入れていいという特徴があるので、いっそCommon Lispで再実装してしまいましょう」、と豪胆なことを言います。

「そんな斜め上の戦略でうまくいくのか」という無粋な話もしたのですが、インフラに合わせてアプリケーションはダイナミックに変更する必要があり、いずれにしても大きく変更するなら同じだろうというのです。実際、前回の予選ではC++でアプリケーションを書き直すことで本戦出場したチームもいたそうです。

そう言われると確かに面白そうだ、ということで、誘われるままにκeenさん、Rudolph Millerさんと3人で出ることになりました。

チーム名は「clfreaks」です。そのままですね。

前日まで

とりあえず、高速なCommon LispのWebアプリケーションを目指すならWooを使うことになりそうなのは目に見えています。

かねてからTODOリストにあったWooのUNIXドメインソケット対応を急遽実装することに。どう考えてもISUCONの前日にすることではない。実装自体は大体2時間くらいで終わった。

あとはRedisを使う場合に備えてcl-redisに使い方確認とか。

過去問を見るとセッションを使うものが出ることが多そうだったのでLackのセッションミドルウェアのパフォーマンスも改善しました。不要の場合はSet-Cookieヘッダを送らないとか。本番では結局使わなかったけど。

それから夜に時間があったので、Common Lispでの軽量なWebアプリケーションの雛形を作りました。ningleかCaveman2でもよかったんだけど、性能上の不安要素はできるだけ取り除きたいという理由で、Clackを生で使ってMy Wayでルーティングすることにしました。

qlot exec clackup --server :woo --listen /tmp/woo.sock app.lisp でアプリケーションが起動することを確認して当日に備えました。

当日

"オンライン"予選なんだけど、LINEさんの好意で渋谷のLINEカフェも利用可能だったので全員で9:30に渋谷のヒカリエに集合しました。

10:00にGCEのイメージが配布され、他の二人はインフラ構成、僕はアプリケーションコードを読んでいました。

かねてからの予定では、最初の2時間ほどは作戦会議だったのですが、11:00頃から、とっととCommon Lispで実装始めちゃったほうがいいかもね、という話になって、Ruby実装を参考にCommon Lispへの移植を始めました。その間に他の二人はRuby実装をデプロイして、MySQLの改善などをしていました。

使ったもの

今回のアプリケーションはERBのテンプレートファイルが多く、静的ファイルにもしづらかったのでテンプレートエンジンとしてCL-EMBを使いました。MySQLへのアクセスはCL-DBIです。

参考実装はセッションミドルウェアを使っていましたが、単にuser_idをクライアント側に保持させたいだけっぽいのでSet-Cookieヘッダで対応しました。

あとは静的ファイルはすべてNginxで配信するのでStaticミドルウェアも無効に。Lackミドルウェアで使ったのは結局Backtraceだけでした。

無限に時間がかかる

そこから割とガツガツ実装したつもりなんですが、時間はいくらでも過ぎていく。

当初はN+1クエリの改善 ( is_friend? や get_user ) とかも書き換え中にやろうとしていたんだけど、考えている時間ももったいないので、とりあえず動かせるところまで持っていくことを優先。ローカルのDBにデータが無いのでローカルで動かすことはできない。フルスクラッチする場合はもっと早めにmysqldumpしておくべきという謎の知見を得る。

テンプレートなんてちょろっとCL-EMB流に書き換えるだけだろ、と思ったら大間違いで、テンプレート内で普通に get_user 呼んだりしていて泡吹きそうだった。 db.xquery とかテンプレート内で呼ぶんじゃない…。

デプロイ

κeenさんが前日にCapfileを用意してくれて、Capistranoでデプロイできるようにしてくれた。

シェル芸人の暴挙は阻止しました。

チャットのログを見ると、だいたい16:30くらいに最初のバージョンをデプロイしたようです。残り1時間半。

バグを踏む

ただ、デプロイしてつもりがNginxが502 Bad Gatewayを返してきます。WooのUNIXドメインソケット対応のバグっぽい。

そこからWooのデバッグを始めて、その場で修正。どう考えてもISUCON中にすることではない。

あとは、RoswellでWooを起動するとC-cで終了できないとか結構不便だった。原因は未確認。

clackupコマンドに --debug nil を指定すると、 :debug に文字列で "nil" が渡っていて真になるとかいう渋いバグも踏んだ。あとで直す。

デプロイが遅い

ISUCONみたいな時間制限のある競技だと、デプロイが遅いと試行回数が減ってしまいます。

Common Lispのデプロイが遅かった原因は、Capistranoだとホットデプロイのために毎回別ディレクトリにgit cloneして qlot install を走らせるんだけど、その度にQuicklispとライブラリのインストールが走り、さらにコードのコンパイルが行われる。御存知の通りSBCLのコンパイルは遅いです。競技中に「あいあんくらっど〜〜〜〜〜」って叫んでいたのはうちのチームだけだと思う。

対応としてリポジトリに quicklisp/ ディレクトリを含めてしまう、ということもしたけどあまり改善しませんでした。今後の課題です。

歓声

デプロイは終わったんだけど、結局アプリケーション側のバグでログインができない状態がしばらく続きました。

17:45になってようやくログインが成功し、ホーム画面が表示され、思わず「お〜〜！！！」と歓声。もはやCommon Lispで書き換えたものが時間内に動いたってだけでうれしい。

ちなみに上位チームが20000とかスコアを叩き出している中、弊チームのベンチマークスコアは5でFAILでした。

振り返り

手際の問題とかいろいろあったと思うけど、今回の出題ではアプリケーションコードのボリュームが結構あって、Common Lispで再実装するという選択をした時点で予選通過の見込みは無かったように思います。かと言って、このチームでCommon Lisp捨てて中途半端な成績を残しても悔いは残るってんで、まあ今回は仕方なかったね、っていう話をしました。

結果、Common Lispの性能上の優位性を示すことはできませんでした。

たとえば、今回は静的ファイルへのアクセスがほとんど来なかったので、思い切ってNginxを切ってWooをフロントにしてみるようなチャレンジができなかった。LackやCL-DBIのパフォーマンスがどの程度出るか、とかも踏み込めなかったのは心残りです。

いくらか見つかったCommon Lispでの問題は今後の課題にします。

ISUCON 6？

なんにせよ今回の参加は僕個人にとっては良い経験となりました。LINEの運営者陣、Treasure Dataの出題陣、あと誘ってくれたチームメンバーに感謝します。

来年もISUCONがあるとしたら、出場するよりもむしろCommon Lispのアプリケーション実装を作る出題側の手伝いをしたいと思いました。競技中にアプリケーションを書き換える時間を無くせばCommon Lispでももっと踏み込んだ性能比較ができますしね。

参考

チームメンバーのκeenさんもエントリを書いています。