三流エンジニアの落書き帳

さあ、今日も未知という扉を開けてみよう

CentOS7(systemctl環境)でMySQL RouterのMax oplen filesの上限を引き上げる

もともと年末年始休暇で暇な時間をつぶすために始めたブログですが、さすがに更新頻度が低いですね。

さて、先週MySQL8.0.19がGAとなりましたね:)

一番注目すべき新機能は何なんでしょうかね…

InnoDB Replica Setはちょっと触ってみたので今度記事にしてみます。

さて今回はMySQL8.0.19とは全く関係のない単発ネタです。

やりたかったこと

rpmでインストールしたmysqlrouterをmysqlrouterユーザで起動しなおかつMax open filesの上限を引き上げたい

環境はVM上のCentOS7.6です。

TL;DR

  • mysqlrouterは接続ごとにソケットファイルを生成する
  • デフォルトではmysqlrouterは一般ユーザ(mysqlrouter)で起動されるためMax open filesの上限が低い
  • 接続数が多い環境だとすぐにToo many open filesが発生してしまう
  • mysqlrouter.serviceの[Service]にLimitNOFILE=設定したい上限値と記述することで上限を引き上げることが可能

インストール

まずはmysqlrouterのインストールから

このサーバではすでにmysqlrouter(8.0.18)が存在していたので、正確にはUpgradeです。

[root@instance2:/usr/src]$ ll
合計 797728
drwxr-xr-x. 2 root root         6  411  2018 debug
drwxr-xr-x. 2 root root         6  411  2018 kernels
-rw-r--r--. 1 root root 765306880 1210 22:04 mysql-8.0.19-1.el7.x86_64.rpm-bundle.tar
-rw-r--r--. 1 root root  22840048  116 17:52 mysql-router-community-8.0.19-1.el7.x86_64.rpm
-rw-r--r--. 1 root root  28714372 1217 10:25 mysql-shell-8.0.19-1.el7.x86_64.rpm
drwxr-xr-x. 2 root root      4096  114 11:17 tmp
[root@instance2:/usr/src]$ 
[root@instance2:/usr/src]$ 
[root@instance2:/usr/src]$ rpm -Uvh mysql-router-community-8.0.19-1.el7.x86_64.rpm 
準備しています...              ################################# [100%]
更新中 / インストール中...
   1:mysql-router-community-8.0.19-1.e################################# [ 50%]
整理中 / 削除中...
   2:mysql-router-community-8.0

bootstrapしてconfなどを自動生成します。

[root@instance2:/usr/src]$ mysqlrouter --bootstrap=instance1:3306 --user=mysqlrouter
Please enter MySQL password for root: 
# Bootstrapping system MySQL Router instance...

- Creating account(s) (only those that are needed, if any)
- Verifying account (using it to run SQL queries that would be run by Router)
- Storing account in keyring
- Adjusting permissions of generated files
- Creating configuration /etc/mysqlrouter/mysqlrouter.conf

Existing configuration backed up to '/etc/mysqlrouter/mysqlrouter.conf.bak'

# MySQL Router configured for the InnoDB ReplicaSet 'example'

After this MySQL Router has been started with the generated configuration

    $ /etc/init.d/mysqlrouter restart
or
    $ systemctl start mysqlrouter
or
    $ mysqlrouter -c /etc/mysqlrouter/mysqlrouter.conf

the cluster 'example' can be reached by connecting to:

## MySQL Classic protocol

- Read/Write Connections: localhost:6446
- Read/Only Connections:  localhost:6447

## MySQL X protocol

- Read/Write Connections: localhost:64460
- Read/Only Connections:  localhost:64470

mysqlrouterユーザーで起動しているためデフォルトだとMax open filesが1024と低い状態で起動されてしまいます。

[root@instance2:/usr/src]$ systemctl start mysqlrouter

[root@instance2:/usr/src]$ systemctl status mysqlrouter
● mysqlrouter.service - MySQL Router
   Loaded: loaded (/usr/lib/systemd/system/mysqlrouter.service; disabled; vendor preset: disabled)
   Active: active (running) since 木 2020-01-16 17:54:23 JST; 3s ago
 Main PID: 14653 (main)
    Tasks: 9
   CGroup: /system.slice/mysqlrouter.service
           └─14653 /usr/bin/mysqlrouter -c /etc/mysqlrouter/mysqlrouter.conf

 116 17:54:23 instance2 systemd[1]: Started MySQL Router.
 116 17:54:24 instance2 mysqlrouter[14653]: logging facility initialized, switching logging to loggers specified in configuration

