■目次
Apache チューニングMySQL チューニングWebアプリケーション チューニング動作確認参考
■Apache チューニング
※preforkでのチューニング例 開発サーバや会員しか使わないサイトなら、設定する意味は少ない 短期間にApacheのプロセス数が大きく波打つようなサイトならチューニングが有効 (プロセスを作成するときの負荷を無くせるので) 基本的にはpreforkの設定はデフォルトのままで大丈夫 ※Apacheに標準で付属している Apache Bench で、負荷テストができる ※Apacheの設定ファイル httpd.conf を編集してチューニングする ※Apache Bench でのテストを飛ばして、はじめからメモリ量をもとに考えても良さそう Apache Benchを使った負荷テストのやり方 http://blog.verygoodtown.com/2012/05/apache-bench-ab/ 同時接続数(MaxClients)をいくつに設定すべきか? http://canalize.jp/archives/006925.php Apache Benchでサクッと性能テスト http://qiita.com/flexfirm/items/ac5a2f53cfa933a37192 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 以下、Apache Bench の実行例 $ ab -n 100 -c 10 http://refirio.net/ http://refirio.net/ に対し、10人からのリクエストを100回実行 This is ApacheBench, Version 2.3 <$Revision: 655654 $> Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ Licensed to The Apache Software Foundation, http://www.apache.org/ Benchmarking refirio.net (be patient).....done Server Software: Apache Server Hostname: refirio.net Server Port: 80 Document Path: / Document Length: 399 bytes Concurrency Level: 10 Time taken for tests: 0.028 seconds Complete requests: 100 … 成功したリクエスト(すべて成功) Failed requests: 0 … 失敗したリクエスト(なし) Write errors: 0 Total transferred: 65300 bytes HTML transferred: 39900 bytes Requests per second: 3518.15 [#/sec] (mean) … 1秒間に処理したリクエスト数(数字が大きいほど多く処理できた) Time per request: 2.842 [ms] (mean) … 1リクエストあたりの処理時間 Time per request: 0.284 [ms] (mean, across all concurrent requests) Transfer rate: 2243.51 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 0 0.5 0 2 Processing: 0 2 0.8 2 5 Waiting: 0 2 0.8 2 4 Total: 1 3 1.0 2 6 WARNING: The median and mean for the total time are not within a normal deviation These results are probably not that reliable. Percentage of the requests served within a certain time (ms) 50% 2 66% 3 75% 3 80% 3 90% 5 95% 5 98% 6 99% 6 100% 6 (longest request) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - $ ab -n 3000 -c 35 http://refirio.net/ http://refirio.net/ に対し、35人からのリクエストを3000回実行 Concurrency Level: 35 Time taken for tests: 0.749 seconds Complete requests: 3000 Failed requests: 0 Write errors: 0 Total transferred: 1962265 bytes HTML transferred: 1198995 bytes Requests per second: 4006.01 [#/sec] (mean) Time per request: 8.737 [ms] (mean) Time per request: 0.250 [ms] (mean, across all concurrent requests) Transfer rate: 2558.87 [Kbytes/sec] received - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - $ ab -n 3000 -c 35 http://refirio.net/blog/ http://refirio.net/blog/ に対し、35人からのリクエストを3000回実行 Concurrency Level: 35 Time taken for tests: 68.335 seconds Complete requests: 3000 Failed requests: 0 Write errors: 0 Total transferred: 20271000 bytes HTML transferred: 19548000 bytes Requests per second: 43.90 [#/sec] (mean) Time per request: 797.242 [ms] (mean) Time per request: 22.778 [ms] (mean, across all concurrent requests) Transfer rate: 289.69 [Kbytes/sec] received - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 以下、負荷テスト $ ab -n 1000 -c 100 http://refirio.net/blog/ Concurrency Level: 100 Time taken for tests: 26.575 seconds Complete requests: 1000 Failed requests: 0 Write errors: 0 Total transferred: 6757000 bytes HTML transferred: 6516000 bytes Requests per second: 37.63 [#/sec] (mean) Time per request: 2657.485 [ms] (mean) Time per request: 26.575 [ms] (mean, across all concurrent requests) Transfer rate: 248.30 [Kbytes/sec] received $ ab -n 2000 -c 200 http://refirio.net/blog/ … Load Average が210くらいになったが、なんとかさばけている模様 Concurrency Level: 200 Time taken for tests: 1532.602 seconds Complete requests: 2000 Failed requests: 0 Write errors: 0 Total transferred: 13514000 bytes HTML transferred: 13032000 bytes Requests per second: 1.30 [#/sec] (mean) Time per request: 153260.231 [ms] (mean) Time per request: 766.301 [ms] (mean, across all concurrent requests) Transfer rate: 8.61 [Kbytes/sec] received $ ab -n 3000 -c 300 http://refirio.net/blog/ … 「apr_poll: The timeout specified has expired」となった(このとき、Load Average が270くらいになった) サーバに負荷がかかりすぎたようで、しばらくSSHもHTTPも繋がらなくなった 少なくともこのページでは300人からのアクセスはさばけない Requests per second: 3518.15 [#/sec] (mean) … 1秒間に処理できるリクエスト数(Requests per second)がどんどん小さくなり、 1リクエストあたりの処理時間(Time per request)がどんどん多くなっている これは、リクエストが多くなると処理待ちが多く発生するようになるため。 Apache Bench でのアクセスは、完全な同時アクセスではない 最初はアクセスが少なく、だんだん増えていき、一定数が続き、徐々に減っていく。という実際のアクセスを再現する つまり、上の結果から「秒間200アクセスをさばくサーバ」と言えるわけでは無い 「200人の同時アクセスに耐えられるようにしてほしい」 の要件があった場合、 「秒間200アクセスをさばく」という意味なのか「200人が一定時間をかけて色々なページを見ている状況」 なのか確認しておく必要がある 普通のサーバなら、同時にさばけるのはせいぜい秒間数十くらい? 秒間100などという数字が出てきた時点で、ロードバランサでの複数台構成を考える? ネットワークも考慮できる分、Apache Bench ツールよりLoadImpactの方が便利 だた、他のサーバからabツールを使えばネットワークを考慮に入れられる Abを利用した負荷テスト時にTimeoutになる場合の対処法 http://qiita.com/y-mori/items/83547f4dd604e8e5c509 「TCP: time wait bucket table overflow」や「nf_conntrack: table full, dropping packet.」のエラーメッセージは無かった。 トップページへのリクエストなら大丈夫なので、負荷が高いページヘのリクエストだから問題があった? - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 今度は、普通のHTMLページに対してリクエストしてみる $ ab -n 3000 -c 300 http://refirio.net/ Concurrency Level: 300 Time taken for tests: 1.470 seconds Complete requests: 3000 Failed requests: 0 Write errors: 0 Total transferred: 1962265 bytes HTML transferred: 1198995 bytes Requests per second: 2040.96 [#/sec] (mean) Time per request: 146.990 [ms] (mean) Time per request: 0.490 [ms] (mean, across all concurrent requests) Transfer rate: 1303.68 [Kbytes/sec] received $ ab -n 5000 -c 500 http://refirio.net/ Concurrency Level: 500 Time taken for tests: 6.414 seconds Complete requests: 5000 Failed requests: 0 Write errors: 0 Total transferred: 3273489 bytes HTML transferred: 2000187 bytes Requests per second: 779.53 [#/sec] (mean) Time per request: 641.412 [ms] (mean) Time per request: 1.283 [ms] (mean, across all concurrent requests) Transfer rate: 498.40 [Kbytes/sec] received $ ab -n 6000 -c 600 http://refirio.net/ … 完了できない Benchmarking refirio.net (be patient) Completed 600 requests Completed 1200 requests Completed 1800 requests Completed 2400 requests Completed 3000 requests Completed 3600 requests Completed 4200 requests Completed 4800 requests Completed 5400 requests apr_socket_recv: Connection reset by peer (104) Total of 5892 requests completed # vi /var/log/messages … エラーメッセージを確認
kernel: possible SYN flooding on port 80. Sending cookies.
CentOS6などでdmesgにpossible SYN flooding on port(SYN flooding警告)が出た場合の対策方法を考えてみるテスト http://triplesky.blogspot.jp/2014/07/centos6dmesgpossible-syn-flooding-on.html 受け付けられないほどの大量アクセスが来たというログが残っている。 このサーバの接続可能数の飽和点は500程度。余裕を持たせて400程度 また先にテストしたように、PHPプログラムのページの場合は210程度が限界 ただしそのまま210に設定すると、他のサービスが稼働できないくらいの接続を受け入れてしまう可能性がある また、swapも多発してサーバが非常に重くなる可能性がある 実際、abツールで計測している最中はサーバが非常に重くなった。 この状態ではサーバが落ちていなかったとしても「210をさばける」と言うわけにはいかない - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 今度は、メモリ量からMaxClientsの目安を考えてみる 同時接続数(MaxClients)をいくつに設定すべきか? http://canalize.jp/archives/006925.php MaxClients=使用可能なメモリ量/Apacheの1プロセスが使用するメモリ量 という考え方で計算できる refirio.netサーバではメモリは1GBで、実際に使っているのは500MBくらい。 psで見ると、httpdの1プロセスが2%くらいを使っているので、1プロセスあたり10MBくらい使っている よって、MaxClientsは90〜100くらいが良さそう ↑の数値はRではなくSのプロセスだし、データベースなども考慮する必要がある。 常時立ち上げるなら80くらいでもいいかも? (ただし例えばMySQLを使うなら、MySQLのmax_connectionsとの兼ね合いも考慮する) Sのプロセスでメモリの消費量を計算すると、実際はもっと消費する可能性があるので注意 ある程度サーバに負荷をかけてRのプロセスを表示させ、それをもとに計算した方がいい #なお、某大学の入試サイトサーバではメモリは16GBで、実際に使っているのは1.4GBくらい。 #psで見ると、httpdの1プロセスが0.1%に満たないくらいを使っているので、1プロセスあたり1.4MBくらい使っている #refirio.netとは大きく差があるが、1プロセスあたりの消費メモリは、実行されているプログラムによっても変わる #また、長い間プロセスが使われていないとメモリ量はどんどん小さくなるらしい #このサーバはスペックが高いので、他のプロセスが使われないので、最小状態になっている? #Apacheの使用メモリが搭載メモリ数を超えてしまうと、スワップが発生してサーバが重くなる #逆に少なすぎると503エラーになる # vi /etc/httpd/conf/httpd.conf … Apacheの設定ファイルを編集
<IfModule prefork.c> StartServers 8 … 起動時に生成される子サーバプロセスの数。常に動かしておきたい数に設定しておく MinSpareServers 5 … アイドル状態にいる子サーバプロセスの最小(希望)個数 MaxSpareServers 20 … アイドル状態にいる子サーバプロセスの最大(希望)個数 ServerLimit 256 … MaxClientsに指定可能な値の上限 MaxClients 256 … 起動される子サーバプロセスの最大数。つまり応答できる同時リクエスト数 MaxRequestsPerChild 4000 … 個々の子サーバプロセスが扱うことのできるリクエストの総数。0に設定すると無制限 </IfModule>
一旦以下のように変更する
<IfModule prefork.c> StartServers 80 MinSpareServers 80 MaxSpareServers 80 ServerLimit 80 MaxClients 80 MaxRequestsPerChild 0 </IfModule>
これで様子を見つつ、エラーログに以下のメッセージが記録されたら、400までの値で少しづつ上げて調整する [error] server reached MaxClients setting, consider raising the MaxClients setting 400まで上げてもエラーログがでるようなら、もうその1台のサーバでは対応できない(Apache的に)ので、 ロードバランサで複数台構成を検討する必要がある また、これはあくまでもサーバスペックのみでの話。実際には転送量制限や帯域制限もあるので注意。 Apacheのチューニングメモ http://qiita.com/nownabe/items/1111cc32da9fe63289f0 中規模サイトのApacheチューニング http://qiita.com/kou/items/acb3dcf1dcb428d7a3ec 他にも以下を設定しておくと良さそう Timeout ... 60秒でも長い?10秒くらいでいいかも(ただしAWSの場合は120に設定する) KeepAlive ... OFFでよさそう(AWSの場合は有効に設定する) Amazon ELBをうまくつかうには、KeepAliveを有効にしよう。Timeoutは60秒よりだいぶ長くしよう。その背景。 https://debiancdn.wordpress.com/2012/06/14/amazon-elb%E3%82%92%E3%81%86%E3%81%BE%E3%81%8F%E3%81%A4%E... ※今はサーバスペックが向上しているので、回線(帯域)の方がネックになることがある 帯域をもとに同時にさばける数を計算し、それをもとに判断するという手段もある ※サーバ契約前・構築前ならどのように判断する?
■MySQL チューニング
※マシンの搭載メモリをもとに設定を調整する ※MySQLの設定ファイル my.cnf を編集してチューニングする ※MySQLのメモリ設定には、大きく分けて以下の2つがある グローバルバッファ ... mysqld全体で確保するメモリ スレッドバッファ ... コネクションごとに確保するメモリ MySQL 初めてのチューニング http://www.slideshare.net/Craftworks/my-sql-6113813 MySQLのmy.cnfファイルサンプル http://time-complexity.blogspot.jp/2013/10/mysqlmycnf.html MySQLの設定ファイル my.cnf をgithubにて公開しました & チューニングポイントの紹介 http://blog.nomadscafe.jp/2012/10/mysql-mycnf-github.html # vi /etc/my.cnf
[mysqld] datadir=/var/lib/mysql socket=/var/lib/mysql/mysql.sock user=mysql # Disabling symbolic-links is recommended to prevent assorted security risks symbolic-links=0 character-set-server = utf8 [mysqld_safe] log-error=/var/log/mysqld.log pid-file=/var/run/mysqld/mysqld.pid
以下のように設定を調整(WebサーバとDBサーバは共通で、搭載メモリは1Gとする)
[mysqld] datadir=/var/lib/mysql socket=/var/lib/mysql/mysql.sock user=mysql # Disabling symbolic-links is recommended to prevent assorted security risks symbolic-links=0 character-set-server = utf8 max_connections = 65 #最大同時接続数 #サーバに合わせて設定する必要がある #SHOW GLOBAL VARIABLES like '%connections%'; で Max_used_connections を参考にするといい(MySQLを起動してからの最大同時接続数) #負荷テストをすると必然的に上がってしまう? #グローバルバッファ innodb_buffer_pool_size = 512M #InnoDBのインデックスやレコードをキャッシュする領域。DB専用サーバなら全体の7〜8割程度、そうでないなら5割程度を割り当てる innodb_additional_mem_pool_size = 8M #InnoDBのテーブル定義情報を格納する領域。それほど気にする必要は無い。エラーログに警告が出たら増やせばいい innodb_log_buffer_size = 8M #InnoDBのトランザクションを管理する領域。大抵は8M、多くても64Mで十分。他のパラメータにメモリを渡すほうが得策 key_buffer_size = 8M #MyISAMで索引検索をする際にインデックス情報を格納する領域。MyISAMを使うなら256M程度にする。DB専用サーバなら全体の25%程度割り当てても問題ない query_cache_size = 128M #SELECT文の実行結果をキャッシュする領域。100〜200Mくらいが推奨されているようだが、128M以上には設定しない方がいい。INSERT, UPDATE, DELETEが頻繁に起こるサーバーでは大きく設定する必要はない #スレッドバッファ myisam_sort_buffer_size = 1M #MyISAMでインデックスのソートに使われる領域。それほど大きくする必要は無い。MyISAMを使うなら8M程度でいい sort_buffer_size = 2M #ソートの際に利用される領域。ORDER BY や GROUP BY を多用するなら多めにするといい。スレッドバッファなので2M以上には設定しない方がいい read_rnd_buffer_size = 1M #ソート後にレコードを読み込む際に利用される領域。ディスクI/Oが減るので、ORDER BY の性能向上が期待できる。ORDER BY を多用するなら多めにするといい。1〜2Mくらいが妥当 join_buffer_size = 128K #インデックスを使用しないテーブルの結合に使われる領域。インデックスを用いないテーブル結合は避けるべきなので、大きくする必要は無い read_buffer_size = 128K #テーブルスキャンの際に利用される領域。インデックスを用いないクエリは避けるべきなので、大きくする必要は無い #メモリ以外の設定 innodb_log_file_size = 128M #InnoDBの更新ログを記録するディスク上のファイル。innodb_buffer_pool_sizeを大きくしたら合わせて大きくする。1M以上に設定する。32bitマシンの場合は4G以下に設定する #innodb_log_file_size * innodb_log_files_in_group(デフォルト2) の値が innodb_buffer_pool_size を超えてはいけない table_open_cache = 1024 #開いたテーブルのファイルポインタを格納する領域。「同時接続数×テーブル数」が最低限必要。1024〜2048が一般的。MyISAMでは1テーブルにつき2つ消費 thread_cache_size = 20 #スレッドをキャッシュにいくつ保存しておくのか決める。実際の稼働状況を把握しないと何とも言えない。max_connectionsの1/3ぐらいか wait_timeout = 10 #接続したクライアントが何もせずにいるとき、接続を切断する時間。単位は秒でDefaultは28800秒(8時間)で、かなり大きい値 #Webでの接続の場合、Webサーバのタイムアウト時間と同じでいい? [mysqld_safe] log-error=/var/log/mysqld.log pid-file=/var/run/mysqld/mysqld.pid
設定内容の妥当性を確認する stack_sizeはコネクションごとに確保される領域。バイナリパッケージの種類によっては変更できないものもあり、256K固定と考えて良さそう 64bitマシンの場合、以下の式が搭載メモリの8〜9割程度になるように(DB専用サーバでないなら7割程度にする) 32bitマシンの場合、以下の式が2GBに近いもしくは上回っていると危険(32bitマシンではソフトに割り当てられるメモリは2Gまでだから?) innodb_buffer_pool_size + key_buffer_size + (max_connections * (sort_buffer_size + read_buffer_size + read_rnd_buffer_size)) + (max_connections * stack_size) 512M 8M 65 2M 128K 1M 65 256K 520M + ( 65 * 3.2M ) + 65 256K 520M + ( 65 * 3.2M ) + 17M 520M + 210M + 17M 747M 上の設定だと、最大747MのメモリがMySQLに割り当てられることになる スペック(メモリ)が変われば innodb_buffer_pool_size と innodb_log_file_size の値を変更し、それに合わせて max_connections も変更する 他の値も一通り見直す メモリ1Gのサーバなら、以下くらいに抑えてもいいかも?実験中 innodb_buffer_pool_size + key_buffer_size + (max_connections * (sort_buffer_size + read_buffer_size + read_rnd_buffer_size)) + (max_connections * stack_size) 300M 8M 30 2M 128K 1M 30 256K 310M + ( 30 * 3.2M ) + 30 256K 310M + ( 30 * 3.2M ) + 8M 310M + 96M + 8M 414M MySQLからの結果によってページを出力する場合、Apache的に同時接続数80に耐えられたとしても max_connectionsが65なら同時接続数65までにしか対応できないので注意 MySQLのチューニング http://nohohonlab.exblog.jp/i2 一旦ApacheのMaxClientsと同じ値で良さそう その設定でメモリ不足になるなど問題があるようなら、ApacheのMaxClients側の値を減らす wait_timeoutも設定する?その場合、ApacheのTimeoutと同じ値でいい? チューニングではないが、以下の値も明示的に設定しておくといいかも? max_allowed_packet インポート時に「MySQL server has gone away」が発生したときの対処 http://company.nankikumano.jp/contents/tech_info/104/ ■設定変更時のエラー対策 MySQLのinnodb_log_file_sizeを設定して再起動(service mysqld restart)しようとするとエラーになる [root@aws-terraport etc]# service mysqld restart mysqld を停止中: [ OK ] MySQL Daemon failed to start. mysqld を起動中: [失敗] InnoDBは my.cnf で指定された innodb_log_file_size の値と実際のログファイルのサイズが異なるとエラーにするらしい ただし、そのファイルが存在しなかった場合は再作成してくれるらしい よって以下の手順で変更する mysql> SET GLOBAL innodb_fast_shutdown=0; # service mysqld stop # mv /var/lib/mysql/ib_logfile* /tmp # vi /etc/my.cnf # service mysqld start これなら大丈夫だった MySQLのinnodb_log_file_sizeを変更したらMySQLが起動しなくなった - /dev/null http://gitpub.hatenablog.com/entry/2013/08/02/001600 innodb_log_file_size のサイズを変更するには - Enjoi Blog http://blog.enjoitech.com/article/196
■Webアプリケーション チューニング
Webアプリケーションへの同時アクセス対策メモ http://refirio.org/view/367 PHPでは microtime() で実行時間を調べることができ、memory_get_usage() や memory_get_peak_usage() で、使用メモリ量を調べることができる http://refirio.org/memos/php/benchmark/ http://refirio.org/memos/php/benchmark/benchmark.php http://refirio.org/memos/php/benchmark/memory.php MySQLのExplainを確認する http://woshidan.hatenablog.com/entry/2015/06/20/165817 MySQLのexplainとかについてしらべたときのメモ http://qiita.com/lastcat_/items/de7b530a94fbcf9ba646 MySQLのEXPLAINを徹底解説!! http://nippondanji.blogspot.jp/2009/03/mysqlexplain.html なぜ、SQLは重たくなるのか?──『SQLパフォーマンス詳解』の翻訳者が教える原因と対策 https://employment.en-japan.com/engineerhub/entry/2017/06/26/110000 MySQLのクエリの良し悪しはrows_examinedで判断する - かみぽわーる http://blog.kamipo.net/entry/2018/03/22/084126 Linuxコマンドでボトルネックを調べる // Speaker Deck https://speakerdeck.com/pyama86/linuxkomandodebotorunetukuwodiao-beru
■動作確認
ひととおりの設定が終わったら、実際のコンテンツもしくは実際のコンテンツに近いものを設置する データベースにはダミーデータを大量に登録するなど、実際のものに近づける その上で、ロードインパクトや外部サーバからのabツールで実際に攻撃してみて、問題ない範囲を探る ・1ヶ月あたりもしくは1日あたりどれくらいのPVに耐えられるか? ・1秒間にどれくらいのアクセスに耐えられるか? 負荷テストについては test.txt を参照
■参考
秒間100万クエリ・8万リクエストの「グラブル」安定稼働を支える、Cygames「3つの取り組み」【デブサミ2017】 (1/2):CodeZine(コードジン) https://codezine.jp/article/detail/10044 秒間100万クエリを受け付ける大規模ソーシャルゲームのバックエンドDBシステムの設計・運用ノウハウ // Speaker Deck https://speakerdeck.com/cygames/miao-jian-100mo-kueriwoshou-kefu-keruda-gui-mo-sosiyarugemufalsebatu... モンストを支えるインフラの今とこれから // Speaker Deck https://speakerdeck.com/isaoshimizu/monsutowozhi-eruinhurafalsejin-tokorekara ドラゴンクエストXは「世界は一つ」を実現するためにどのようなサーバ構成にしているのか? - GIGAZINE http://gigazine.net/news/20120824-dragonquest-backstage-cedec2012/ Nianticの求人から推測する『Pokemon GO(ポケモンGO)』のサーバ構成 - Qiita https://qiita.com/shibacow/items/1ac6f65100252b78a707 Amazon AWSでユーザ数1100万以上にスケーリングするためのビギナーズ・ガイド | POSTD https://postd.cc/a-beginners-guide-to-scaling-to-11-million-users-on-amazons/ Amazon CloudFront の デフォルト上限値が更に大幅にアップしました!(2016秋) | Developers.IO https://dev.classmethod.jp/cloud/aws/cloudfront-limit-update-201610/ 応答速度28.8倍。WordPressをApacheからNginxに移行して感じたブログ運営 http://hamako9999.net/apache2nginx/ MySQLのメモリー使用量を最適化する設定のベストプラクティス | Yakst https://yakst.com/ja/posts/3983