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_id と taggings.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のコストを増加させるだけ
ボトルネックの発見
- インデックスが必要なカラムを抽出
-
lol_dbaの
db:find_indexesなどを使う
-
lol_dbaの
- スロークエリーログを追う
- EXPLAINを読む
-
ActiveRecord::Relationのexplainを使う(User.all.explainのように使う)
-
- 各種プロファイラを使う