偶然暇だったら出ましょうということでid:takuya-aとid:shiba_yu36とチームを組んで出た.チーム名は「ディメンジョナルハイソサイエティぬれねずみ」で,サークル名ジェネレータで決めた.僕は3回目で過去2回は予選敗退,二人は初参加.
www.doujinbu.com
準備
- 事前準備はだいたいしばゆーがやってくれて助かった
- 最初はGoでやるかって言ってたけど慣れたPerlになった.練習する時間はないので,ぶっつけ本番で問題の質によって言語を決めようという話をしていた
- デプロイはrsyncで
- 初参加の年はソフトウェア工学を重視しCircleCIが通ったら自動デプロイしていた
- これが最悪で,急いで手で書き換えてデプロイしたら自動デプロイが走ってロールバックされたり,ベンチマーク実行中にデプロイされたり
- 去年はrsyncでデプロイしていて,コミットする前にデプロイしてうまくいったらコミットできて便利だったので採用
朝
- 朝集まって最初にやったことがすし善のちらし寿司(上)の出前の予約
- ご飯が楽しみなことは大事で,スコアが出ないときにご飯がコンビニのおにぎりだと地獄みたいになる
- コラボレーションを重視して席の配置を3人横並びにした
- 3配列で仕事を作れない気がしたのでペアプロしていく作戦
- 去年も僕がウロウロしてペアプロし続けるような動きをしていた
- 序盤はしばゆーがデプロイの設定をやってくれている間にたくやさんと二人でコードを読んだ
- ER図とレコード数を書いた
- シートは1000席で固定,一方,予約のデータがたくさんあるのでここがメインの話題になりそう
- MySQLでいろんなことをやっている,画像の配信もなく,アプリケーション側ではたいしたことをやってない
- コード読むのはだいぶ丁寧にやって,気づきをホワイトボードにずらずら書いていった
- あくまで気づいただけで,そのまま手は動かさずボトルネックになっている順に直した
- 手元で動かすのはあきらめた
- ふだんの開発はDocker内に閉じ込めているのでDBD::mysqlが入ってない
- 複数台構成にするにあたってh2oが入っていたのを初手でnginxに載せ替えることになった
- 「h2o 設定」とかで検索してQiita開いたところで,この調子では勝てないでしょうという話になった
- ブラウザで開いてみるとHTTP 1.1って書いてあるのが見えて,通信の感じは普通だったのでnginxでもよさそうという判断
- ベンチマークを回してpt-query-digestを見ると,N+1クエリがボトルネックだったので直した
- 手元で動かしてないので@がなかったりuseが変だったり全然動かない,のでrsyncして直していく
- 複数台にしてN+1クエリを直したあたりで昼ごはん
- 2時間半様子を見て,2分くらいでコードを書いたら点数が急に上がったのでおもしろかった.この時点でわりと上位に行けて,小手先でやると一時的には点数が上がるけど,のちにストレージ全載せ替え組が台頭して負けるという想定をしていた
昼
- 寿司食べて,インデックスないので
KEY event_id_and_canceled_at (event_id, canceled_at)
を足したら2万点くらいになった - 点数上がっていってるけどこのままMySQLにするかおもしろストレージに載せ替えるか?という話
- 同じ席をダブルブッキングでき,キャンセルすると繰り上がりになる仕様などが隠されているので,再実装は難しいと判断してMySQLのままでいくことにした
- 書き込みが多く,MySQLとの整合性を保つのが難しそうだったので,Redisも入れてない
- 淡々とベンチ回す→pt-query-digestを見て対策,を繰り返して,調子よく進められた
- レポート生成時にSELECT FOR UPDATEしてるのをやめた
- 予約時のORDER BY RANDをやめるため,単純に空き席を引いてくる→Perl側でシャッフルして1件選ぶ→その一件をSELECT FOR UPDATEするようにした
- シートをメモリに載せるため,Web.pmの下に1000行のダンプを追加してファイルからパースするのを試した,が失敗
- 折衷案で,初回にSELECTして,結果をメモリに置いておく作戦にしたらうまくいった
夕方
- 2~3万点をうろうろしているあたりでOOM Killerに殺されて点数が安定しなくなった
- レポート生成時にSELECT FOR UPDATEやめたのが原因で,一度レポートを作ると22%くらいのメモリを掴んだままになり,並列に動いて殺されてしまう
- 予約削除でもロックを取ってるけど,直列に動かせればなんでもいいので,GET_LOCK, RELEASE_LOCKするように変えた
- メモリ使用量対策で,rowsのループの中でHashを組み立ててレポートを作るんじゃなくて,rowsのループ内で直接文字列を組み立てることにした
- 文字列連結の効率が悪いのではという話になり,
my $body = "\0" x 15000000;
したあとに代入し直すことでメモリを確保しておく試みをした - このあたり僕は見てなくて,残りの二人がプロファイルべんり君(実行時間をwarnしてくれるやつ)を実装して様子を見てくれた
- ほかのチームは管理画面用のplack workerを分けて,かつすぐ殺すようにしたと話していてなるほど感があった
- たくやさんにここだけXSでCで書いてもらおうという話をしていたけど結局やらなかった
直せなかったこと
- 予約時にwhile(1)でループして,SELECT FOR UPDATEしたあとにトランザクションを始めていてどう考えてもおかしいのだけど,そこを触るとベンチマークが通らない
- 予約の取り消しでデッドロックになってしまうのの解消はできず,500エラーを直せなかった
- 残り20分の時点でMySQLのウォームアップやりたくなったけど間に合わなかった
- レポート作るのが遅いので,レポートを生成してファイルに書き出し続けるプロセスを立ててはどうか,とか話してたけど間に合わなかった
40867点で本戦に行けることになった.思いつきで手を出さず,ボトルネックを直すようにしたことと,コラボレーションを重視して2人ペアと1人の2ラインで動いたのがよかった.情報共有できていて,ここから先どうなっているか把握してない,という事態を避けられた.しばゆーがサーバーの設定やってくれつつ,終わったら普通にコードを書く側に回ってくれて,メンバー構成的に知識はアプリケーション側に偏っていて,問題とうまくマッチして得意分野だったので勝てたという形だと思う.こっちを直したら次はあっちというおもしろい問題で,ベンチマークも待たされることなく快適だった.運営の皆様お疲れ様でした.
10月は,結婚式が朝から晩まで三次会まであり,その翌日に鴨川でビールを飲む,その直後に仕事の締切,その週末に本戦,本戦が終わったら賞金で飲みに行くぞというスケジュールで,緊張感が増してきた.
コードはこちら.
github.com
ホワイトボードの様子も貼っておきます.