2013年10月07日

ISUCON 3 っていう、インターネット系エンジニアが全ての技量を試されるガチンコバトルなイベントがあって、kamipoさんとgfxが「あと1人チームメンバー募集!」って言われたのでふらっとパーティに入って参加してきました。

暫定順位みると、大体11位っぽい?

前日

僕は金曜の深夜に飲みすぎ+生ものに当たって土曜日は寝込んでて日曜も朝からダメだった、gfxもなんか体調悪かった、kamipoさんもなんかあれらしかった。

当日合流前

kamipoさんだけ、素晴らしいオフィスで有名なフリークアウト入り。僕とgfxは最初から遅刻宣言。

開始直後

kamipoさんだけawsアカウントの準備してて現地ついてたらしい。僕らは遅刻って言ったからおにぎり食べてたらしい。

合流前

おなかおかしいから、フリークアウトのビルの前でやってた丸しぇに玄米リゾット売ってたので買おうとするも、店員さんがコンビニ言ってたらしく10分くらいまってからリゾット買って合流した。

合流後

10:49 <@kamipo> いまの時点まででやったこと
10:49 <@kamipo> percona-toolkitいれた
10:49 <@kamipo> apacheをnginxに入れ替えた
10:59 <@kamipo> webapp以下をgit pushした https://github.com/kamipo/isucon3

ていうログみたから、とりあえず checkout してみる。

すでに nginx にして800くらいのスコアから2000くらいになったとか言ってた。

ポリシー

他の人がどう思ってたか知らないけど

  • どうせ予選なので決勝進めれば順位にこだわらない
  • 普通の仕事と同じ事をする
  • 運用可能な変更だけする
  • ベンチが早ければいいじゃなくて、引き継いだ人が使える物にする
  • プロセスが落ちてデータロストとかしない
  • 余計なミドルウェアとかいれない
  • というか、元々のシステム作った人が逃亡して仕様書が無い状況で、現状のコードから意図を汲み取って同じ挙動で早く動かす
  • そもそも仕様がガチで reject するレベルで糞だけど友達のサービスを助けると思って目をつぶる
  • さっさと帰って寝たい
みたいなポリシーで進めていこうとおもいました。
というか、二人とこれといって打ち合わせとか意識合わせとか何もしてない。

やったこと

foreach (userid IN memos.userid) { db.execute("SELECT * FROM users WHERE id=?", userid) }みたいな糞みたいなクエリを1個に纏めた
たぶん20%くらい早くなった。

アクセスするたびに users テーブル見る糞実装してるので、直すついでに変更が一切発生しないデータをキャッシュに保存してqueryを減らした

kamipoさんが「ORDER BY created_at DESC, id DESC の created_at は自明過ぎて不要だし index ないから ORDER BY id DESC だけにして」って言ったからした
ここで

13:17 <@Yappo> Result:   SUCCESS 
13:17 <@Yappo> RawScore: 3828.3
13:17 <@Yappo> Fails:    0
13:17 <@Yappo> Score:    3828.3
くらいだった。

memos.is_private で、全体公開とプライベート用のメモのリストの出し分けを辞める
memos.is_private=0 かどうかで、メモ一覧を出し分けると is_private には index が貼られていない、そして 0 or 1 なのでカーディナリティが低すぎる。ということで、パーティションにしようかとも思ったんですが、パーティションにすると自分自身のメモを見るときは is_private の値が関係無くなってコストかかってしまうので、 public な memo_id だけを保存しておく public_memos というテーブルを作りました。
このテーブルは後にも活きてきます。

メモ全件数を保存しとくテーブルを作った
public_memos に呼応した kamipo さんのアイディアでメモの全件を保存しとくテーブルを作りました。 trigger 周りは全部 kamipo さんが言ってた奴で僕の中ではすっかり存在忘れてました。
gfx がメモの全件数を cache にぶっ込んで、メモを追加したら incre するやつ入れてたけど INSERT と incre の間に GET がくるとベンチマークが FAIL するような現象があったのでキャッシュ辞めました

OFFSET が糞みたいな感じだったので OFFSET 無くてもサーチ出来るようにした
わりと LIMIT 100 OFFSET 13200 みたいなあり得ない感じのクエリが飛んでて、僕とか Senna+MySQL ハックで OFFSET のこういう感じなのやばいの知ってたんですが、どうこう言ってる間に kamipo さんが前述の新規テーブルを組み合わせて良い感じのクエリ作ってました。

