鳩舎

レースしない

白金動物園として ISUCON 9 に出場し、優勝しました

感無量です。

やったことは GitHub 上ですべて公開されています。

github.com

いつものことですが、メンバーが何をしていたのか細かいことまでは知りません。

自分の記憶

  • [10:23:27] 862fe59 : Fix initialize MySQL file
    • MySQL のイニシャライズ実行ファイルのユーザーを root に変更。各位の手元で動かすときに楽させるため。
    • メンバーの手元の MySQL がどういう設定になっているのかは大体知ってるし。
  • [10:27:33] 2c68bf1 : Change database user
    • 同上事情。こちらはアプリ側。
  • [10:40:53] edd3254 : ええかげんにせえよマジで
    • app.rb で直したが、 MYSQL_USER が databaseMYSQL_DATABASE が user に渡されている問題を解決したコミット。くだらんところに罠埋めやがってええかげんにせえよ。という気持ちでコミットした。
  • [10:56:43] 0787d83 : :tada: ローカルで bx rackup と localhost:9292 で楽しく開発!
    • SPA なのにアプリが静的ファイルやマルチパスで index.html を返さない問題を解決して手元起動できるように。
    • のちに反している index.html が 0 byte なことに気づく。本物はサーバー上の /var/www/html にあって、それは後で sorah が引っ張ってきてくれた
  • [11:13:47] 98709c6 : Use our payment service
    • ペイメントサービスの URL をチームのものをデフォルト採用するように変更
    • この辺で完全にローカル開発 ready になる。同時刻少し前にペイメントサービスの CORS の問題を発見し出題チームへ連絡。コミット時には修正されていた。迅速な対応ありがとうございました。
  • [11:51:27] 8f05d4d : Static station master
    • station マスターをコードとしてダンプ
  • [11:53:45] 83aa96b : Fix stations
    • メモリから返す station が仕様と齟齬が出ていたため修正
  • [12:11:35] d15c54b : Dump master tables
    • 他マスタテーブルをコード化。Ruby の pretty print 形式でダンプした。
  • [12:22:22] 4a62d4b : Dump with marshal
    • ↑ダンプでは Date オブジェクトなどの問題があったため、 Marshal ダンプに形式変更。Marshal を信じろ。
  • [12:48:50] a730657 : Ignore direnv and log file
    • 普通に開発中邪魔なファイルを ignore した
  • [13:16:41] f618e86 : Get by memory
    • マスタテーブルから引いてるデータ取得をメモリ取得に
  • [13:24:19] edc16b8 : Get by mem 2
    • 同上
  • [13:24:20] 203c941 : Get by mem 3
    • 同上
  • [13:27:10] 8d1ea1d : Fix first seat
    • 同上の最中に発生したバグの解決
  • [13:28:07] 3a4cf40 : Fix nil bug
    • 同上の最中に発生したバグの解決
  • [13:44:02] 0b98982 : Get by mem 4
    • メモリからとってくる
  • [14:17:33] 3d467e5 : Allowed?
    • この辺からアプリケーション特有の問題の解決へ乗り出し始める
    • これはまず初手で、○△☓と残席数を表示する箇所が、フロント UI で『高負荷時は出ないときもあります』と宣言していたことによって試した変更
    • 『高負荷時とは?』『ISUCON 中は高負荷では?????』という安易な発想により(計算したくないし)全部固定で返すようにしようとする。
    • 結果は一行後わかる。
  • [14:20:08] 307fb0a : Fix fake avail
    • ダメでした。
  • [14:38:01] 114c047 : Cache seat available
    • でもやっぱ残席数がネックで遅いのでキャッシュして解決しようとする。実際、ここのチェックで問題でるほどチケット売れたチームいないんじゃないか?
  • [14:46:39] e57f0b9 : Long cache
    • キャッシュ戦略が通ったので調子こいてキャッシュの残存期間を伸ばす。30秒。怒られるだろうな〜って半笑いで入れたら通る。真顔になる(残席数チェックがずっと同数なのはチケットが売れていない = 負荷がさばけていない証左である)。
  • [14:47:07] 43d3ac4 : long avail days
    • Available days をデフォルトの10日から20日に増やしてみる。
  • [14:51:11] d5d5898 : Back to days
    • 即下げる。秒で怒られた。ひーん。
  • [15:28:22] e13716e : Good bye train master
    • Train Master テーブルへのアクセスを潰していく。Train Master は行数も多く、ここにアクセスしなければしないだけ、MySQL のメモリが空くことを狙っていた。事実、再起動後にスコアが上がったので戦略は正しかった模様。
  • [15:42:25] c22d034 : Change avail seats query
    • 残席数計算が必要のないテーブルをジョインしまくる謎の実装になっていたので削りに削った。あと、reservation に dep_id と arrival_id が入ったのでジョインの必要性が上がったというのもある。
    • これはそこそこ効いたっぽい。UNION でテンポラリテーブル作るのがな〜と言って、別立てのクエリ複数回にするかを悩むものの、キャッシュもされてるしネックじゃないからほっとけということでほっとく。
  • [15:56:41] d37fd2d : Seat resevation and date train info
    • seat_reservations に date と train_class と train_name を入れる。これで reservations とジョインしなくても大体発1で引けるようになる。インサートコストよりセレクトコストが重い。
  • [15:58:05] 5302b53 : Add placeholder
  • [16:10:07] bdb9986 : Unjoin resevertion
    • ↑のパッチで目指した通り。SELECT FOR UPDATE のロック範囲を絞りたいという狙いもあった。
  • [16:44:22] dac871d : Fix seat_reservations select
    • 予約時、 seat_reservations への N+1 クエリがバチボコ出るので1本化する狙い。実際問題うまく行ったし、効果も高かった。何十本と出ていたクエリが1本になると効果が高い。
  • [16:54:17] 92d3255 : Good bye seat master
    • ↑などの効果もあって、 seat master へのアクセスがどんどん必要なくなってきた。当然に大きなテーブルなので読まずにいれば MySQL のメモリが空く。当然アクセスやめる。
  • [17:09:25] dc767ed : Fix error
    • ↑↑のパッチがバグってたので直した。
  • [17:23:35] cbeff5f : Remove seat master
    • 最後まで seat master へのアクセスがちらほら残ってたのを潰しきった。
    • 貢献できたこととしてはこれが最後。

