SEEDS Creator's Blog

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

#isucon 4 予選に参加しました(スコア 37513)

@memememomo (uchiko) と onihsiと@cs_sonar(僕)で参加しました。 チーム名は「京都スイーツ」です。 結果としては本戦出場はできそうにないスコアで残念でした・・・。 (2014/10/06 追記。失格になってました。)

以下備忘録です。

インスタンス立ち上げ

AMI-IDをゲットしてインスタンスの立ち上げ。 全員にそれぞれ検証環境を用意する事を前日話していたのでインスタンス台数は4で一気に立ち上げ。 一つを本番用、他3つをメンバー用。

perlに切り替え

superviserd stopしてもrubyのアプリが立ち上がったままだったので なんでやと思いながらも躊躇なくkill -killした。 無事perlで立ち上がってベンチ実行してスタートダッシュ成功。 一瞬でも1位がとれて、「優勝したあ!isucon優勝!」とあほみたいに騒ぐ。

ベンチを回すコマンド作成

ベンチマークツールの実行コマンド(特にapiキーあり)がいろいろ面倒なので

/bin/ben (ベンチテスト用)
/bin/benben (ベンチ+スコア送信用)

という名のシェルスクリプトを作る。 benben でスコア報告ベンチが走るのでちょっと楽。 ただ、これにより、「べんべんいく?」「とりあえずべんしよう」とかはたから見たら異様な会話に。

git(bitbucket)を使う

gitはbitbucketのプライベートリポジトリを使った。 このあたりの設定をmemememomoがやってくれた。

vim

memememomoが秘伝のvim設定を投入。

ログ解析

アクセスのパターンを確認できればいいかと簡易のアクセス解析ツールを使った。 前日にぐぐって見つけたvisitorsというもの。

cd /usr/local/src
wget http://www.hping.org/visitors/visitors-0.7.tar.gz
tar zxvf visitors-0.7.tar.gz
make
./visitors -A -o text /var/log/nginx/access_log

* Different pages requested: 4
1)    /: 4160
2)    /login: 2284
3)    /mypage: 440
4)    /report: 6

え、urlこんだけ!?

nginxの設定

nginxの設定が必要最低限だったので修正。 ついでに静的ファイルはnginxから返すようにやっておいた。

worker_processes 8;

events {
  worker_connections  10000;
}

http {
  sendfile        on;
  include       mime.types;

  upstream app {
    server 127.0.0.1:8080;
    keepalive 1000000;
  }

  server {
    location /images {
      alias /home/isucon/webapp/public/images;
    }
    location /stylesheets {
      alias /home/isucon/webapp/public/stylesheets;
    }
    location = /favicon.ico {
      log_not_found off;
      alias /home/isucon/webapp/public/favicon.ico;
    }
    location / {
      proxy_pass http://app;
    }
  }
}

mysqlの設定

mysqlも必要最低限だったので設定

[mysqld]
datadir=/var/lib/mysql
socket=/var/lib/mysql/mysql.sock
symbolic-links=0
max_allowed_packet=300M
skip-name-resolve
innodb_flush_log_at_trx_commit = 0
innodb_additional_mem_pool_size=40M
innodb_log_buffer_size=32M
innodb_log_file_size=256M
innodb_buffer_pool_size=3600M
max_connections = 2048
max_connect_errors = 10000
tmp_table_size=134217728
max_heap_table_size = 134217728
key_buffer_size = 512M
table_cache=2048
sort_buffer_size = 1M
read_buffer_size = 1M
read_rnd_buffer_size = 4M
myisam_sort_buffer_size = 1M
thread_cache_size = 128
thread_concurrency = 8

[mysqld_safe]
log-error=/var/log/mysqld.log
pid-file=/var/run/mysqld/mysqld.pid

ネットワークやサーバー制限まわりの設定

このあたりでつまずくのは時間が勿体ないので、あらかじめ上限をあげておきました。 (ですので、ローカルポート枯渇などではひっかかってないです)

iptablesをOFF ipconntracとか出て時間をくいたくないので念のため。

/etc/init.d/iptables stop
chkconfig iptables off
chkconfig iptables --list
iptables -F

sysctl.conf の設定 この設定はvarnishの公式に「varnish使うならこんな設定まじおすすめ」みたいに紹介されてたもの。

net.ipv4.ip_local_port_range = 10240 65000
net.core.rmem_max=16777216
net.core.wmem_max=16777216
net.ipv4.tcp_rmem=4096 87380 16777216
net.ipv4.tcp_wmem=4096 65536 16777216
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_fin_timeout = 3
net.core.netdev_max_backlog = 30000
net.ipv4.tcp_no_metrics_save=1
net.core.somaxconn = 262144
net.ipv4.tcp_syncookies = 0
net.ipv4.tcp_max_orphans = 262144
net.ipv4.tcp_max_syn_backlog = 262144
net.ipv4.tcp_synack_retries = 2
net.ipv4.tcp_syn_retries = 2
net.ipv4.tcp_max_tw_buckets = 56384

ファイルディスクリプタでも時間をくいたくないのでlimits.confを設定。

*               hard    nofile             65535
*               soft    nofile             65535

ここまでやってみてスコアは2500とあまりかわらず。 MySQLボトルネックなのはわかっていたのでボトルネックがうつった時に効果を発揮してくれるでしょう

