Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails関連

Rails 7.1: ActiveRecord::Relation#explainに:verboseや:analyzeオプションが追加(翻訳)

Rails 7.1: ActiveRecord::Relation#explainに:verboseや:analyzeオプションが追加(翻訳)

Rails 7.1でActiveRecord::Relation#explainメソッドが拡張されました。これによって、explainにオプションを指定すると詳細なクエリプラン分析を出力できるようになります。

🔗 EXPLAINを理解する

Rails 7.1の新しいオプションを詳しく見る前に、Active Recordのexplainメソッドの目的と利用法を軽くおさらいしておきましょう。explainメソッドは、データベースオプティマイザが選択したSQLクエリの実行プランを取得するのに使われます。これにより、データベースがクエリをどのような意図で実行しているか(操作の実行順、利用するインデックス、見積もりコストなど)についての洞察を得られます。

クエリプランを分析することで、パフォーマンス上のボトルネックの特定、データベーススキーマ設計の最適化、微調整による効率の向上が行なえます。しかしRails 7.1より前は、explainメソッドで出力できる詳細度に限りがありました。

🔗 クエリプランの詳細分析を取得するオプション

Rails 7.1のexplainメソッドにオプションを渡せるようになったことで、explainの出力をカスタマイズしてより詳細なクエリプラン分析を得ることが可能になります。
生SQLでも同様のオプションが利用可能ですが、利用するデータベースによってオプションが異なっている点が重要です。本記事ではPostgreSQLの例に特化することにします。それでは利用可能なオプションをいくつか見ていきましょう。

🔗 :analyze

:analyzeオプションを指定すると、(単なるプランの出力ではなく)ステートメントが実際に実行されます。続いて、実際のランタイム統計情報(プランノードごとの総実行時間や実際に返された総行数など)が表示に追加されます。

Service.where('age > ?', 25).joins(:user).explain(:analyze)
EXPLAIN (ANALYZE) SELECT "services".* FROM "services" INNER JOIN "users" ON "users"."id" = "services"."user_id" WHERE (age > 25)
                          QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------------------------------------
 Hash Join  (cost=15.50..29.42 rows=103 width=232) (actual time=0.015..0.017 rows=0 loops=1)
   Hash Cond: (services.user_id = users.id)
   ->  Seq Scan on services  (cost=0.00..13.10 rows=310 width=232) (actual time=0.012..0.012 rows=0 loops=1)
   ->  Hash  (cost=14.12..14.12 rows=110 width=8) (never executed)
         ->  Seq Scan on users  (cost=0.00..14.12 rows=110 width=8) (never executed)
               Filter: (age > 25)
 Planning Time: 0.915 ms
 Execution Time: 0.472 ms

🔗 :verbose

verboseオプションは、実行プランごとのステップに関する追加情報など、より詳細な出力を提供します。追加される情報には、統計情報やコスト見積もりなどの関連する詳細情報が含まれます。

Service.where('age > ?', 25).joins(:user).explain(:verbose)
EXPLAIN (VERBOSE) SELECT "services".* FROM "services" INNER JOIN "users" ON "users"."id" = "services"."user_id" WHERE (age > 25)
                          QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------------------------------------
 Hash Join  (cost=15.50..29.42 rows=103 width=232)
   Output: services.id, services.user_id, services.provider, services.uid, services.access_token, services.access_token_secret, services.refresh_token, services.expires_at, services.auth, services.created_at, services.updated_at
   Inner Unique: true
   Hash Cond: (services.user_id = users.id)
   ->  Seq Scan on public.services  (cost=0.00..13.10 rows=310 width=232)
         Output: services.id, services.user_id, services.provider, services.uid, services.access_token, services.access_token_secret, services.refresh_token, services.expires_at, services.auth, services.created_at, services.updated_at
   ->  Hash  (cost=14.12..14.12 rows=110 width=8)
         Output: users.id
         ->  Seq Scan on public.users  (cost=0.00..14.12 rows=110 width=8)
               Output: users.id
               Filter: (users.age > 25)
(11 rows)

PostgreSQLのEXPLAINについて詳しくは以下の公式ドキュメントを参照してください。

参考: PostgreSQL 15ドキュメント: EXPLAIN

詳しくは#47043を参照してください。

関連記事

Rails 7: PostgreSQLの外部キー制約にdeferrableを指定可能になった(翻訳)


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。