他やったことは残りの二人のエントリに詳しいはずなので、二人のエントリを待つといいです。

ISUCON と白金動物園

正直、優勝できたことが嬉しすぎてよくわからない。コミットを見返してみると本当に飛び道具めいたことは全然してなくて、ただただ愚直に、効きそうなことをじわじわと進めていった。残席数全部固定で返そうとしたやつが少し飛び道具だったけど、当然のように失敗した。

白金動物園としてチームを組んで、もう6年になる。6年間、成長し続けてきたかと言われれば自信がない。正直、会社経営者としての成長の方が多くあり、エンジニアとして強くなっていったのかでいえば少し腕が落ちたんじゃないかという危惧すらある。ISUCON 5で準優勝した時、1時間で Go 製 HTTP プロキシを書かされたが、あれを今やれるかと問われればかなり難しい。

予選前に id:mirakui が出ないかもしれないと言い始めた時、強い落胆と失望。そして安心があった。落胆や失望は『そうやってこの戦いへ背を向けるのか』というリーダーへの恨み言であり、安心は『彼がそう言ってくれるなら、あの恐怖の戦場へ向かわずに済む』という安堵だった。ISUCON は怖い。あくまでゲームでありコンテストだとわかっていても、スコアが出て、かつ業務らしく見せかけてくるアプリケーションを高速化するという設定が、自分のエンジニアとしての性能を明確に測られている気持ちにさせる。ISUCON で負けることで『お前はもうエンジニアではない』と誰かに言われているような気持ちになってしまうのではないか。あるいは、負けたことを言い訳にして『エンジニアとしては引退ですかね〜』などと心にもない言い訳を披露するハメになるのではないかと怯えていた。

だが出たかった。勝ちたかった。このチームで勝つことに価値があった。

スコアが発表されていく時の気持ちをまだ覚えている。3位のスコアを見て上位陣は全員 FAIL したのか!と怯え、2位のスコアを見て FAIL を確信し、1位のスコアを見て自分たちの最終計測より高くて『あ、関係ないわ』となってヤケを起こしかけた。白金動物園と名を呼ばれた瞬間、会場の全員を無視してメンバーに抱きついた。冗談やギャグのように思えるかもしれないが、あの20秒の間、会場にいる全ての人間がどうでも良かった。目の前にいる id:mirakuiid:sora_h の2人にだけ用事があった。2人もまた、会場を無視して互いの方を向いて腕を広げていた。

終了後、いくつかのチームに『白金動物園に勝ちたかった』『打倒白金動物園でした』みたいなことを言われた。正直、自分たちに耳目が集まっているなんて一切考えていなかったし、ぶっちゃけ本戦出場率50%のチームに用事もないだろうと思っていた。いままで他の優勝者や入賞者へやっかみや憧れがあって、自分たちはまだまだ弱いチームだとしか思えていなかったから、本戦後2次会で、メンバーの2人や戦ってくれたみんなに対してありがとうの気持ちがめちゃくちゃあって、何言われても楽しくて誇らしくて、これが優勝チームの気持ちか……とリーダーと声を潜めて少し笑った。

自身のエンジニアとしての成長に自信が足りなくても、チームとしての成長ならば 100% 保証できる。俺たちは強くなった。焦りが減り、無謀さを捨て、勇気を得て、信頼を作り上げた。6年前、チームを組もうと声をかけてくれたことに感謝したいし、切磋琢磨の場をつくり続けてくれた id:941 さんや歴代出題者たち、始まりを立ち上げてくれた古き強豪たち、すべてのみなさんへの感謝しか無い。

毎年おこなわれているのを眺めながら『まぁでももうちょっと技術力付けてからチャレンジかな……』と思っているみんなにはぜひ参加して欲しい。できれば、3人で。人生で何よりも信頼できるチームメンバーを得られたことは、僕の誇りだし、憧れるしかなかった古強者たちと肩を並べられる場所にくるまで成長する必要を感じられたのも、ISUCON という大会のおかげだ。

今年の優勝チームは Ruby だったらしいぞ、ってんでちょっと盛り上がってるけど、それすら僕個人にとってはフレーバーでしかない。ただ、使い慣れた言語で勝つ、という強いプライドとポリシーを保ちきれたことには、誇らしい気持ちがある。

次の ISUCON はめでたくも10年目ということで、大きな節目の年になる。来年どうなるかはまだわからないけれど、ぜひ参加したい。ウチは毎度奇数年しか本戦いけてないチームだから、もしかしたら予選落ちとかしてみんなから総ツッコミくらうかもしれないけど……w

なんだかまとまらないブログになってしまったけど、正直な気持ちをダンプしたらこうなったので、読みにくさは許して欲しい。

みんなありがとう。また相手をしてくれると嬉しい。

勝負だ!かかってこい!