hero_picture

isucon3 予選で敗退しました(うさぎ工房)

isuconは初回からずっと出ているのでこれで3回目。

いつもは同僚の@shokiri @memememomo (Uchiko) 、僕、の3人で出場するのですが

お互いの予定の折り合いがつかず、僕は出場できない可能性が出てきました。

でも僕はどうしても出場したい・・・!

そこで、いつもの社内メンバーは「進撃の超大型パティスリー兄弟」

僕は一人ソロ活動で社外の友人(@gom_oh)や元社員(ttoz)を誘って「うさぎ工房」として予選登録しました。

僕自身がOps側である所や、メンバーのプログラマPerlPSGI/Plackは初めて触る二人だったので

集まって過去ISUCONで自家製ISUCONしたり、クエリ最適化について勉強したりといった準備をしました。

結果はスコア的には5300でfinish。見事敗退となりました。

ちなみに弊社の本チームである「進撃の超大型パティスリー兄弟」チーム側は

なんと総合4位で予選通過!さすがです!

若干悔しさもあるけど、弊社から本戦にいく人がいて、本当に嬉しかったです。おめでとうございます!

そちら側の詳細はきっと彼らが記事にしてくれるはず。本戦でもばっちり頑張ってください!

こちらの記事は点数の低い僕らがやった事なので、

アンチパターンとして楽しんでいただければ。

最終構成

最終的には varnish perl mysql とちょっとだけmemcached の構成でした。

phpMyAdminを立ち上げる

まず、MySQL関連の操作でphpMyAdminしか使えない僕はphp5.5をソースコンパイル

ビルドインサーバーとして立ち上げました。これ便利ですね

1/usr/local/lib/php -S 0.0.0.0:3000
2

my.cnfを設定

APIキー登録して初回ベンチが確認する。たしか800くらいでした。

初回ベンチですぐにDBボトルネックとわかったので、my.cnfを以下に変更。

(ええ、もちろん find / -name my.cnf しました。)

1key_buffer = 512M
2max_allowed_packet = 10M
3table_open_cache = 10240
4sort_buffer_size = 1M
5read_buffer_size = 1M
6read_rnd_buffer_size = 4M
7myisam_sort_buffer_size = 1M
8thread_cache_size = 128
9query_cache_type= ON
10query_cache_size= 16M
11thread_concurrency = 8
12innodb_flush_log_at_trx_commit = 0
13innodb_file_per_table
14innodb_additional_mem_pool_size=40M
15innodb_log_buffer_size=32M
16innodb_log_file_size=256M
17innodb_buffer_pool_size=8000M
18max_connections = 2048
19max_connect_errors = 10000
20tmp_table_size=1342177280
21max_heap_table_size = 1342177280
22

インデックスを張りました

最終的にはcreated_atとか使ってなかったので無駄だった。

1ALTER TABLE `memos` ADD INDEX ( `created_at` ) ;
2ALTER TABLE `memos` ADD INDEX ( `user` ) ;
3

ここらへんで1500くらいだったかな。

フロントエンドはVarnish

フロントエンドはVarnishを使用。

編集や削除はされないようだったので、なんとかリクエストヘッダの値から「ログインしているか否か」を判別して全体キャッシュできないか考えてましたが、リクエストヘッダで判断できる材料がなく、また、Plackとか全然わからないのでヘッダーの修正とかはできませんでした。

結局フロントエンド側での大規模なキャッシュは僕の力では厳しそうだったので

静的ファイルだけvarnishでキャッシュ。設定の主要部分だけだけど以下のような感じ。

1backend web1 {
2.host = "127.0.0.1";
3.port = "5000";
4}
5sub vcl_recv {
6set req.backend = web1;
7if (req.url ~ "\.(jpg|png|gif|css|js|ico)$") {
8return (lookup);
9}
10return (pass);
11}
12sub vcl_fetch {
13set beresp.ttl = 86000s;
14return (deliver);
15}
16

スキーマとかSQLの改修

ここらへんで2500くらいだったかな。

この時でもDBボトルネックはまだまだ明らかでしたので

ここでメンバーの@gom_ohがDBに以下の改修を行いました。