[root@instance2:/usr/src]$ cat /proc/$(pidof mysqlrouter)/limits
Limit                     Soft Limit           Hard Limit           Units     
Max cpu time              unlimited            unlimited            seconds   
Max file size             unlimited            unlimited            bytes     
Max data size             unlimited            unlimited            bytes     
Max stack size            8388608              unlimited            bytes     
Max core file size        0                    unlimited            bytes     
Max resident set          unlimited            unlimited            bytes     
Max processes             15027                15027                processes 
Max open files            1024                 4096                 files      
Max locked memory         65536                65536                bytes     
Max address space         unlimited            unlimited            bytes     
Max file locks            unlimited            unlimited            locks     
Max pending signals       15027                15027                signals   
Max msgqueue size         819200               819200               bytes     
Max nice priority         0                    0                    
Max realtime priority     0                    0                    
Max realtime timeout      unlimited            unlimited            us        

Max open filesの上限を引き上げる理由

そもそもなぜMax open filesの上限を引き上げたかったというと、mysqlrouterはそれ経由の接続が発生すると 接続ごとに通信のためのソケットファイルを用意するので、MySQLへの接続数が多い環境ではMax open filesが低いとmysqlrouter経由での接続ができなくなってしまうからです。

例をお見せします。

簡単にするためにMax open filesのLimitを16に変更します。

[root@instance2:/var/log/mysqlrouter]$ cat /proc/$(pidof mysqlrouter)/limits
Limit                     Soft Limit           Hard Limit           Units     
Max cpu time              unlimited            unlimited            seconds   
Max file size             unlimited            unlimited            bytes     
Max data size             unlimited            unlimited            bytes     
Max stack size            8388608              unlimited            bytes     
Max core file size        0                    unlimited            bytes     
Max resident set          unlimited            unlimited            bytes     
Max processes             15027                15027                processes 
Max open files            16                   16                   files     
Max locked memory         65536                65536                bytes     
Max address space         unlimited            unlimited            bytes     
Max file locks            unlimited            unlimited            locks     
Max pending signals       15027                15027                signals   
Max msgqueue size         819200               819200               bytes     
Max nice priority         0                    0                    
Max realtime priority     0                    0                    
Max realtime timeout      unlimited            unlimited            us        
[root@instance2:/var/log/mysqlrouter]$ 

mysqlrouter経由での接続が無い状態を確認します。

[root@instance2:~]$ ps -ef | grep mysqlroute[r]
mysqlro+ 13935     1  9 09:46 ?        00:09:08 /usr/bin/mysqlrouter -c /etc/mysqlrouter/mysqlrouter.conf

[root@instance2:~]$ lsof -i:6446
COMMAND   PID        USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
main    13935 mysqlrouter    6u  IPv4  47585      0t0  TCP *:mysql-proxy (LISTEN)

[root@instance2:~]$ ll /proc/$(pidof mysqlrouter)/fd
合計 0
lr-x------. 1 mysqlrouter mysqlrouter 64  117 09:46 0 -> /dev/null
lrwx------. 1 mysqlrouter mysqlrouter 64  117 09:46 1 -> socket:[47561]
lrwx------. 1 mysqlrouter mysqlrouter 64  117 09:46 2 -> socket:[47561]
l-wx------. 1 mysqlrouter mysqlrouter 64  117 09:46 3 -> /var/log/mysqlrouter/mysqlrouter.log
lrwx------. 1 mysqlrouter mysqlrouter 64  117 09:46 4 -> socket:[92764]
lrwx------. 1 mysqlrouter mysqlrouter 64  117 09:46 5 -> socket:[47580]
lrwx------. 1 mysqlrouter mysqlrouter 64  117 09:46 6 -> socket:[47585]
lrwx------. 1 mysqlrouter mysqlrouter 64  117 09:46 7 -> socket:[47610]
lrwx------. 1 mysqlrouter mysqlrouter 64  117 09:46 8 -> socket:[47611]

