0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

第24章|今さら学ぶ「パフォーマンス」

0
Last updated at Posted at 2026-02-27

第24章|今さら学ぶ「パフォーマンス」

📚 シリーズ目次はこちら → 「今さら学ぶ」シリーズ — はじめに
🗺️ KnowledgeNoteの設計を確認 → 設計マップ

この章でわかること

  • 「遅い」ときの調査手順 — 症状の場所を特定してから治療する
  • DBクエリの最適化 — N+1問題の復習と explain の使い方
  • counter_cache — カウントを事前に保存しておく
  • キャッシング — よく出る料理は作り置きする
  • フラグメントキャッシュ / ロシアンドールキャッシュ
  • Solid Cache — Rails 8.0 標準のキャッシュストア

🏠 たとえ話で掴む「パフォーマンス」

パフォーマンス改善は 体の不調を治す のと同じです。

「なんか体がだるい」と言われても、医者はいきなり薬を出しません。まず「どこが痛い?」と聞いて、血液検査やレントゲンで原因を特定してから治療します。

「アプリが遅い」→ どこが遅い?

① DBが遅い?   → SQLの実行時間を確認(ログ / explain)
② コントローラ?→ 不要な処理をしていないか確認
③ ビューが遅い?→ Partialのループが多すぎないか
④ ネットワーク?→ アセットのサイズが大きすぎないか

最も多い原因はDBクエリ です。まずはログでSQLの実行時間を確認するのが定石です。


⚡ パフォーマンス最適化とは何か — 技術的な定義

パフォーマンス最適化 とは、アプリケーションのレスポンス時間やリソース消費量を改善するプロセスです。

Webアプリのレスポンスは、大きく4つの区間に分解できます。

リクエスト → [① ルーティング] → [② コントローラ処理] → [③ ビュー描画] → レスポンス
                                       ↓
                                  [④ DB問い合わせ]

Railsの開発ログでは、各区間の所要時間が表示されます。

Completed 200 OK in 850ms (Views: 120.5ms | ActiveRecord: 712.3ms)

この例だと、全体850msのうちDBに712ms(約84%)かかっています。 ボトルネック (一番時間がかかっている箇所)を特定してから対策するのが鉄則です。

パフォーマンス最適化で重要なのは、 推測ではなく計測に基づいて判断する ことです。「なんとなく遅そう」で最適化を始めると、効果の薄い場所に時間を使ってしまいます。


🔍 遅い箇所の特定

ログで確認

Completed 200 OK in 850ms (Views: 120.5ms | ActiveRecord: 712.3ms)
                                              ↑ ここが大きい = DB が遅い

ActiveRecord: 712.3ms なら、DBクエリに時間がかかっています。

rack-mini-profiler で可視化

# Gemfile
group :development do
  gem "rack-mini-profiler"
end

ページの左上にリクエストの所要時間が表示され、クリックするとSQLの詳細が見えます。

bullet でN+1を自動検出

# Gemfile
group :development do
  gem "bullet"
end
# config/environments/development.rb
config.after_initialize do
  Bullet.enable = true
  Bullet.alert = true            # ブラウザにアラート表示
  Bullet.bullet_logger = true    # ログに出力
  Bullet.rails_logger = true
end

bullet は、N+1問題が発生したときにブラウザ上にアラートを表示してくれるGemです。「どのアソシエーションで includes が必要か」まで教えてくれるので、N+1対策が格段に楽になります。


⚡ DBクエリの最適化

N+1問題の対策(復習)(→ 第17章で詳しく扱います)

# ❌ N+1
Article.all.each { |a| puts a.user.name }

# ✅ includes で一括取得
Article.includes(:user).all.each { |a| puts a.user.name }

explain でクエリの実行計画を見る

Article.where(status: :published).explain
# => EXPLAIN for:
#    SELECT "articles".* FROM "articles" WHERE "articles"."status" = 1
#    Seq Scan on articles  (cost=0.00..18.50 rows=500 width=200)
#    ↑ Seq Scan = 全件スキャン(インデックスが使われていない)

Seq Scan(Sequential Scan)が表示されたら、 インデックスを追加すべきサイン です。

# インデックスを追加
add_index :articles, :status
# → Seq Scan が Index Scan に変わり、検索が高速になる

select で必要なカラムだけ取得

# ❌ 全カラム取得(bodyが大きいと無駄)
Article.all

# ✅ 必要なカラムだけ取得
Article.select(:id, :title, :user_id, :created_at)

counter_cache — カウントを事前に保存する

記事の一覧画面で article.comments.count を呼ぶと、記事ごとに SELECT COUNT(*) FROM comments WHERE ... が実行されます。記事が100件あれば100回のSQLです。

counter_cache は、関連レコードの件数を 親テーブルのカラムに事前保存 しておく仕組みです。

# マイグレーション:articles テーブルにカウント用カラムを追加
add_column :articles, :comments_count, :integer, default: 0, null: false

# app/models/comment.rb
class Comment < ApplicationRecord
  belongs_to :article, counter_cache: true
  # ↑ counter_cache: true を付けると、
  #   commentが作成/削除されたとき、自動で articles.comments_count が更新される
end
# counter_cache なし(毎回SQL)
article.comments.count   # → SELECT COUNT(*) FROM comments ...

