こんにちは エンジニア の @len です。現在、弊社のサービスの一つである ferret OneOnePage の速度パフォーマンス改善をやらせてもらっていています。今回はそこで学んだことや経験を共有したいと思います

環境
 ・ Ruby 2.4.4
 ・ Rails 5.1.5
 ・ Mongoid 6.3.0
 ・ MongoDB 3.6.5

STEP 1. スロークエリを見つける

基本的には、Railsのログを頑張って見ればなんとかスロークエリを見つけることができますが、大きなサービスの場合(特にSPA)、結構時間がかかります。そこで、スロークエリを早めに見つけるためにいくつかツールを使います。
自分は主に、2つのツールをよく使います。

  1. rack-mini-profiler
    どの画面、どのpartial、どのファイル、どの行で(スロー)クエリがあるか出してくれるツールです。

    
2. Page Load Time (Chrome拡張機能)
ちらっと表示速度を見たい、client-serverどちらが遅いかを見たい時を使います。拡張なので、本番でも使えます。

STEP 2. スロークエリを改善対策

スロークエリの改善といえば、indexを使うとすぐに効果が出せますね。自分も以前はそう思っていました。しかし、indexを使うにもデメリットはあります。indexを使う前に、以下のように他の方法がないか考えてみましょう。

  • このクエリは本当に必要なのか?
  • このクエリに既存のindexを利用できるか?
  • このクエリの結果を絞ることはできるか?(全件検索か、一週間分か一日分か)

STEP 3. indexを作成する

indexの書き方は色々ありますが、それぞれの場合において適切なindexを使い分ければ、より効果を出すことができます。

  • partial indexが使える場合は使います。これによってストレージの負荷を減らせます。
    class Article
       belongs_to: author, option: true
       belongs_to: category, option: true
    end

Categoryに関係なく、Authorに紐づいたArticleを探したいときは次のクエリを発行します。

Article.where(author_id: 1)

この場合、index author: 1, category: 1 よりindex author: 1の方が適切です。
検索速度は一緒ですが、ストレージにかかる負荷が少ないです。

  • compound indexが利用できれば、ストレージを減らすことができます

  • compound indexの順位は気をつける
    article_1という名前のArticleを探したいときは次のクエリを発行します
    Article.where(name: "article_1", author_id: 1)
    index name: 1, author_id: 1index author_id: 1, name: 1は場合によって、クエリ速度が違います。

  • 一時的に使いたいindexなら、TTL Indexを利用します
    期間を設定し、それより古くなったら自動でindexを消すことができます

STEP 4. 本番環境で新しいindexを反映する

indexを作成する時、db.createIndexを使うと、他のデータベースに関連する作業がブロックされてしまうので、メンテナンスではないときは、必ずbackground: trueをつけてください。より長い時間がかかりますが、他の作業は正常に動けます。

最後に

速度を改善するために、色々な方法があります。使おうとする前に、他の方法を試してください。
indexを使うのはデメリットがありますので、リスクをちゃんと理解した上で、indexを利用しましょう。