SEEDS Creator's Blog

読者です 読者をやめる 読者になる 読者になる

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] 結果を管理サーバに送信しました 

ちょっとあがった。