Open39

isucon本メモ

euryopaeuryopa
sudo su - isucon
/home/isucon/private_isu.git/benchmarker/bin/benchmarker -u /home/isucon/private_isu.git/benchmarker/userdata -t http://<target IP>
euryopaeuryopa

https://github.com/tatsujin-web-performance/tatsujin-web-performance
競技者用インスタンスでnginxのログ設定

sudo vim /etc/nginx/nginx.conf
log_format json escape=json '{"time":"$time_iso8601",'
                            '"host":"$remote_addr",'
                            '"port":$remote_port,'
                            '"method":"$request_method",'
                            '"uri":"$request_uri",'
                            '"status":"$status",'
                            '"body_bytes":$body_bytes_sent,'
                            '"referer":"$http_referer",'
                            '"ua":"$http_user_agent",'
                            '"request_time":"$request_time",'
                            '"response_time":"$upstream_response_time"}';

access_log /var/log/nginx/access.log json;

設定ファイルの検証

sudo nginx -t

nginx再起動

sudo systemctl reload nginx
Warning: The unit file, source configuration file or drop-ins of nginx.service changed on disk. Run 'systemctl daemon-reload' to reload units.

warining通りに

sudo systemctl daemon-reload

を実行(あとで調べる)

euryopaeuryopa

aarch64用のalpをインストール

wget https://github.com/tkuchiki/alp/releases/download/v1.0.21/alp_linux_arm64.zip
unzip alp_linux_arm64.zip
sudo mv alp /usr/local/bin/
sudo chmod +x /usr/local/bin/alp
alp --version
sudo rm alp_linux_arm64.zip
euryopaeuryopa
sudo rm /var/log/nginx/access.log
sudo systemctl reload nginx
cat /var/log/nginx/access.log | alp json

古いアクセスログを消しておかないと、jsonフォーマット適用前のcombinedのアクセスログもalp jsonの対象になるため、エラーが起きる
また、nginxをreloadしないと新しくaccess.logが出力されない。

cat /var/log/nginx/access.log | alp json
invalid character '.' after top-level value

alpのコマンド

alp json \
    --sort sum -r \
    -m "/posts/[0-9]+,/@\w+,/image/\d+" \
    -o count,method,uri,min,avg,max,sum \
    < /var/log/nginx/access.log
euryopaeuryopa

abコマンドインストール

sudo apt update
sudo apt install apache2-utils
which ab
ab -c 1 -n 10 http://localhost/
euryopaeuryopa

ログにタイムスタンプを付けてローテート

#! /bin/sh
#実行時点の日時をYYYMD-HHMMSS 形式で付与したファイル名にローテートする
sudo mv /var/10g/nginx/access.log /var/log/nginx/access.log. date `Y%m%d-%H%M%S`
#nginx にログファイルを開き直すシグナルを送信する
sudo nginx -s reopen
euryopaeuryopa

mysqlのスロークエリログを出力させる

sudo vim /etc/mysql/mysql.conf.d/mysqld.cnf
[mysqld]
slow_query_log      = 1
slow_query_log_file = /var/log/mysql/mysql-slow.log
long_query_time     = 0
sudo systemctl restart mysql
euryopaeuryopa

スロークエリログを出力

sudo mysqldumpslow /var/log/mysql/mysql-slow.log

スロークエリログを消して、再度出力できるようにする

sudo rm /var/log/mysql/mysql-slow.log
sudo mysqladmin flush-logs
euryopaeuryopa

mysqlにアクセス

sudo mysql
show database;
use isuconp;
show tables;
show create tables comments\G
euryopaeuryopa

rubyのワーカーの並列プロセス数を4にする

sudo vim /home/isucon/private_isu/webapp/ruby/unicorn_config.rb
systemctl restart isu-ruby
systemctl status isu-ruby
worker_processes 4
euryopaeuryopa

vscodeの拡張機能remote sshを使ってec2内のファイルをvscodeで編集
ローカルでアクセスする際に使用した ssh -i 〜のコマンドを打つ

ssh -i ~/Downloads/private-isu.pem ubuntu@....

configでuserがrootになっているので、ubuntuに直す

euryopaeuryopa

5章

スロークエリログツールのインストール

