月別: 2013年10月

isucon#3 オンライン予選を総合4位で暫定通過しました

isucon#3 オンライン予選2日目に、
@memememomoと「進撃の超大型パティスリー兄弟」として参加してきました。
現在はまだ暫定ではありますが、2日目の1位(総合4位)で予選通過予定とのことです。

過去に isucon#1, isucon#2 と参加してきて、
思ったような結果を残せなかったので、今回は非常に嬉しいです。
チューニング内容的には普通のことしかしておらず、その辺りは他のひとの方が大分詳しい印象。
3度目の挑戦ならではの対策などについて書いてみました。

事前準備

事前に決めていた対応方針は以下のようなものです。
・使用する言語は、Perl(@memememomoの強い要請により)
・2人で並行作業は行わず(インフラ/プログラムで担当分けせず)すべてダブルチェック対応
ボトルネックへの対応に集中する
方針がブレるとisuconの熱気にやられておかしな方向に進んでしまうため、
対策の方針を決めるのは僕たちにとっては重要でした。

当日の朝は、Lingrのログを見たり、AWSのSecurityGroupを作ったりしてました。
また、一日目の上位スコアは公開されていたので、
予選通過ラインは1万点くらいだろうと見積もることもできました。

isuconスタート

git導入して初期準備は完了。
README.mdを読む。
workloadオプションは、ダブルチェックで間違った認識に着地。
なので一回も使ってません。

環境作ったり、システム確認したりで、
チューニングに着手したのは、11:00頃でした。
無駄に指差しダブルチェックしててかなり時間をロスしていたと思います。
初期スコアは、700点くらい。
このときすでに1位は2,000〜3,000点くらいだったと思います。

初期状態ではMySQLボトルネックになっていたので、
スロークエリを改善したり、インデックス貼ったり、
プロキシサーバをapacheからvarnishに変更したり。
この時点でスコアは2,500くらいでトップ10圏外でした。

また、最初からnginxが入っていて怪し過ぎたので
完全削除してインストールし直しました。
isuconでは予め用意されているものはワナにしか見えません。
静的ファイルをnginxに任せて、スコアは若干上がったと思います。

その後、地味にSQLを改善し続けて、
14時過ぎにスコアは3,800くらいとなり、
ここでようやくtop10に入りました。

その後もひたすら普通にSQLの改善を続けて、
15時頃にスコアは4,600くらいに。

そして、残り3時間

予選2日目は、1日目よりも他チームのスコアの伸びが早かったと思います。
この時点でトップはすでに2万を超えていましたし、
僕たちはトップ10から外れていました。
2日目のtop10のスコアは1万を超えてくるかもしれないという不安が過ります。
そろそろ、何らかのブレークスルーを出さなければ負ける時間帯だと思えてきました。
アプリ側でまだまだ改善すべき処理は残っていたのですが、
ワナに掛かってもまだ時間のあるこのタイミングでの
フロントでのキャッシュ対応に切り替えました。
想像以上にキャッシュ対策が上手くハマり、FAILを出しつつも24,000くらいのスコアで一気にトップに。
さらにキャッシュの調整を加え、30,000を超えました。
オンライン予選では、AMIの提出までが勝負なので、
実際にベンチを走らせることができる18:00までに、
もろもろのテストを行うことにしました。
AMIを作成し、他アカウントでインスタンス起動/ベンチ実行。
スコアも問題ないことを確認しました。
そして無事に予選終了。

まとめ

ボトルネック対応に集中したのが良かったと思います。
ダメなところもありましたが、指差しダブルチェックもいい感じでした。
初動が遅かったので本戦ではスピーディに対応したい。
SQLの改善では既存スキーマから大幅に変えることはしませんでしたが、
そのおかげで着実にスコアは上がったものの、
微増といった感じだったので、もっと思い切って変えてもよかったなと思っています。

最後になりましたが、isuconには今回で3回も参加させていただいており、
企画/運営をされておりますLINE/面白法人カヤック/DATAHOTELの皆様には
ほんとうに感謝しております。ありがとうございます!

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をソースコンパイル
ビルドインサーバーとして立ち上げました。これ便利ですね

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

my.cnfを設定

APIキー登録して初回ベンチが確認する。たしか800くらいでした。
初回ベンチですぐにDBボトルネックとわかったので、my.cnfを以下に変更。
(ええ、もちろん find / -name my.cnf しました。)

key_buffer = 512M
max_allowed_packet = 10M
table_open_cache = 10240
sort_buffer_size = 1M
read_buffer_size = 1M
read_rnd_buffer_size = 4M
myisam_sort_buffer_size = 1M
thread_cache_size = 128
query_cache_type= ON
query_cache_size= 16M
thread_concurrency = 8
innodb_flush_log_at_trx_commit = 0
innodb_file_per_table
innodb_additional_mem_pool_size=40M
innodb_log_buffer_size=32M
innodb_log_file_size=256M
innodb_buffer_pool_size=8000M
max_connections = 2048
max_connect_errors = 10000
tmp_table_size=1342177280
max_heap_table_size = 1342177280

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

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

ALTER TABLE `memos` ADD INDEX ( `created_at` ) ;
ALTER TABLE `memos` ADD INDEX ( `user` ) ;

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

フロントエンドはVarnish

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

結局フロントエンド側での大規模なキャッシュは僕の力では厳しそうだったので
静的ファイルだけvarnishでキャッシュ。設定の主要部分だけだけど以下のような感じ。

backend web1 {
.host = "127.0.0.1";
.port = "5000";
}
sub vcl_recv {
set req.backend = web1;
if (req.url ~ "\.(jpg|png|gif|css|js|ico)$") {
return (lookup);
}
return (pass);
}
sub vcl_fetch {
set beresp.ttl = 86000s;
return (deliver);
}

スキーマとかSQLの改修

ここらへんで2500くらいだったかな。
この時でもDBボトルネックはまだまだ明らかでしたので
ここでメンバーの@gom_ohがDBに以下の改修を行いました。

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

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

public_idというmemosテーブルでパブリックに公開しているだけのmemo_id一覧を入れるテーブルを作成しました。
それから、ベンチ実行後のスクリプトで現在公開中である記事のIDをインサート

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

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

#公開ページの総数
SELECT count(*) FROM memos WHERE is_private=0SELECT count(memo_id) FROM public_id
#TOPページ
SELECT * FROM memos WHERE is_private=0 ORDER BY created_at DESC, id DESC LIMIT 100SELECT m.id, u.username, m.created_at, m.content
FROM memos m
JOIN (
SELECT p.memo_id
FROM public_id p
ORDER BY p.memo_id DESC
LIMIT 100
) t
ON m.id = t.memo_id
JOIN users u
ON u.id = m.user;
#recentページ
SELECT * FROM memos WHERE is_private=0 ORDER BY created_at DESC, id DESC LIMIT 100 OFFSET %d", $page * 100

SELECT m.id, u.username, m.created_at, m.content
        FROM memos m
        JOIN (
          SELECT p.memo_id
          FROM public_id p
          ORDER BY p.memo_id DESC
          LIMIT 100 OFFSET %d
        ) t
        ON m.id = t.memo_id
        JOIN users u
        ON u.id = m.user',
        $page * 100);

これでスコアが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くらいでまわした結果

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

ちょっとあがった。

© SEEDS Co.,Ltd.