デフォルトで9つのソケットファイルが作られていることがわかります。(その内1つは/dev/null用、もう1つはログ出力用のようですね)

続いてmysqlrouter経由でMySQLサーバに接続した状態を確認します。

[root@instance2:~]$ mysql -uroot -h127.0.0.1 -P6446 &
[1] 15265
[root@instance2:~]$ Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 221388
Server version: 8.0.19 MySQL Community Server - GPL

Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

[1]+  停止                  mysql -uroot -h127.0.0.1 -P6446

[root@instance2:~]$ lsof -i:6446
COMMAND   PID        USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
main    13935 mysqlrouter    6u  IPv4  47585      0t0  TCP *:mysql-proxy (LISTEN)
main    13935 mysqlrouter    9u  IPv4  94305      0t0  TCP localhost:mysql-proxy->localhost:54458 (ESTABLISHED)
mysql   15265        root    3u  IPv4  94304      0t0  TCP localhost:54458->localhost:mysql-proxy (ESTABLISHED)

[root@instance2:~]$ ll /proc/$(pidof mysqlrouter)/fd
合計 0
lr-x------. 1 mysqlrouter mysqlrouter 64  117 09:46 0 -> /dev/null
lrwx------. 1 mysqlrouter mysqlrouter 64  117 09:46 1 -> socket:[47561]
lrwx------. 1 mysqlrouter mysqlrouter 64  117 09:46 10 -> socket:[94306]
lrwx------. 1 mysqlrouter mysqlrouter 64  117 09:46 2 -> socket:[47561]
l-wx------. 1 mysqlrouter mysqlrouter 64  117 09:46 3 -> /var/log/mysqlrouter/mysqlrouter.log
lrwx------. 1 mysqlrouter mysqlrouter 64  117 09:46 4 -> socket:[94779]
lrwx------. 1 mysqlrouter mysqlrouter 64  117 09:46 5 -> socket:[47580]
lrwx------. 1 mysqlrouter mysqlrouter 64  117 09:46 6 -> socket:[47585]
lrwx------. 1 mysqlrouter mysqlrouter 64  117 09:46 7 -> socket:[47610]
lrwx------. 1 mysqlrouter mysqlrouter 64  117 09:46 8 -> socket:[47611]
lrwx------. 1 mysqlrouter mysqlrouter 64  117 09:46 9 -> socket:[94305]

9,10という名前のソケットファイルが増えていることがわかると思います。

ということは1つの接続で2つのソケットファイルが作られるということでしょうか。

同じ要領で接続数を増やしてみます。

counter=1
while true
do
  echo "$counter 番目の接続"
  mysql -uroot -h127.0.0.1 -P6446 2>&1 >/dev/null &
  pid=$(echo "$!")
  wait $pid >/dev/null
  if [ $? -eq 1 ];then
    echo "接続に失敗しました。"
    break
  fi
  echo "接続に成功しました。"
  counter=$((counter + 1))
done

上記のようなスクリプトで何番目の接続で失敗するか確認してみます。

下記が実行結果です。(不要な箇所は割愛)

1 番目の接続
接続に成功しました。
2 番目の接続
接続に成功しました。
3 番目の接続
接続に成功しました。
4 番目の接続
接続に失敗しました。

4番目の接続で接続に失敗してしまったことが分かりました。

念のためmysqlrouterを経由しない接続は成功することを確認します。

[root@instance2:~]$ mysql -uroot -h instance1 -e "SELECT 'direct'" ; mysql -uroot -h 127.0.0.1 -P6446 -e "SELECT 'rooter'"
+--------+
| direct |
+--------+
| direct |
+--------+
ERROR 2003 (HY000): Can't connect to remote MySQL server for client connected to '0.0.0.0:6446'

この時の状態を確認してみます。