sudo apt update
sudo apt install percona-toolkit
git clone https://github.com/kazeburo/query-digester.git
cd query-digester
sudo install query-digester /usr/local/bin
sudo pt-query-digest /var/log/mysql/mysql-slow.log | tee digest_$(date+%Y%m%d%h%M).txt
euryopaeuryopa

インデックスを作成

alter table comments add index post_id_idx(post_id, created_at desc);
alter table comments add index idx_user_id(user_id);
euryopaeuryopa

6章

my.cnfのserverブロックに

innodb_flush_method=O_DIRECT

を指定しmysqlを再起動するとmysqlのbuffer poolとOSのディスクキャッシュの2重管理を防げる

fsync: ディスクキャッシュ上に書いたデータをストレージデバイスに同期させるOSの命令。ミリ秒単位で時間がかかるため、OSが行う非同期のフラッシュ操作に任せることもできるが、OSダウン等によるデータの損失に注意。

innodb_flush_log_at_trx_commit=2

とすれば、1秒ごとにログをフラッシュする。デフォルトの1ではコミットごとにログをフラッシュする。

disable-log-bin=1
sync_binlog=1000
euryopaeuryopa

ngx_http_gzip_static_moduleを使用すると、事前にgzip圧縮したファイルをnginxから配信できるので、圧縮によるCPU消費を抑えられる。Zopfliを使ってgzip圧縮すると圧縮効率がよい。
ngx_http_gunzip_moduleを使うとgzip圧縮したファイルおくだけでgzip対応/非対応どちらのリクエストにも対応できる。

euryopaeuryopa

アプリケーションサーバーでもgzip圧縮をしたほうがよい。少ない容量でプロキシに送れるため。

euryopaeuryopa

アップストリームサーバーとのコネクションをkeep aliveしたいときはHTTP/1.1を利用し、Connection headerを空文字にする

location / {
    proxy_http_version 1.1;
    proxy_set_header Connection "";
    proxy_pass http://app;
}

upstream app {
    server localhost:8080;
    keepalive 32;
    keepalive_requests 10000;
}
euryopaeuryopa
sendfile on;
tcp_nopush on;

上記はデフォルトでは無効だが、基本的には有効にしておくとよい。
sendfile: ファイル読み込みと送信時にsendfileシステムコールを使うことで、カーネル空間からユーザー空間へのメモリコピーが発生しないので効率が上がる。ネットワークファイルシステムでは無効にする。
tcp_nopush: sendfileを有効にしたときのみ有効にできる。送信するパケット数を減らして効率よくファイル送信ができる。

euryopaeuryopa

インメモリのキャッシュは短めに設定して、問題があるデータをキャッシュしたとしてもすぐに消されるようにしておく。
データを保存する際に自動でシリアライズしてくれるライブラリもあるが、シリアライズ方法を変えると古いデータが読み込めなくなる可能性があるので注意

euryopaeuryopa

nginxのproxy_cache_lockを有効にすると、1つのリクエストがキャッシュを更新する間はproxy_cache_keyが同一のリクエストをブロックしてoriginに送らないようにできる。
nginx経由でoriginに行くこと、nginxでOriginからのHTTPレスポンスをキャッシュする設定にしているときのみ使える

euryopaeuryopa

Goではsingleflightを使うと同時発生した複数の呼び出しを1つにまとめられる

euryopaeuryopa

開発用の設定を変更するのもパフォーマンス向上に必要

  • デバッグモードを無効化する
    • その他、mysqlなど必要以上にログ出力していないかをまず確認する
  • ライブラリやフレームワークのデフォルト設定が開発用になっていないか確認
euryopaeuryopa

httpクライアントは使いまわした方が効率が良い。コネクションはOSのプロセス単位で保持するので、マルチプロセスの場合はその数だけコネクションが保持される。
Goのhttp.Clientは使用法に注意

  • http.DefaultClientはtimeoutが設定されないので、http.Clientを使う
hClient := http.Client{
    Timeout: 5 * time.second,
}
  • res.Body.Close()を忘れるとTCPコネクションが再利用されない
  • BodyをReadせずにCloseするとTCPコネクションが切断されるのでレスポンスボディを読み切る必要