前後のメモのリンクの為のIDを効率よくとる
もとの実装が「そのユーザの作ったメモの全レコードとカラムを全部メモリに読み込んでプログラム側でループを回して前後関係を取る。」みたいな信じられない実装だったので KEY(user_id, memo_id) 的な index を追加して1個のクエリで解決しました。いちおう「自分自身の場合は is_private やつをだして、他人のだったら public なのだけ出す」みたいなロジックだったけど、すでに public_memos ってテーブルが別に作ってたのに UNION して解決でした。

既存の users テーブルの内容を cache に乗せる
うっかり忘れてたけど、初期化 SQL でユーザレコードが結構作られていて、この部分のログイン処理とかでキャッシュが聞いてなかったので、ベンチマーク初期化スクリプトでキャッシュあっためるようにしたら2000位スコア上がって11200くらいになった気がします。

考えた事

割と「どーせ SQ Lがダメで IO おかしくなるだろから、まともな SQL にして様子みよう」みたいなノリで DB schema みてから、ざっとアプリケーションコードみて無駄なクエリとか消してました。無駄なループの中で無駄な重複クエリ消すとかそういうのですね。

kamipo さんは逆に slow query log を実直に解析して僕とかに問題点報告してて「これこれこうしたらいいよ!」みたいに言われてたけど、大体僕もアプリ側のコードみて問題把握してて実装してたりするので「そうそう、かんぺき!」ってかみぽさん言ってました。

gfxは普通にプロファイリング取ってたりとかそれの対応とかしてたけど、どっちかというと僕と kamipo さんの見てる問題領域が近くて gfx の方まで見れてなかった感満載だったのが本線での反省点だった。 gfx は xslate まわりのチューニングとか markdown 周りとか starman/starlet あたりしてたのだけは把握してる。

二人とも「想像するな、計測しろ」を自で生きて来た人間だから、僕はそういうのとくにしないでコード見て常識的な事を常識的にやってました。

最初のほうでザックリ作業終わってからベンチとりながら top してたら mysqld があり得ないくらい120%くらいCPU食ってるし io 全然幸ってないから、こりゃありえんって感じで SQL 頑張ってたな。ある程度回復するとようやく CPU が正常になってきて「これから本番」って感じがしてきてよかった。この辺りから --workload 2 とかが意味でてきたし。

どたばた的なの

インスタンス1個でやってたからベンチ回すのに、アナログな queue が発生してたりとか、開発環境ないがしろにしてて git の作業が各人バラバラでおかしくなってたので本戦でれたらどうにかしよう。

とちゅうのどうでもいいこと

あんま意味ないけど plack process に strace してベンチ回してたら js ファイルとかを app server が処理してたから kamipo さんに「なんで static file をバックエンドに飛ばしてるの!」っていったら、 nginx でそのファイル開けない!とか言い出しててよくよくきいたら /home/isucon が 0777 になってたせいで ssh login できないから 0700 にしてから open できなくなったとかだったので「それ 0711 でできるよ」って言って解決した。切羽詰まるとわりとありがちだなーっていう出来事でした!

おわってから

最後の1時間くらいは、割とデッカいリファクタリングしつつキャッシュしてスコア伸ばそうとしてたけど、なんかエラーでちゃったし焦る場所だったけど、焦ったらアウトだからゆるふわにやってて間に合いませんでした。
いちおう間に合わせようとして、酷いコードで実装始めてたら FAIL がでちゃってて kamipo さんがレビュー始めたけど、よく見るレビューしてる時の kamipo さんみたいになってて大変そうだったw

memcached じゃなくて mysql memcache plugin とか言われて、あわてて memcached のポート使うようにしてテストしてみたけど、特に計測値かわらなかった。 mysql すごい。

ログインした時に最終ログイン時刻を記録する為に UPDATE してるとこあるけど、見えない要件で必要かもしれないしパフォーマンスに大差ないと見込んでスルーした。
もう kamipo さんの slow query プロファイルでも SQL で問題全部潰し終わったの確認してたし。

まとめ

他のチームは、バリバリキャッシュ頑張ったりアプリケーションのメモリの中に全部入れてたりミドルウェア追加したりとか頑張ってたらしいけど、割と最初の構成から殆ど変えないで、普通の仕事のノリで実直に軽いノリでやってても11位くらいの順位になったのは割と満足いく感じだと思います。

言葉では語られない fujiwara さんの苦労が色々と見える題材でほんと fujiwara さん凄いとおもいますが、本戦はもっとがんばってください^^

Posted by Yappo at 2013年10月07日 02:03 | TrackBack | tech
Comments
Post a comment









Remember personal info?






コメントを投稿する前に↓の場所にnospamと入力してください。