2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

ISUCON5 予選 part3

Last updated at Posted at 2018-05-23

研究室有志による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 の修正

参考:

最終的な編集箇所はこちら

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
2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?