DBパフォーマンス改善：既存のカラムにindexを設定する方法【Ruby on Rails】

## はじめに
パフォーマンス改善の観点で既存のカラムにindexを設定する方法について書いてみようと思います。　新たにカラムを追加する方法ではなく、元々あるカラムにindexを設定する方法については該当の記事を見つけることができなかったので、書いてみようと思います。 設定自体は簡単ですが、この記事ではなぜindexを設定する必要があるのか、という点について重点をおいて初学者向けに解説していきたいと思います。

## 開発環境
Rails 6.1.4.3
ruby 3.0.3
psql 14.2

## テーブル同士の関係について
#### ER図
indexを設定するlikesテーブルと関係するUsersテーブル、Postsテーブルについて図で紹介します。　自分のPFで書いたER図をそのまま転用しているので、見にくいですがご了承下さい。
※content：投稿内容

![スクリーンショット 2022-05-23 23.14.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2316171/87333c2d-383f-e18f-b63f-6d528aac1c45.png)

テーブル同士の関係を箇条書きで説明すると
- 1ユーザーは複数の投稿が出来るので、`User(1)`に対して`Post(n)`の関係
- 1ユーザーは複数のイイね！が出来るので、`User(1)`に対して`Like(n)`の関係
- 1つの投稿に複数のイイね！を紐付けたいので、`Post(1)`に対して`Like(n)`の関係
- 1つの投稿に対して1ユーザーが最大で1つのイイね！をすることが出来る関係
要するにTwitterみたいにイイ感じにしてくれる関係になっています。笑

#### 実装イメージ
そして、実際の投稿機能が次のイメージになります。
Twitterを利用している方であればイメージしやすいかと思いますが、あるユーザーが投稿した内容に「イイね！」が出来るシンプルな機能をイメージして頂ければER図も理解しやすいかと思います。

![スクリーンショット 2022-05-23 22.55.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2316171/0a8e9004-8970-5ac9-d785-f5b41f7c7efa.png)

#### データベース
次にDBの情報がどうなっているのか、確認してみます。
- Postsテーブル
`id(Post_id)`が`19`の投稿内容ということが分ります。
![スクリーンショット 2022-05-23 23.55.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2316171/8d28ef05-77eb-6aed-eb3e-9365ddc7469a.png)

- Likesテーブル
`Post_id`が`19`と紐づく`user_id`が`11`、`6`、`4`ということが分ります。
要するに1つの投稿に3人のユーザーの「イイね！」が紐づいていることが分ります。
![スクリーンショット 2022-05-23 23.54.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2316171/bd5e23e2-d0b3-57bf-4561-b9743b38a6d1.png)

- Usersテーブル
 ここでは特に重要ではないので割愛します。


## どういった懸念があるのか
本題に入る前に、なぜパフォーマンス改善をする必要があるのか、その理由について触れたいと思います。　まず、Likesテーブルについてどのようにレコードが増えていくか考えてみると、当たり前ですが投稿の数だけ`post_id`と`user_id`の組み合わせは無数に増えていきます。　　そして、コメントと比較すると「イイね！」は気軽にアクションが起こせることから、レコード数が圧倒的に多くなることが想定されます。　その場合、投稿に対する「イイね！」を抽出しようとした場合、その大量のレコードを上から順番に参照して該当の情報を探すと処理に時間が掛かることがイメージ出来るかと思います。

## どうすれば欲しい情報に早く辿り着けるのか
検索する際に欲しい情報のキーワードに一致する情報から優先的に検索することが出来れば目的の情報に早く辿りつけるかと思います。　例えて言えば、本屋に行った時に`データーベース`に関する本を探したいと思った時に、本のジャンルやアイウエオ順が規則正しく並んでいると、お目当ての本に辿り着くことが容易ということがイメージしやすいかと思います。　その逆だと、人なら探すことさえ諦めるかもしれません。
ということで、探したい情報を規則的に並び換えて、欲しい情報に早く辿りつけるようにする為の手法として今回indexをカラムに設定しました。

## indexの効果が期待できる条件とは
ある程度多くのデータを格納するテーブルに格納される値がそれぞれ異なるようなカラムの中で、検索がよく行われるカラムで効果が出やすいようです。　適切に設定しないと、むしろ速度が遅くなってしまうケースもあるとようです。　実際のサービスではもっとシビアな話しだと思います。　私のように個人開発でユーザーがいない又は少ない場合はあまりパフォーマンス改善について意識する必要はないかも知れませんので、初学者向けの記事という趣旨から言えば、どういった場面で使うと効果が出るか概要を理解しておく程度で十分かも知れません。

## indexを設定すると、どうなるのか
- 設定無し：カラムを上から順にみて該当のデータを取得るので、レコードが多いと検索に時間が掛かる
- 設定有り：アルファベット順に並び換え、検索しやすいようにしてくれる
  Likesテーブルの`post_id`と`user_id`カラムの値はアルファベットではなく、数字ですがこの場合も数字ごとに並び変えてくれるようです。

## indexの設定
作業自体はマイグレーションファイルを作成し、下記のように記載するだけですが、マイグレーションファイルについて復習したい方は下記の記事を参考にすると良いかと思います。
参考：[【Rails】 マイグレーションファイルを徹底解説！](https://pikawaka.com/rails/migration)

indexについては、 [Railsドキュメント](https://railsdoc.com/page/add_index) を参考にしました。

```ruby:db/migrate/2022052XXXXXXX_add_user_and_post_references_to_likes.rb
class AddUserAndPostReferencesToLikes < ActiveRecord::Migration[6.1]
  def change
    add_index :likes, :user_id
    add_index :likes, :post_id
    add_foreign_key :likes, :users
    add_foreign_key :likes, :posts
  end
end
```

ちなみに、新たに`user_id` と `post_id` カラムを追加し、同時にindexを設定する場合は、下記のように書くことになるかと思います。 間違っていたら申し訳ないです。
```ruby:新たにカラムを追加する場合
class AddUserAndPostReferencesToLikes < ActiveRecord::Migration[6.1]
  def change
    add_reference :likes, :user, foreign_key: true
    add_reference :likes, :post, foreign_key: true
  end
end
```

そして、マイグレーションの実行
```:ターミナル
$ rails db:migrate
```

マイグレーション前後で`schema.rb`ファイルを比較してみると、"post_id"と"user_id"にindexが設定されていることが分かります。 そして、”foreign_key”も設定されていることも確認できました。
※foreign_keyとは、外部キー制約のカラム名を指定することが出来るオプションです。
参考：[【Rails】 アソシエーションを図解形式で徹底的に理解しよう！](https://pikawaka.com/rails/association)
```ruby:db/schema.rb
- ActiveRecord::Schema.define(version: 2022_05_0X_XXXXXX) do
+ ActiveRecord::Schema.define(version: 2022_05_2x_XXXXXX) do

  create_table "likes", force: :cascade do |t|
    t.integer "user_id"
    t.integer "post_id"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
+   t.index ["post_id"], name: "index_likes_on_post_id"
+   t.index ["user_id"], name: "index_likes_on_user_id"
  end

+ add_foreign_key "likes", "posts"
+ add_foreign_key "likes", "users"
```

最後までお読み頂きありがとうございました。
初学者の記事なので、間違っている箇所があればご指摘頂ければ幸いです。