内容は公開IDの一覧だけのテーブルを作ってtopページやrecentページのDB負荷を削減する、といった感じの修正となります。

1CREATE TABLE `public_id` (
2`memo_id` int(11) NOT NULL,
3PRIMARY KEY (`memo_id`)
4) ENGINE=InnoDB DEFAULT CHARSET=utf8;'
5

public_idというmemosテーブルでパブリックに公開しているだけのmemo_id一覧を入れるテーブルを作成しました。

それから、ベンチ実行後のスクリプトで現在公開中である記事のIDをインサート

1INSERT INTO public_id (memo_id) SELECT id FROM memos WHERE is_private = 0 ORDER BY id DESC;
2

上記は初期スクリプトで実行。

1#公開ページの総数
2SELECT count(*) FROM memos WHERE is_private=0
34SELECT count(memo_id) FROM public_id
5
1#TOPページ
2SELECT * FROM memos WHERE is_private=0 ORDER BY created_at DESC, id DESC LIMIT 100
34SELECT m.id, u.username, m.created_at, m.content
5FROM memos m
6JOIN (
7SELECT p.memo_id
8FROM public_id p
9ORDER BY p.memo_id DESC
10LIMIT 100
11) t
12ON m.id = t.memo_id
13JOIN users u
14ON u.id = m.user;
15
1#recentページ
2SELECT * FROM memos WHERE is_private=0 ORDER BY created_at DESC, id DESC LIMIT 100 OFFSET %d", $page * 100
34SELECT m.id, u.username, m.created_at, m.content
5        FROM memos m
6        JOIN (
7          SELECT p.memo_id
8          FROM public_id p
9          ORDER BY p.memo_id DESC
10          LIMIT 100 OFFSET %d
11        ) t
12        ON m.id = t.memo_id
13        JOIN users u
14        ON u.id = m.user',
15        $page * 100);
16

これでスコアが5000くらいになりました。

Markdownの結果をmemcacheでキャッシュ

その他はMarkdownの処理が重かったので、ここだけmemcacheでキャッシュとか、ちまちまして5300くらいに。

まんまとポート11211につないでましたが。

あとどこかのタイミングでStarmanからStarletに変更しましたがスコア的に動きはなし。

最終的にはまだまだDBがボトルネックなまま5300でフィニッシュとなりました。

感想

初日に「1位の人とか人間なの?」と思ってたのですが、

2日目の弊社メンバーが1位に輝いてて、出先からの発表見てのけぞった。

どんな事をしたのか聞くのが楽しみです。

競技中も楽しかったのですが、普段なかなか会えない友達や元同僚と集まって

お菓子ほおばりながら共通の目的をもって取り組んだ時間が勉強になったし楽しかった。

特に普段は他の二人にまかせていた所を本腰を入れて取り組まないといけない状態だったので、

今まで以上にソースを見たりDB構造を見たり、という部分に入っていけたのがよかったです。

反省点としては、

[READMEをしっかり読んで意識を共有しておけばよかった]

workloadがAMI提出時のコマンド入力で気づきました。「ただボトルネック調査の為に負荷を大きくできる」くらいの認識しか持ってなくて(んなわけないのに)、、、試せる事はちゃんと試すべきでした。結果、一度もWorkloadを変更してなかった!!

[とりあえずperlだろみたいな雰囲気になってた。]

やりたい事をちゃんとやれる言語でやる道も検討したらよかったと。PHPで着実なボトルネック修正で予選抜けたところもあってそう思いました。「こうしたら早くなりそう!」→「Perl、、というかPlackって奴でどうやんの。」→「わからん」、のコンボが多かった。

ISUCONは参加者は楽しいけど、運営の方々は本当に大変そうで少し申し訳ない気持ちに。

運営の皆様本当にありがとうございました。

そして「進撃の超大型パティスリー兄弟」、本戦がんばれー!

おまけ

終了後に本番ベンチが解放されていたのでWorkload 5くらいでまわした結果

1Result:   SUCCESS
2RawScore: 8285.3
3Fails:    0
4Score:    8285.3
5[OK] 結果を管理サーバに送信しました
6

ちょっとあがった。