インデックスを張る

onihsiがインデックスを張ってくれました

ALTER TABLE `isu4_qualifier`.`login_log` 
ADD INDEX `ip` (`ip` ASC),
ADD INDEX `login` (`login` ASC),
ADD INDEX `user_id-succeeded` (`user_id` ASC, `succeeded` ASC),
ADD INDEX `ip-succeeded` (`ip` ASC, `succeeded` ASC),
ADD INDEX `succeede-user_id` (`succeeded` ASC, `user_id` ASC);

これと上記のインフラ周りの修正でスコアは20000くらいになった。

banのユーザをredisでカウントするようにした

ipアドレス と ユーザー名を各々キーにしてログイン失敗で

    $self->redis->incr($ip);
    $self->redis->incr($user->{id});

みたくインクリメント ログイン成功で

    $self->redis->set($user->{id}, 0);
    $self->redis->set($ip, 0);

ゼロに戻す

これでip_bunnedの確認とかが

sub ip_banned {
  my ($self, $ip) = @_;
  return undef unless $ip;
  my $count = $self->redis->get($ip) || 0;
  return $self->config->{ip_ban_threshold} <= $count;
};

となる でも/reportsの修正は怖かったのでlogin_logへのINSERTはそのまま。 こんな修正をmemememomoがばばーとやってくれた。

でも24000。あんまりのびなくてあるぇえええ?ってなる。

セッションの管理をメモリで行うようにした

@memememomoがベンチ回すと「max Open files」的なエラーが出るというので見てみると /tmp内にあるセッションファイルがものごっつい量になっていた。 これ以上ファイルが書き込めないという状態になっていて1ディレクトリの上限ファイル数でも叩いてたのかな。(あんまり調べてない) こんだけファイルができるくらいだからセッション書き込みにおけるディスクIOも問題となっているに違いない事に気づいて memememomoにセッションをメモリで管理するように変更してもらった。

Plack::Session::Store::Cache
Cache::FastMmap
CHI

redis使うよりもこっちの修正のほうが効いて たぶんここらへんで32000 いや、まぁここがボトルネックで解消した結果redis修正が生きた、という可能性もありますが。

平文のパスワード

このときベンチではもうほとんどがアプリの負荷だったのでどこかアプリで重いとこないかを探した。 プロファイルしようかともmemememomoが悩んでたんですが calculate_password_hashって明らかに重いだろ、、、という事でここをなんとかする事に。

元のinitialのsqlデータやtsvデータには元passwordが平文で書かれてたのでこれを入れちゃえば calculate_password_hash使わなくてよくなる。 usersテーブルをコピーしてusers_pwを作成。 ここのpassword_hashに平文のパスワードを入れて、平文のパスワードで認証するようにした。 これでなんか重そうな雰囲気のcalculate_password_hashを呼ばなくてよくなった。

終わり

ベンチ中topをなにげなく眺めてると、memememomoの秘伝のvimがCPU20~30%も消費してる事を発見。 vimを落としたベンチで Score 37513 の最高スコアを送信してend

無駄だったこと1

/のエラー文言部分だけを別URLにしてindex.txからSSIで読むようにした。 これにより/をnginxでキャッシュできたんだけど、速くなるわけなかった。 ssi処理のぶん遅くなっただけで意味がなくて元に戻した。 なんでこれで早くなると思ったのか思い返したら謎。

無駄だったこと2

ログイン失敗数等を別テーブルで持たせてSQLの負荷軽減をした。 Redisでカウントする事にしたのでこの修正も無駄になった。

やりたかったこと1

すべてのデータをredisに入れてmysqlと決別したかった。 でも過去の経験から普段やったことない作業をやると泥沼化が見えてるのでやらない事にした。 具体的にはreportsの部分に手を入れるのが怖かったのと、redis力が不安だった

反省会

反省会の焼肉屋でTOPページのindexをエラー文言別に静的に用意しておいてnginxがCookieで振り分けると早くなりそうという案が出た。 他の人のブログでもそんな改修があって、これについては気づきが足りなかったなぁと悔しい思いに。 実際この作業をやっても43000点くらいまでしか伸びなかったのですが、 これをやってたら本戦にもしかしたら出れたかもと思うとうぎゃーとなりました。

最後に

最後になりましたが、isuconには今回でもう4回も参加させていただいており、 企画/運営/協賛されておりますLINE/cookpad/DATAHOTEL/Amazonの皆様にはほんとうに感謝しております。 ありがとうございます!

本戦出場者が決定しました!

http://isucon.net/archives/40576269.html

京都スイーツはギリギリだめでした。。。と思って読み進めると、失格してました。

・「京都スイーツ」チームは、 /mypage にログインユーザ名が表示されていないため、表示崩れとみなし、失格といたしました。

調べてみると SELECT * を書き換える時に必要なカラムが漏れてた模様。 どちらにしてもスコア足りてなかったので結果は同じですが失格は悲しい。 一応チェックされるほどの上位にいけたということで許して下さい、、、と会社に言い訳。

ここまでチェックするって運営さんはすごく大変だったろうな、、、と改めて感謝の気持ちでいっぱいです。 今年も楽しい問題、ありがとうございました。