[root@instance2:~]$ lsof -i:6446
COMMAND   PID        USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
main    13935 mysqlrouter    6u  IPv4  47585      0t0  TCP *:mysql-proxy (LISTEN)
main    13935 mysqlrouter    9u  IPv4 109395      0t0  TCP localhost:mysql-proxy->localhost:39054 (ESTABLISHED)
main    13935 mysqlrouter   11u  IPv4 109405      0t0  TCP localhost:mysql-proxy->localhost:39058 (ESTABLISHED)
main    13935 mysqlrouter   13u  IPv4 109416      0t0  TCP localhost:mysql-proxy->localhost:39064 (ESTABLISHED)
mysql   15793        root    3u  IPv4 109394      0t0  TCP localhost:39054->localhost:mysql-proxy (ESTABLISHED)
mysql   15796        root    3u  IPv4 109404      0t0  TCP localhost:39058->localhost:mysql-proxy (ESTABLISHED)
mysql   15799        root    3u  IPv4 109415      0t0  TCP localhost:39064->localhost:mysql-proxy (ESTABLISHED)
[root@instance2:~]$ ll /proc/$(pidof mysqlrouter)/fd
合計 0
lr-x------. 1 mysqlrouter mysqlrouter 64  117 09:46 0 -> /dev/null
lrwx------. 1 mysqlrouter mysqlrouter 64  117 09:46 1 -> socket:[47561]
lrwx------. 1 mysqlrouter mysqlrouter 64  117 09:46 10 -> socket:[109396]
lrwx------. 1 mysqlrouter mysqlrouter 64  117 09:46 11 -> socket:[109405]
lrwx------. 1 mysqlrouter mysqlrouter 64  117 09:46 12 -> socket:[109406]
lrwx------. 1 mysqlrouter mysqlrouter 64  117 09:46 13 -> socket:[109416]
lrwx------. 1 mysqlrouter mysqlrouter 64  117 09:46 14 -> socket:[109417]
lrwx------. 1 mysqlrouter mysqlrouter 64  117 09:46 2 -> socket:[47561]
l-wx------. 1 mysqlrouter mysqlrouter 64  117 09:46 3 -> /var/log/mysqlrouter/mysqlrouter.log
lrwx------. 1 mysqlrouter mysqlrouter 64  117 09:46 4 -> socket:[113849]
lrwx------. 1 mysqlrouter mysqlrouter 64  117 09:46 5 -> socket:[47580]
lrwx------. 1 mysqlrouter mysqlrouter 64  117 09:46 6 -> socket:[47585]
lrwx------. 1 mysqlrouter mysqlrouter 64  117 09:46 7 -> socket:[47610]
lrwx------. 1 mysqlrouter mysqlrouter 64  117 09:46 8 -> socket:[47611]
lrwx------. 1 mysqlrouter mysqlrouter 64  117 09:46 9 -> socket:[109395]

ソケットファイルの最大数は15ですね。

Max open filesの上限が16なので、4本目の接続を作ろうとすると15+2=17で上限を超えてしまうということで接続に失敗してしまったと思われます。

mysqlrouterのログも確認してみましょう。

2020-01-17 12:25:46 routing ERROR [7fdfc2486700] [routing:example_rw] Failed accepting connection: Too many open files
2020-01-17 12:25:46 routing ERROR [7fdfc2486700] [routing:example_rw] Failed accepting connection: Too many open files
2020-01-17 12:25:46 routing ERROR [7fdfc2486700] [routing:example_rw] Failed accepting connection: Too many open files
2020-01-17 12:25:46 routing ERROR [7fdfc2486700] [routing:example_rw] Failed accepting connection: Too many open files
2020-01-17 12:25:46 routing WARNING [7fdfc017f700] [routing:example_rw] fd=15 Can't connect to remote MySQL server for client connected to '0.0.0.0:6446'
2020-01-17 12:25:46 routing WARNING [7fdfc017f700] [routing:example_rw] fd=15 Can't connect to remote MySQL server for client connected to '0.0.0.0:6446'