# counter_cache あり(カラムを読むだけ)
article.comments_count   # → SQLなし。articlesテーブルの値を返すだけ

一覧画面で件数を表示する場面では、counter_cache があるだけでクエリ数が劇的に減ります。

⚠️ ポリモーフィック関連では counter_cache は標準では使えません。 belongs_to :likeable, polymorphic: true のように関連先が動的に決まる場合、Railsはどのテーブルのカウントカラムを更新すべきか判断できないためです。ポリモーフィックなカウントが必要な場合は、after_create_commit / after_destroy_commit コールバックで手動管理するか、counter_culture gem の利用を検討します。


🍱 キャッシング — よく出る料理は作り置きする

キャッシュ は、「一度作った結果を保存しておき、次回以降はDBに問い合わせず保存した結果を返す」仕組みです。レストランでいう 作り置き です。

フラグメントキャッシュ

ビューの 一部分(フラグメント) をキャッシュします。

<%# app/views/articles/_article.html.erb %>
<% cache article do %>
  <%# ↑ この article の内容が変わるまでキャッシュされる %>
  <div class="p-4 border rounded mb-4">
    <h2><%= link_to article.title, article_path(article) %></h2>
    <p>by <%= article.user.name %> · <%= article.created_at.strftime("%Y/%m/%d") %></p>
    <span>❤️ <%= article.likes_count %></span>
  </div>
<% end %>

cache article と書くだけで、Railsは articleupdated_at をキーにしてキャッシュを管理します。記事が更新されると自動でキャッシュが無効化されます。

ロシアンドールキャッシュ

キャッシュの中にキャッシュを入れ子にするパターンです。

<%# 記事一覧のキャッシュ(外側) %>
<% cache @articles do %>
  <% @articles.each do |article| %>
    <%# 各記事のキャッシュ(内側) %>
    <% cache article do %>
      <%= render article %>
    <% end %>
  <% end %>
<% end %>

1件の記事が更新されても、 その記事のキャッシュだけが再生成 され、他の記事はキャッシュが効いたままです。

Solid Cache(Rails 8.0 標準)

Rails 8.0 では、キャッシュの保存先として Solid Cache がデフォルトです。従来のRedisの代わりに、 DBにキャッシュを保存 します(開発環境ではSQLite、本番環境ではPostgreSQL等のDBを使用)。

# config/environments/production.rb
config.cache_store = :solid_cache_store
# → Redis不要!DBだけでキャッシュが動く
従来(Redis) Solid Cache(Rails 8.0)
追加ミドルウェア 必要(Redis サーバ) 不要(DBに保存)
運用コスト Redisの管理が必要 DB だけで完結
パフォーマンス 高速 十分に高速(SSD前提)

🛠️ KnowledgeNoteでの具体例

# app/controllers/articles_controller.rb
def index
  @articles = Article.published
                     .recent
                     .includes(:user, :tags)  # N+1対策
                     .select(:id, :title, :user_id, :status, :created_at, :likes_count)
                     .page(params[:page])
                     .per(20)
end
<%# app/views/articles/index.html.erb %>
<% @articles.each do |article| %>
  <% cache article do %>
    <%= render "article", article: article %>
  <% end %>
<% end %>

<%= paginate @articles %>

💼 面接で聞かれたら?

Q:アプリが遅いとき、どう調査しますか?

「まず開発ログでレスポンスの所要時間を確認し、DBクエリ・ビュー描画・コントローラのどこに時間がかかっているか切り分けます。最も多い原因はDBクエリで、N+1問題やインデックス不足が該当します。N+1はincludesで解決し、インデックス不足はexplainで確認して追加します。一覧画面での件数表示にはcounter_cacheが有効です。ビューが遅い場合はフラグメントキャッシュを検討します。」

深掘りされたら:

  • 「キャッシュの無効化はどうする?」→ Railsのフラグメントキャッシュはモデルの updated_at をキーにしているので、レコードが更新されると自動で無効化される。
  • 「Solid Cache とは?」→ Rails 8.0標準のキャッシュストア。Redisの代わりにDBにキャッシュを保存するため、追加のミドルウェアが不要。
  • 「counter_cache とは?」→ 関連レコードの件数を親テーブルのカラムに事前保存する仕組み。comments.count のように毎回SQLでCOUNTするのを避けられる。ただし通常の has_many/belongs_to にのみ対応しており、ポリモーフィック関連には使えない。

🔗 もっと深く知りたい人へ(1次情報リンク)


まとめ

  • ✅ 「遅い」ときはまず どこが遅いか をログで特定する。推測ではなく計測に基づいて判断する
  • ✅ 最も多い原因はDBクエリ。N+1問題とインデックス不足を疑う
  • explain でクエリの実行計画を確認。Seq Scan はインデックス追加のサイン
  • ✅ counter_cache で件数表示のクエリを削減。一覧画面の高速化に効果大
  • ✅ フラグメントキャッシュでビュー描画を高速化。updated_at で自動無効化
  • ✅ Rails 8.0 の Solid Cache はRedis不要。DBだけでキャッシュが動く

📚 シリーズ目次:「今さら学ぶ」シリーズ — はじめに

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?