なまえは まだ ない

思いついたことをアウトプットします

ISUCON14延長戦の記録② インデックスの追加

ISUCON14の延長戦をやってます

以下の記事の続きです。

furusax0621.hatenablog.com

前回はチューニングの前段階として、競技用環境の整備をしました。 ベンチマーカーを回して得た結果を元に、サーバーチューニングをしていきます。

なお、最終的なコードは以下のリポジトリで公開しています。

github.com

インデックスを追加する

初回のスコアはおよそ1,000前後になりました。スロークエリログを収集している関係で、素の状態でベンチを回すより若干低いかなと思います。

改善の第一弾として、MySQLのテーブルにインデックスを追加していきます。ISUCONは毎度まったくと言っていいほどインデックスが貼られていないため、MySQLサーバーへの負荷が最初のボトルネックになりがちです。本戦当時は top コマンドなどで状況を確認しましたが、今回は割愛します。

さて、インデックスを不必要に追加すると、レコード挿入・更新時のインデックス再計算コストがかさんで性能劣化を引き起こします。俗に言うインデックスショットガンアンチパターンです。これを回避するには、実際に利用されているクエリを解析して必要最小限のインデックスを割り出す必要があります。

ここで役に立ってくるのがpproteinです。pproteinで収集したスロークエリログの統計は、様々なカラムで降順/昇順にソートすることができます。

スロークエリログの分析方法についてはたくさんの解説記事がありますが、まず気にしたいのはRows_examinedの値、そしてRows_sentとのギャップです。Rows_examinedはクエリを実行する過程で走査された行数、Rows_sentは実際にクライアントへ送信された行数を指します。ここに大きなギャップがある場合、WHERE句で絞り込みをしているのにインデックスが存在しておらず、テーブルのフルスキャンが走っている可能性があります。典型的な例だとたった数行を取得するために数万行のレコードをスキャンしていたりします。

pproteinでRows_examinedの平均値や最大値の降順でソートし、Rows_sentの行と見比べてギャップの大きそうなクエリを絞り込んでいきます。この方法でインデックスを割り出した結果が以下のPull Requestです。

github.com

インデックスを適用するやり方は色々な派閥があるかと思いますが、私はインデックス追加用のSQLファイルを作成し、初期データが投入された後に適用するようにしています。

ISUCONは例年Initializeエンドポイントの中でスキーマの初期化や初期データの投入が行われます。今回はテーブルの再作成、マスタデータの投入、初期データの投入という順番で実施されてました。インデックスはテーブル作成時にも追加できますが、データ投入前に設定するとレコードが追加される度にインデックスの計算が走り、想定外の時間がかかってしまう可能性があります。Initializeエンドポイントにはタイムアウト(今回は30秒)があるので、チューニングを突き詰めていく過程でこれが足を引っ張ってしまうかもしれません。データ投入後であればインデックス計算は一度で済むため、比較的高速に適用することができます。

まとめ・次回予告

チューニングの第一弾として必要なインデックスの割り出し、追加をしました。この変更を入れることで、スコアが3,500程度に伸びました。まずは一歩前進ですね。

次回からいよいよアプリケーションコードの実装改善に移っていきます。つづく。