ISUCONに負けた
ISUCON、見事に予選落ちしました。憂鬱な火曜日で出てました。言語はRubyを選択しました。
やったこと
nginxアクセスログ解析
kataribeを使ってアクセスログの解析を行った。 message、login、iconsあたりが遅いことが判明。 特にiconsがボトルネックになっていたので、icon取得部分を改善することにした。
icon取得部分の改善
- 以下のSQLを実行するとユニークの画像件数が67件しかないことが判明
SELECT MIN(id), name, data FROM image GROUP BY name, data
- とりあえず、重複行は全て削除
DELETE FROM image WHERE id NOT IN ( SELECT i FROM ( SELECT MIN(id) AS i, name, data FROM image GROUP BY name, data ) hoge )
- 続いて、Unique Indexを追加。
- ridgepoleを使用していたので、Schemafileに以下を追加
add_index :image, :name, unique: true
- アプリケーションについては以下のように修正
- statement = db.prepare('INSERT INTO image (name, data) VALUES (?, ?)') - statement.execute(avatar_name, avatar_data) - statement.close + begin + statement = db.prepare('INSERT INTO image (name, data) VALUES (?, ?)') + statement.execute(avatar_name, avatar_data) + statement.close + rescue => e + end
/message actionのチューニング
- n+1が発生したので、改善
- Schemafileに以下を追加して、Index追加
add_index :message, %w[channel_id id]
ログインのパスワード認証をDB側で行うように修正
※これは効果なかったかもしれない
- コードを以下のように修正
+ name = db.escape(params[:name]) + password = db.escape(params[:password]) - row = db.query("SELECT * FROM user WHERE name = '#{name}'").first - if row.nil? || row['password'] != Digest::SHA1.hexdigest(row['salt'] + params[:password]) + # パスワード認証とユーザー取得を一括で行う + row = db.query("SELECT id FROM user WHERE name = '#{name}' AND password = SHA1(CONCAT(salt, '#{password}')) LIMIT 1").first + if row.nil?
- Schemafileに以下を追加
add_index :user, [:name, :password]
fetch処理修正
fetchは加点対象じゃないと書かれていたが、改善することにした。
- n+1の改善
sleep 1.0
の削除
iconsの画像を静的コンテンツに変更
- 画像ファイルが133種類しかないことがわかったので、全部書き出して、public/icons配下に置いて、nginxから配信することにした。
反省
- 上記以外にも色々やったけど力及ばず。Cache-Controlを確認してなかったのがすごく痛かった。絶対nginxがおかしいんじゃないかという話をしていた。ちゃんとレギューレーション読めばよかった。
- 台風だったので、当日帰れなくなったら困るからリモートにしようという話になった。一緒の場所でホワイトボード前で話すとかできなかったのも結構よくなかったなーって思ってる。
次に向けて
- 次は予選突破ぐらいするぞ!(出場機会あれば)
最後に
- 運営のみなさまありがとうございました。非常に勉強になりました。