運用3年目から始めるパフォーマンス改善 ~モニタリング・MySQL・Fastly・アプリケーション~
こんにちは、Webエンジニアの Shimpei Takamatsu です。マンションマーケットエンジニアブログは今回からMediumに移行し、これが移行後1本目のエントリーです!
今回は直近2–3ヶ月ほど取り組んでいるパフォーマンス改善のお話です。
概要
現在のマンションマーケットのサイトは初期バージョンのリリース後、リニューアルのためにスクラッチで作り直されており、
現在のメインリポジトリへの最初のコミットは2015/4/4でした。
開発開始から約2年半が経過したプロジェクト、ということになります。
今回のポストでは、運用3年目を迎えた私達のプロダクトがパフォーマンス面で直面した悩みを紹介した後、
実際にアクションに移した以下4つの施策についてご紹介します。
1. アーキテクチャの理解
2. モニタリング・監視
3. アーキテクチャ・ミドルウェアレベルでの改善(MySQL・Fastly)
4. プロファイリング~アプリケーションの改善(非同期化・Redisによるキャッシュ)
3年目の悩み
スタートアップにありがちなことだと思いますが、
マンションマーケットの開発ではユーザに価値を素早く届けるための機能追加が日々行われ、
機能は充実していく一方、徐々にサイトスピードが悪化している状態でした。
特に情報の密度が高い”マンション詳細ページ”(/mansions/detail/:id)は機能追加が行われることも多く、日に日にパフォーマンスが悪くなっていました。
100msecのサイトスピード改善は1%のコンバージョンレート向上に直結すると言われる(※1)ほど、サイドスピードがユーザ体験に直結します。
さらに、マンションマーケットではマンション詳細ページが検索からのランディングページになることも多いため、
サイトスピードはSEO対策上重要で、サイトの流入数に直接的な影響を及ぼします。
今まで機能追加に重きを置いた開発が行われてきましたが、3年目にして守りの一手を打つタイミングが来たと言えるでしょう。
※1:
—
ここからは実際に行った施策についてご紹介します。
1. アーキテクチャの理解
パフォーマンス改善に取り掛かるにあたって一番最初に行ったのは、アーキテクチャの全体を像理解することです。
インフラや主要なミドルウェアを洗い出し、アーキテクチャ全体を図に起こしました。
比較的シンプルなRailsアプリケーションの構成ではありますが、
memcachedとRedisの両方がそれぞれ違った目的で導入されており整理の余地があることがわかったり、
画像の動的リサイズにrefile gemを使っており、画像へのアクセスをアプリケーションサーバで受けていることが、アーキテクチャ上特殊な点であることなどがわかりました。
単純な構成であったとしても、図に起こすことでどのレイヤを狙って改善を行うかを示し、関係者と共通認識を作ることができるので、
性能改善の前にアーキテクチャを図に起こし、チームで読み合わせを行うことをオススメします。
2. モニタリング・監視
改善を行うにしても現在の数値やボトルネックが可視化されていなければ、改善のポイントを見つけることはできません。
マンションマーケットではApplication Performance MonitoringとしてNewRelic、
インフラレベルの監視ツールとしてmackerelを試用していましたが、まだ本運用にのっていなかったため、どちらも契約の後セットアップしました。
APMを導入したことで、なんとなく感じていたパフォーマンス上の課題を可視化し、目標設定・効果測定することが可能になりました。
マンション詳細画面のパフォーマンスの悪さは想定どおりでしたが、
先のrefile gemを使った画像のリサイズがNewRelicの消費時間順で上位に来ていたことは、初期の開発から携わっているメンバーにとっても予想外だった様子でした。
mackerelは直接のパフォーマンス改善からは外れますが、メモリが日常的に逼迫した状態が続いていたため、現状のリクエスト数から試算しなおし、ワーカ台数を減らす決定をすることができました。
この変更により不定期に手作業で行っていたUnicorn再起動の運用をなくすことができました。
監視ツールはモニタリング・アラートのためだけではなく、インフラ面での現状を知り、計画を立てていくために重要な役割を果たしています。
3. アーキテクチャ・ミドルウェアレベルでの改善
実際に行った改善施策のうち、アーキテクチャ・ミドルウェアレベルでの改善を紹介します。
MySQLのバージョンアップ・パラメータチューニング
マンションマーケットではRDBとしてMySQLを採用しており、Amazon RDSで運用しています。運用開始以来特に手を入れていなかったため、バージョンは5.6で運用されていました。
今回はMySQLのバージョンを5.7に上げると同時に、MySQLサーバのパラメータチューニングも行いました。
MySQLサーバのパラメータは莫大にあるため、大枠の指針としてkamipoさんが公開しているmy.cnfを参照し、既存の設定内容と差分のある部分のドキュメントを読み、設定内容を決めていきました。
一気に調整を入れて検証・リリースしたため、個別のパラメータ変更のベンチマークは取れていないのですが、NewRelicのレスポンスタイムの平均値でみて5%程度の効果がありました。
パフォーマンス改善の副次的な効果ですが、ピーク時のリソース消費の削減にも効果がみられ、アクセスのスパイクタイミングでヒヤッとする場面を減らすことができました。
CDN(Fastly)の導入
アーキテクチャ上の大きな変更点として、CDNとしてFastlyを導入しました。
Fastlyは応用範囲が広く、先日のFastly Yamagoya Meetupでも様々な運用事例が紹介されていました。現状マンションマーケットでは主にマンション詳細画面のキャッシュに利用しています。
CDNはもっとサービス規模が大きくなったタイミングでなければ費用対効果が合わない印象でしたが、Fastlyは従量課金でコストが読みやすく、私達のプロダクトのアクセス量だと比較的安価に導入可能だったため、導入の判断が出来ました。
Fastlyの導入にあたって準備したことや苦労した部分などはまた別のエントリーで紹介したいと思います。
4. プロファイリング~アプリケーションの改善
アーキテクチャ・ミドルウェアレベルでの改善とは別に、アプリケーション側に改善も併せて行っています。
プロファイリング
ボトルネック特定のためのプロファイリングにはrack-line-profを利用しています。対象ファイルを指定して行毎の処理時間を出力することができます。
改善対象とするエンドポイントのController・Viewの一通りのプロファイリングを行い、改善の効果が高そうな箇所を特定し、対応方法を検討していきました。
非同期化
ページの下部、ユーザから見てファーストビューから外れている箇所を対象に、サーバサイドレンダリングからクライアントサイドでの非同期なデータ取得・描画に実装を変更しました。
Controller・View上の既存処理を、APIとJavaScriptでの通信・描画処理に切り替え、初期ロード時のサーバサイド処理時間を短縮する施策です。
Redisによるキャッシュ
既にバックグラウンドJobのバックエンドとしてRedisが運用されていましたが、キャッシュ用に新規でRedisを追加しました。
キャッシュする、と言ってもRailsにもページキャッシュ・フラグメントキャッシュなどいくつかの種類があるように、キャッシュの戦略を決める必要がありました。
既にFastlyも導入済みのためViewレベルのキャッシュ(ページキャッシュやフラグメントキャッシュ)はRailsのレイヤでは行わない方針とし、Redisクライアントを直接使い、取得データをキャッシュする方針としました。Railsでいう低レベルキャッシュの状態です。
既に数カ所Redisによるキャッシュに移行した箇所がありますが、特に改善の効果が高かったのは、RDBから都度引いていた都道府県・市区町村別の坪単価ランキングをRedisのsorted setで代替した部分です。
以下はリリース前後のNewRelicでのレスポンスタイムの状況です。
まとめ
今回は最近取り組んだパフォーマンス改善施策についてご紹介しました。
- 運用3年目でパフォーマンスに課題を感じ始めた
- 最初にアーキテクチャを理解するための作図とAPM・モニタリングツールの導入を行った
- アーキテクチャ・ミドルウェアレベルでの改善とアプリケーションレベルでの改善の両方に取り組んでいる
まだまだパフォーマンスには改善の余地があり、これからも継続的に取り組んで行く予定です。