ActiveRecord使用時のインデックス張り忘れとその対策


Rails AntiPatternsを読んでいたら、ActiveRecord使用時のインデックス張り忘れについてチートシート的にまとまっていたのでメモ。

Rails AntiPatternsとは

Railsの開発におけるノウハウをアンチパターン/解決策という構成でまとめた書籍。
内容は少し古めだが基本は押さえられている。

残念ながら日本語に翻訳されていないが、下記のサイトでpdfの無料ダウンロードが可能。

Rails AntiPatterns - Free Download eBook - pdf

プライマリキー

Railsの規約に沿っていれば id としてインデックスまで自動的に設定されるため考慮する必要なし。

外部キー

has_many/belongs_to の子側に設定された外部キーなど。
たとえば、下の例では comments.post_id にインデックスが必要。

class Post < ApplicationRecord
  has_many :comments
end

class Comment < ApplicationRecord
  belongs_to :post
end

インデックスの必要性が事前にわかっていればモデルのジェネレート時に設定してしまうのがよい。

$ rails g model Comment post_id:integer:index

ポリモーフィック関連で使われるカラム

下記の例の場合、post.taggings で、SELECT "taggings".* FROM "taggings" WHERE "taggings"."taggable_id" = ? AND "taggings"."taggable_type" = ? というクエリが走るので、taggings.taggable_idtaggings.taggable_type の複合インデックスが必要になる。

class Tag < ApplicationRecord
  has_many :taggings
end

class Tagging < ApplicationRecord
  belongs_to :tag
  belongs_to :taggable, :polymorphic => true
end

class Post < ApplicationRecord
  has_many :taggings, :as => :taggable
end

ユニーク制約の対象カラム

下記の場合、 users.email にユニークインデックスが必要。

class User < ActiveRecord::Base
   validates :email, :unique => true
end

ジェネレート時に設定してもよい。

$ rails g model DummyUser email:string:unique

STI(単一テーブル継承)で使っているカラム

下の例だと UpdateNotification でSELECTした際にWHERE句に "notifications"."type" IN ('UpdateNotification') がついてくるため、 notifications.type にインデックスが必要。

class Notification < ApplicationRecord
end

class UpdateNotification < Notification
end

to_param で使われているカラム

/users/1 の代わりに /users/Jake といったアクセスを可能にする to_param
たとえばユーザ名でアクセスさせる場合は users.name にインデックスが必要になる。

to_param - リファレンス - - Railsドキュメント

WHERE句で使われるカラム

  • ステータス管理のカラム等
    • user_id など別のカラムと組み合わせて使うことが多いため、複合インデックスもチェック
  • booleanのインデックス
    • 値がtrue/falseのどちらかに極端に偏っていない限り不要
  • datetimeのカラム(created_at など)
    • 通常orderで使われることが多いが、where でも使われているのであればインデックスの作成を検討する

その他

注意点

  • 大量のデータを保持して稼働している本番環境でインデックスを張らない
  • むやみやたらにインデックスを張らない。使われないインデックスはINSERT/UPDATEのコストを増加させるだけ

ボトルネックの発見