ログからも”Too many open files”が原因でエラーとなっていることが分かりました。(Can't connectはWARNINGなのか・・・)

最後にMax open filesの上限を引き上げてみて許容される接続数が増えることを確認してみます。

[root@instance2:/var/log/mysqlrouter]$ cat /proc/$(pidof mysqlrouter)/limits
Limit                     Soft Limit           Hard Limit           Units     
Max cpu time              unlimited            unlimited            seconds   
Max file size             unlimited            unlimited            bytes     
Max data size             unlimited            unlimited            bytes     
Max stack size            8388608              unlimited            bytes     
Max core file size        0                    unlimited            bytes     
Max resident set          unlimited            unlimited            bytes     
Max processes             15027                15027                processes 
Max open files            32                   32                   files     
Max locked memory         65536                65536                bytes     
Max address space         unlimited            unlimited            bytes     
Max file locks            unlimited            unlimited            locks     
Max pending signals       15027                15027                signals   
Max msgqueue size         819200               819200               bytes     
Max nice priority         0                    0                    
Max realtime priority     0                    0                    
Max realtime timeout      unlimited            unlimited            us        

先ほどと同じスクリプトを利用します。

下記が実行結果です。

1 番目の接続
接続に成功しました。
2 番目の接続
接続に成功しました。
3 番目の接続
接続に成功しました。
4 番目の接続
接続に成功しました。
5 番目の接続
接続に成功しました。
6 番目の接続
接続に成功しました。
7 番目の接続
接続に成功しました。
8 番目の接続
接続に成功しました。
9 番目の接続
接続に成功しました。
10 番目の接続
接続に成功しました。
11 番目の接続
接続に成功しました。
12 番目の接続
接続に失敗しました。

11番目の接続までは成功しているのでこの時のソケットファイルの数は9 + (2 * 11) = 31

この状態でもう1本接続を増やそうとすると32の上限に引っかかるため接続できない

ということでMax open filesの上限を引き上げてあげればmysqlrouter経由の接続が増えても問題ないことが分かりました。(もちろん別途でMySQLサーバ側およびMySQL Router側のmax_connectionsも調整する必要はあります)

対策

さて本題ですが、mysqlrouter.serviceにLimitNOFILE=65536と追記することでこの問題を回避できます。

[root@instance2:/usr/src]$ cp /usr/lib/systemd/system/mysqlrouter.service{,.org}

[root@instance2:/usr/src]$ vim /usr/lib/systemd/system/mysqlrouter.service

[root@instance2:/usr/src]$ diff /usr/lib/systemd/system/mysqlrouter.service.org /usr/lib/systemd/system/mysqlrouter.service
31a32
> LimitNOFILE=655356

あとは設定変更を反映させるためにsystemctl daemon-reloadを実行して再度mysqlrouterを起動させれば完了です。

[root@instance2:/usr/src]$ systemctl daemon-reload

[root@instance2:/usr/src]$ systemctl restart mysqlrouter

[root@instance2:/usr/src]$ cat /proc/$(pidof mysqlrouter)/limits
Limit                     Soft Limit           Hard Limit           Units     
Max cpu time              unlimited            unlimited            seconds   
Max file size             unlimited            unlimited            bytes     
Max data size             unlimited            unlimited            bytes     
Max stack size            8388608              unlimited            bytes     
Max core file size        0                    unlimited            bytes     
Max resident set          unlimited            unlimited            bytes     
Max processes             15027                15027                processes 
Max open files            655356               655356               files     
Max locked memory         65536                65536                bytes     
Max address space         unlimited            unlimited            bytes     
Max file locks            unlimited            unlimited            locks     
Max pending signals       15027                15027                signals   
Max msgqueue size         819200               819200               bytes     
Max nice priority         0                    0                    
Max realtime priority     0                    0                    
Max realtime timeout      unlimited            unlimited            us  

無事Max open filesを引き上げることが出来ました!

Max open filesの上限はどの程度が適切か

さて上記の対応でMax open filesの上限を引き上げることができましたが、655356はちょっと高すぎですね。 ここは環境によって適切な値を設定すべきですが、どのように考えるのが適切なのでしょうか。

あくまで僕個人の考えなのですが、mysqlrouter1台に接続する台数×2+(マージン)程度がいいのではないかと思います。

僕が担当しているサービスではmysqlrouter2台をL4の下にぶら下げてWEBサーバはL4に接続するという形をとっています。(InnoDB Cluster環境) Oracle公式ではmysqlrouterはWEBサーバにインストールするのがよいなどと書いているのですが、ちょっと理由があってそのような形はとっていません。

このような構成だとMySQLサーバ全体の接続数÷2がmysqlrouter経由で接続されることになります。そのためMySQLサーバ全体の接続数が1000だとすると、mysqlrouterのMax open filesの上限は1000+マージンで2000程度が妥当なのかなと思います。