defer res.Body.Close()
_, err = io.ReadAll(res.Body)
euryopaeuryopa

ストレージの性質を理解することで、アプリケーションからのファイルI/Oを効率的に行える。
ファイルシステムの性能計測にはfioコマンドを使用する

euryopaeuryopa

lsblk, dfコマンド:
接続されたブロックストレージ(=ブロックデバイス)を確認する

euryopaeuryopa

/etc/fstabファイル:OSが起動するときに自動でディスクをマウントする設定を書くファイル

defaultではファイルのメタデータのみ削除し、ファイルの実体は後で消す手法
これだと実体を削除する際にディスク負荷が発生。
マウントオプションをdiscardにしておくと、メタデータと一緒にファイルの実体も削除するので、突発的な負荷を回避できる

euryopaeuryopa

/dev/sdaのI/Oスケジューラは/sys/block/ada/queue/schedulerで確認できる

euryopaeuryopa

top -1コマンド: CPU利用率を見れる。1をオプションにつけると、各CPUコアの利用率が見られる。

topで表示される値

  • us: ユーザ空間(システムコールを利用するLinux OS上のアプリケーション動作部分)におけるCPU利用率。デプロイされているWebアプリケーションがCPUを利用している際に上昇する値
  • sy: カーネル空間(Linux Kernel内の処理)におけるCPU利用率。forkやコンテキストスイッチが多く発生しているときに上昇。
  • ni: nice値。どのプロセスを優先して切り替える(コンテキストスイッチ)のかの優先度。19→ー20になるにつれて優先度があがる。nice -n19 XXXのように-nの後に数字を書くことで処理XXXの優先度を変更できる。
    • reniceコマンド: 実行中のプロセスのnice値を変更できる
    • ioniceコマンド:I/Oスケジューラの優先度を変更できる。ログファイルの圧縮・削除といった大きなI/O処理を行うときに優先度を下げることがある。
  • id: 利用されていないidleのCPU
  • wa: I/O処理待ちのプロセスのCPU利用率
  • hi: Hardware Interrupt。ハードウェア割り込みプロセスの利用率
  • si: Soft Interrupt。ソフト割り込みプロセスの利用率
  • st: Steal。パブリッククラウドなどの仮想化された環境のLinux上で利用されているCPU利用率。
euryopaeuryopa

ulimitコマンド: User limit。プロセスが利用できるリソースの制限を設定する概念。開けるファイル数の上限やCPU稼働時間などの制限を設定できる。/proc/PID/limitsのファイルを見ればわかる。制限を変更することもできる。mysqlの場合は/etc/systemd/sysytem/mysql.service.d/limits.confというフォルダとファイルを新規作成し、そこで値を設定することで変更できる。

euryopaeuryopa

LinuxのKernelパラメータ

  • net.core.somaxconn: socket max connection。接続要求のキューを貯めるbacklogのサイズを決める設定。sysctl net.core.comaxconnで確認、sudo sysctl -w net.core.comaxconn=8192で一時的な変更ができる。永続な変更も行える

  • net.ipv4.ip_local_port_range:ポート番号の範囲の設定。コネクションが過多になるとデフォルトのポート番号では足りないことがある。IANAでは0~1023番ポートが「System Ports」、1024~49151が「User Ports」、49152 ~65535が 「Dynamic and/ or Private Ports」とされており、動的なポート(Ephemeral Ports)としては1024以降が適切。sysctlコマンドで変更ができる。

  • 同一ホスト内の別プロセスに接続する際は、ポート番号を指定せずにUnix domain socketをnginxで使うように設定するとパフォーマンスがあがる。listen unix:/var/run/nginx.sock

euryopaeuryopa

MTU(Maximum Transmission Unit): ネットワークインターフェースから送信できる最大送信サイズ。1500 byteが基本だが、9000 byteまで拡大できる。ip linkコマンドで設定を確認・一時変更できる。udevというLinux Kernelにおけるデバイス管理ツールを使用すると、恒久的に設定を変更できる。

MTUはパケットをやり取りするクライアント/サーバー、経由するネットワーク機器すべてにおいて同一の設定がされていないと意味がないため、効率化に寄与することはそうそうない。