Share
Embed
「最高のチューニング」をしないために / hack@delta 24.10
FUJIWARA Shunichiro
October 29, 2024
Transcript
45 slides
-
自己紹介 @fujiwara (X/Twitter, GitHub, Bluesky) 面白法人カヤック SREチーム ISUCON 優勝4回 /
運営(出題)4回 github.com/kayac/ecspresso github.com/fujiwara/lambroll -
サイトが落ちる原因 もはやうろおぼえ (当時書いたドキュメントもリポジトリもサービス譲渡時に引き渡し 済、チャットはSkypeだったのでログもない) アプリケーションサーバーでメモリが足りていない ピーク時に不足 → swap in/outが多発 MySQL
で filesort するクエリが大量に発生 JOIN したテーブルの count だったはず お題 → 投稿(こえ) → コメントを数えるクエリ disk IO 多発 nginxでコネクション数が足りなくて詰まる 当時からnginxをバリバリ使っていたのは先進的だが -
当時は「diskにアクセスしたら負け」 2011年当時借りていたレンタルサーバー(専有) CPU 2〜4コア メモリ 4〜8GB ストレージ SATA HDD シングル
or RAID-1 (7200rpm IOPS 100以下ぐらい?) ストレージがとにかく遅い (今から見ると) -
diskにアクセスするとなぜ負けてしまうのか(当時) アプリケーションサーバーでメモリが足りなくなるとswapが発生して遅い swap out = メモリに載りきらない分をdiskに書き出す swap in = diskから読んでメモリに戻す
diskが遅いため、ほとんどの処理時間をswap処理に費やしてしまう MySQL の filesort が発生すると遅い クエリ結果がメモリに載りきらない場合、ファイルに書いてからソート diskが遅いため(以下略 雪だるま式に待たされるリクエストが増加 nginxのコネクションが埋まって接続不能になる -
具体的なチューニング アプリケーションサーバーのプロセス数を減らす swap が発生しない数まで下げる slow query logを出す 関連テーブルの count 結果を更新時にカラムに保存する
いわゆる「非正規化」 関連テーブルのINSERT時に +1, DELETE時に -1 一度メンテナンスを入れてデータを埋めてindexを足して再開 covering index にできたので filesort 撲滅 nginxの設定 worker_connections 1536 → 4096 keepalive_timeout 65 → 5 -
2011 → 2024 ストレージがそもそも速い(SSDで数万IOPSが普通、当時のHDDは100 IOPS/1本程度) メモリから溢れても即終了にならない クラウドならスケールアップが容易 64コア/512GBとか128コア/1TBとか… 当時のこえ部の問題は、今ならお金を払えばすぐに解決できる アプリケーションサーバーのメモリ
= スケールアップ MySQL の filesort = 高速なストレージ、スケールアップ nginx のコネクション数 = マネージドなLBに任せる でも「お金を掛けなくても解決できた」問題でもある -
Lobi ゲームユーザー向けのコミュニティ サービス 2010年 ナカマップとしてリリース 2013年 Lobiに改名 オンプレミスの仮想化基盤(KVM) アプリケーションサーバーは比較的 容易にスケール可能
hostは24vCPU、台数も余裕あり データベースには ioDrive 当時最強の フラッシュメモリストレージ -
事件発生 2013年6月某日 ある日のピーク時刻(23時頃)にアプリケーションのレイテンシが急激に悪化 全てのサーバーの CPU, メモリ, IO などは完全に余裕がある 数十分すると回復する 毎日発生するけどモニタリングしても全く理由が分からない
slow query log もちゃんと取っているけど何もでてこない -
# ethtool eth1 Settings for eth1: Supported ports: [ TP
] Supported link modes: 10baseT/Half 10baseT/Full 100baseT/Half 100baseT/Full 1000baseT/Full Supports auto-negotiation: Yes Advertised link modes: 10baseT/Half 10baseT/Full 100baseT/Half 100baseT/Full 1000baseT/Full Advertised pause frame use: No Advertised auto-negotiation: Yes Speed: 100Mb/s Duplex: Full Port: Twisted Pair PHYAD: 1 Transceiver: internal Auto-negotiation: on MDI-X: on Supports Wake-on: pumbag Wake-on: g Current message level: 0x00000001 (1) Link detected: yes -
# ethtool eth1 Settings for eth1: Supported ports: [ TP
] Supported link modes: 10baseT/Half 10baseT/Full 100baseT/Half 100baseT/Full 1000baseT/Full Supports auto-negotiation: Yes Advertised link modes: 10baseT/Half 10baseT/Full 100baseT/Half 100baseT/Full 1000baseT/Full Advertised pause frame use: No Advertised auto-negotiation: Yes Speed: 100Mb/s <======================== ???? Duplex: Full Port: Twisted Pair PHYAD: 1 Transceiver: internal Auto-negotiation: on MDI-X: on Supports Wake-on: pumbag Wake-on: g Current message level: 0x00000001 (1) Link detected: yes -
100Mb/s !!? # ethtool eth1 Settings for eth1: Speed: 100Mb/s
1GbpsのNIC/スイッチなのになぜか100Mbpsでリンクアップしていた (多分オートネゴシエーションがおかしかった) ネットワークが100Mbpsで頭を打った結果 MySQLはクエリを2msで完了して結果を送信する slow logはクエリが完了したタイミングで書かれる(ので出ない) クライアントは結果を受信するのに20ms掛かってしまう(ので詰まる) -
対策 NICの設定 # /etc/sysconfig/network-scripts/ifcfg-eth1 ETHTOOL_OPTS="speed 1000 duplex full autoneg on"
Zabbixの監視テンプレートで ethtool ethX | grep Speed の結果を取得する設定 -
ちゃんと負荷試験をするようになった システムにリクエストを送信して負荷を掛け、 想定の/必要な性能が出ているかどうかを確認するテスト リリース前や新機能追加、大規模改修時にやることが多い カヤックの例 2014年ごろまで: ほぼしていない 2015年ごろから: やりはじめた 2018年ごろから:
本番の想定スケール限界までやる 2015年にはオンプレ廃止、全部クラウドなのでやりやすくなった -
負荷試験をやる/やらない 負荷試験は結構大変 開発の最終盤にならないとできない(ことが多い) 準備が大変 問題が出たからといってリリースを遅らせられるかというと…(体制による) とはいえ やる = リリース後に発生する性能問題の9割(体感)は把握できる やらない
= やっていたら把握できていたはずの問題が、リリース後に全部出る やらないと事故確率が大幅に上がると思ってよい -
負荷試験をする「前」に考えること ゴールを決める レイテンシ(レスポンスタイム)が目標? どのレスポンスを何秒で返せればOKなのか 平均値? 中央値? P99? max? そのレイテンシが保てる最大の並列アクセス数は? スループット(単位時間内に処理できるリクエスト数)が目標?
レイテンシ要件はない? レイテンシ1secで1,000並列 レイテンシ10secで10,000並列(どちらも1,000req/sec) あるサーバーリソースでのxxの最大化が目標? -
負荷試験をする「前」に考えること その試験で測りたいものを言語化する レイテンシ スループット サーバーリソースの利用率・利用量(コストも含む) 運用中に実際に起きるどのような状況で、これらの数値目標を達成するのか 【具体的な例】 状況 : Push通知から◦◦分以内に◦◦人のユーザーがアプリを起動する
シナリオ : アプリが起動してホーム画面に遷移するときに発生するAPIアクセス 目標 : 平均レイテンシ250msec、P99 2sec、サーバーエラーなし 詳しくは ISUCON本 1章へ -
結果を評価する 1. 負荷テスト側の出力とサーバー側のメトリクスに齟齬がないか →メトリクスの取得がおかしい可能性が高いので見直す 2. エラーは起きてないか →エラーが目標/想定より多い場合は性能を評価する意味がない 原因を調査する 3. レイテンシは目標に達しているか
→これ以上並列度を増やすのはたいてい無意味 ボトルネックの調査をする 4. スループットは目標に達しているか or →並列度を2倍にして変化を見る 無闇に変数を変えて試さない -
クライアント側で起きる問題の頻出例 ネットワーク帯域が上限を打っている 1Gbpsの回線で1MBのレスポンスを取得→(たったの)125req/sec(=125MB/sec) HTTP Keep-Aliveが無効になっている 3 way handshake のオーバーヘッドが大きい Goで自作した場合にやりがち:
http.Response.Body を全て読み切らない →次のリクエスト時は新規のTCP接続になる (ISUCONの作問者も最初はだいたい全員やらかします) ファイルディスクリプタを使い果たしている ulimit -n (max open files)がデフォルトの1024 ローカルポート(エフェメラルポート)が枯渇している 大量/高速に接続切断を繰り返すと使い果たす ISUCON本 9章をどうぞ