研究室有志によるISUCON勉強会 ISUCON部 の資料です。
概要
今回は、scoreはあまり気にせず、インフラ側のチューニングの手数を増やすことが目的。以下のような事をやってみる。
- mysql
- /etc/my.cnf いじる
- indexを張る
- /etc/sysctl.conf でネットワーク設定いじる
- nginx.conf
- keepalive, スレッド数, ファイルディスクリプタのキャッシュ, TCP_CORKによるデータ書き出しタイミングの変更、など
スコア遷移
scores | |
---|---|
修正前 | 276.8, 332.4, 405.0 |
my.cnf 修正 | 343.5, 377.8, 391.4 (?) |
mysql で unix socket を使う | 368.3, 377.8, 409.2 (?) |
nginx.conf 修正 | 390.5, 454.6, 463.9 (?) |
nginx で unix socket を使う | 471.9, 479.3, 507.8 |
mysql に index 張る | 572.7, 585.1, 651.9 |
sysctl.conf を修正 | 653.3, 654.9, 665.0 |
mysql のチューニング
index を追加
今回は mysqldumpslow
で合計実行時間が一番長かった以下のクエリに着目する。
Count: 150 Time=0.74s (110s) Lock=0.00s (0s) Rows=201.9 (30288), root[root]@localhost
# 32.3s user time, 50ms system time, 28.73M rss, 85.54M vsz │ SELECT * FROM relations WHERE one = N OR another = N ORDER BY created_at DESC
relations
テーブルの index の情報を見てみる。
mysql> show index from relations;
+-----------+------------+--------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+-----------+------------+--------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| relations | 0 | PRIMARY | 1 | id | A | 483182 | NULL | NULL | | BTREE | | |
| relations | 0 | friendship | 1 | one | A | 10280 | NULL | NULL | | BTREE | | |
| relations | 0 | friendship | 2 | another | A | 483182 | NULL | NULL | | BTREE | | |
+-----------+------------+--------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
id が primary_key に, (one, another) が複合ユニークkeyになっている模様。
explain でクエリの情報を見てみると、keyを何も使ってくれていないことがわかる。
mysql> explain SELECT * FROM relations WHERE one = 10 OR another = 10 ORDER BY created_at DESC;
+----+-------------+-----------+------+---------------+------+---------+------+--------+-----------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-----------+------+---------------+------+---------+------+--------+-----------------------------+
| 1 | SIMPLE | relations | ALL | friendship | NULL | NULL | NULL | 483182 | Using where; Using filesort |
+----+-------------+-----------+------+---------------+------+---------+------+--------+-----------------------------+
ということで、この select クエリ時に働いてくれるような index を張る。今回は、 (one, created_at) と (another, created_at) の2つの複合indexを張る。
mysql> ALTER TABLE relations ADD INDEX one_created_at(one, created_at);
Query OK, 0 rows affected (4.92 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> ALTER TABLE relations ADD INDEX another_created_at(another, created_at);
Query OK, 0 rows affected (5.34 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> show index from relations;
+-----------+------------+--------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+-----------+------------+--------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| relations | 0 | PRIMARY | 1 | id | A | 483182 | NULL | NULL | | BTREE | | |
| relations | 0 | friendship | 1 | one | A | 10280 | NULL | NULL | | BTREE | | |
| relations | 0 | friendship | 2 | another | A | 483182 | NULL | NULL | | BTREE | | |
| relations | 1 | one_created_at | 1 | one | A | 10066 | NULL | NULL | | BTREE | | |
| relations | 1 | one_created_at | 2 | created_at | A | 483182 | NULL | NULL | | BTREE | | |
| relations | 1 | another_created_at | 1 | another | A | 10066 | NULL | NULL | | BTREE | | |
| relations | 1 | another_created_at | 2 | created_at | A | 483182 | NULL | NULL | | BTREE | | |
+-----------+------------+--------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
再び explain してみる。
mysql> explain SELECT * FROM relations WHERE one = 10 OR another = 10 ORDER BY created_at DESC;
+----+-------------+-----------+-------------+----------------------------------------------+-------------------------------+---------+------+------+------------------------------------------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-----------+-------------+----------------------------------------------+-------------------------------+---------+------+------+------------------------------------------------------------------------------+
| 1 | SIMPLE | relations | index_merge | friendship,one_created_at,another_created_at | friendship,another_created_at | 4,4 | NULL | 208 | Using sort_union(friendship,another_created_at); Using where; Using filesort |
+----+-------------+-----------+-------------+----------------------------------------------+-------------------------------+---------+------+------+------------------------------------------------------------------------------+
結局、 (one, created_at) の方のindexは使ってくれておらず、 (one, another) と (another, created_at) の2つのindexを使ってくれている。注目すべきは rows
のところ。 rows
はテーブルから読み出す行数の見積もりになっている。最初のexplainの結果と比較すると、483182 -> 208 に激減していることがわかる。
my.cnf の修正
参考:
innodb_buffer_pool_size=1G
innodb_flush_log_at_trx_commit=0
innodb_flush_method=O_DIRECT
innodb_support_xa=OFF
max_connections=10000
innodb_buffer_pool_size=1G
InnoDB のキャッシュのサイズを設定。マシンの物理メモリサイズの 80% 程度が目安。
innodb_flush_log_at_trx_commit=0
ログ書き込みのタイミングを制御することで、完璧なACIDではなくなるがパフォーマンスがよくなる。
値 | log buffer -> log file | log file -> disk |
---|---|---|
1 (default) | commit毎 | commit毎 |
2 | commit毎 | 約1秒毎 |
0 | 約1秒毎 | 約1秒毎 |
例えば 0 に設定した場合、diskへの保存が commit毎から約1秒毎になる。log fileへのアクセスが減るが、mysqldが死んだときに約1秒分のtransactionのデータを失う。
innodb_flush_method=O_DIRECT
OSでのファイルキャッシュを利用しないように設定。
LinuxはデフォルトでディスクI/Oをキャッシュしてくれている。しかし、RDBMSの場合はキャッシュを自前で管理するので、OSのキャッシュはオフにする。
参考: http://tech.nikkeibp.co.jp/it/article/Keyword/20070207/261244/
innodb_support_xa=OFF
logに書き込む順番を保証しなくする代わりにdisk flushを減らせる。
max_connections=10000
client からの同時接続数を増やす。
ちなみに、現在の max_connections
の値は show variables like 'max_connections';
で確認できる。
go -> mysql の接続を tcp socket から unix domain socket に変更
socket 場所の確認
$ mysql_config --socket
/var/run/mysqld/mysqld.sock
my.cnf の修正
socket=/var/run/mysqld/mysqld.sock
app.go の修正
app.go を修正。詳細は こちら
db, err = sql.Open("mysql", user+":"+password+"@unix(/var/run/mysqld/mysqld.sock)/"+dbname+"?loc=Local&parseTime=true")
go build app.go
を忘れずに。
nginx のチューニング
nginx.conf の修正
参考:
- Module ngx_http_core_module
- ISUCON4 予選でアプリケーションを変更せずに予選通過ラインを突破するの術 - Hateburo: kazeburo hatenablog
- ISUCON Cheat Sheet · GitHub
sendfile
onにすると、カーネル空間内でファイルの読み込みと送信が完了するようになる。
tcp_nopush
onにすると、レスポンスヘッダとファイルの内容をまとめて送るようになり、少ないパケット数で効率良く送れるようになる。sendfileがonのときのみ有効。
keepalive
一定条件を満たす複数リクエストを1 connectionにまとめる。
普通 HTTP webページ は1回のアクセスで複数のリクエストが発生する(画像, css, js, ...)ので、keepaliveを設定するとconnection確立のオーバーヘッドを減らせる。
tcp_nodelay
onにするとパケットサイズが小さくても遅延させずに送信する。これにより、1リクエストあたり最大0.2秒節約できる。
通常は、あるタイミングで送信したいデータサイズが小さい場合は最大0.2秒のあいだ次のデータを待ってまとめてパケットにする(Nagle's algorithm)という制御が行われる。
mime type の include
MIMEタイプと拡張子の関連付けをインポートする。
open_file_cache
一度開いたファイルディスクリプタを再利用できる。
nginx -> go の接続を tcp socket から unix domain socket に変更
参考:
Golang で書いた Web アプリケーションを UNIX ドメインソケットで公開 - at kaneshin
unix側の設定
ソケットファイルの置き場を作成する。 /etc/tmpfiles.d/isuxi.conf
を作成して設定を書く。
$ cat /etc/tmpfiles.d/isuxi.conf
d /var/run/isuxi 0755 root root -
その後、設定ファイルを登録 & 反映
$ sudo systemd-tmpfiles --create /etc/tmpfiles.d/isuxi.conf
$ sudo systemctl daemon-reload
app.go の修正
- unix domain socket を listen する
- unix domain socket を close する
部分を自前で実装します。参考:
Golang で書いた Web アプリケーションを UNIX ドメインソケットで公開 - at kaneshin
詳細は こちら
nginx.conf の修正
user root root;
upstream app {
server unix:/var/run/isuxi/go.sock;
}
詳細は こちら
sysctl.conf の修正
詳しいパラメタの説明は こちら を参考に。
net.ipv4.tcp_max_tw_buckets = 2000000
net.ipv4.ip_local_port_range = 10000 65000
net.core.somaxconn = 32768
net.core.netdev_max_backlog = 8192
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 10