Eight事業部 Platform Unit / Engineering Manager の 藤井洋太郎(yotaro) です。
私が属するPlatform Unitはいわゆる基盤チームとも呼ばれ、「アーキテクチャ刷新」「データ基盤整備」「セキュリティ」「環境整備」など多方面での開発・改善を行っています。
その中でも今回は「APIパフォーマンス改善」の取り組みを取り上げたいと思います。
アプリケーション監視 / New Relic
さて、Eightではアプリケーションのパフォーマンス監視のために、New Relic を利用しています。
New Relic は多機能で、モバイル向け/インフラ向けのサービスも展開されています。眺めているだけでも楽しいNew Relic。
が、それゆえ何から手をつけていいかわからない。。となりがち!?
そこで今回は Eight での New Relic を使った「シンプル」で「効果的」なパフォーマンス改善事例を紹介します。
Eight での導入環境
まず、Eightでは以下のようにNew Relicを導入しています。
Staging環境 | Production環境 | |
---|---|---|
プラン | New Relic Lite(無償版) | New Relic Pro(有償版) |
ホスト数 | 全台 | Availability Zone毎に1台ずつ |
無償版
は機能制限やデータの保持期間が1日となりますが、開発環境であれば十分に利用できるものになっています。
何台に導入しても現状は無料で利用できますし、gemをインストールするだけなので非常におすすめです!
本番環境に適用する際の注意としては、New Relic Agentを有効化すると、いくらかのパフォーマンス劣化/負荷が発生します(Eightでは5〜10%程度CPU使用率が上がります)。 そのため、Eightでは全台に適用ではなく各Availability Zoneに1台ずつNew Relic Agentを有効化するようにしています。
効果が大きい機能にフォーカスする
我々のチームではここ1ヶ月ほど、パフォーマンス改善を行ってきました。
短い期間の中で、Webサーバー全体で約20%のパフォーマンスを向上することができました 🚀
この際に意識したことは、改善効果が高い機能/APIに絞るということです。 対象を絞り、ボトルネックを見つけるのに New Relic が非常に役立ちます。
①Web処理量が多い機能を中心に攻める
サービス上の Transactions > Most time consuming
からすぐに確認することができます。
Eightでは「名刺撮影」「名刺/ユーザ検索」「フィード取得」といった主要機能が上位を占めています。 ユーザ目線としても、これらの主要機能を高速化することの価値は大きいです。
処理量が多い機能にフォーカスすることのメリットは大きく2つあると考えています。
積み重ねとしてユーザ体験向上につながる
Web処理量が多いからといって必ずしもその機能自体が遅いとは限りません。 1件1件の処理は速いけれど、たくさん利用されているというケースは往々にしてあるでしょう。
遅くてたくさん使われている機能はもちろん、高頻度に利用される速い機能も「さらに改善」することでUX向上につながりやすいです。運用コストを抑えられる
パフォーマンス改善をすることで、Web処理量が少なくなれば待ち受けるサーバーの台数を減らすことが可能になります。 20%使用しているAPIが10%になれば、サーバー運用コストも1割削減することができます。
②明らかに遅い機能を潰す
2つめは、処理量ではなく「遅い」機能にフォーカスして改善します。
New Relic でいうところの、 Slowest average response time
または Max(ms)
の項目になります。
当たり前のことのように感じるかもしれませんが、遅い機能は開発環境やステージング環境では気付きにくく、本番環境のみで顕在化することが多いです。(開発環境でも遅かったら気付いて直しているはずですからね..?🤐)
実際に、この処理がなぜこんなに遅いんだ!?という想定外のケースも多くありました。
このようなケースは、特にヘビーユーザなど大量のデータを持つユーザが対象になりがちです。また特定条件化のみで発生することも多いです。
よく使ってくださっているユーザさんほど、遅い体験になってしまう。ということは避けたいですよね。
改善方法
改善対象を決めた後は、どこで遅くなっているかという調査になります。
ボトルネックになる原因はいくつもあると思いますが、もっとも多いケースは同じような処理を繰り返し行ってしまっている事が多いです。いわゆる「N+1問題」が最も多いケースとなるでしょう。*1
また、これらの改善は比較的容易でかつ改善効果が大きいです。
New Relicでは便利なことに、Transaction(API)毎にある程度の粒度で各処理の平均コール数をまとめ上げてくれます。
上記の例では、 DynamoDB に対するリクエストが17〜18回
、キャッシュストアへのアクセスが30回
と繰り返し実行されていることがわかります。
1つ1つの処理は微々たるものですが、塵も積もれば、という感じですね。このCall数を1に近づけられれば 150ms
は速くできそうといった、目標設定もしやすいです。
また、繰り返し多くの処理をしているということは、それだけメソッドコールが発生していることにもなります。これらはNewRelic上では現れて来ず、ControllerやRubyの処理として計算されています。そのためN+1を改善することで、それ以外の部分でも高速化することが期待できます。
細かい部分は省きますが、基本的な考え方は以下です。
RDB
N+1 が発生している場合は、基本に忠実に ActiveRecord の eager loading で対応です。Railsを利用している方は言わずもがなでしょう。
考慮漏れがある箇所に対して、細かくチューニングしていきます。
キャッシュストア(Memcached/Redis)
キャッシュに対しては、 Rails や Dalli gem が提供している *_multi
メソッドを利用することで一括取得/更新ができます。
Rails.cache | メソッド |
---|---|
Rails.cache.read_multi | 一括読み込み |
Rails.cache.write_multi | 一括書き込み *2 |
Rails.cache.fetch_multi | 一括読み込み、キャッシュがなければ書き込み |
Elasticsearch
Eightでの名刺の全文検索で利用している、Elasticsearchでも N+1 が発生していました。
Elasticsearch にも一括で操作可能な API が提供されています。
Rubyでは elastic/elasticsearch-ruby
gem のbulk APIを利用すると良いでしょう。
その他
DynamoDBなどに対する処理の高速化において、現状の設計ではバッチ処理に移行することが難しいケースもありました。
そこで Rails <-> Cache <-> DynamoDB
といったようにキャッシュを挟む構成に変更し、高速化を実現しました。
効果
上記のような改善を通して、実際にEightにおける「処理量が多い機能」と「遅い機能」の改善を行った結果です。
- 名刺追加処理速度が1.5倍に
- フィード取得速度が1.5〜2倍に
- 名刺/ユーザ検索速度が2倍に
- 企業向けの管理者ページの検索機能などは、なんと10倍以上も高速化しました。
(これまでご不便おかけしておりました。すみません。。!🙇🏻)
冒頭でも紹介しましたが、これらの改善をトータルでみたときに、Web全体のパフォーマンスは約20%も改善しました。
狙い通りサーバーコストも削減!
またEightのWebサーバー群は「リクエスト数
」と「平均レスポンスタイム
」によってオートスケーリングするようになっているため、
レスポンスタイムが向上したことによって、目論見通りサーバー運用費を削減することにもつながりました 💸
最後に
長々と紹介してきましたが、実は我々のチームが今回のパフォーマンス改善に向き合った期間は1ヶ月にも満たないかもしれません。 それでも目に見える大きな改善を効果的に行うことができました。
速さは、機能である
私達のチームは機能開発こそ多くありませんが、このように表には現われて来ないような「速さ」や「安定性」という観点でユーザ体験の向上につなげていければと思っています。
*1:bulletというN+1を検知するgemもありますが、現状は誤検知をすることも多くEightでは採用していません。
*2:write_multi は Rails5.2 以降から利用可能です。Rails5.1以下のサポートも切れましたしVer upしていきましょう💪🏻 https://blog.bigbinary.com/2018/07/03/rails-5.2-adds-write_multi-for-cache-writes.html