Rails Performance Patterns Skill
Automatically detects performance issues and suggests optimizations.
What This Skill Does
Automatic Detection:
- •N+1 query problems (missing eager loading)
- •Missing database indexes on foreign keys
- •Inefficient query patterns
- •Large result sets without pagination
When It Activates:
- •Model files with associations modified
- •Controller actions that query models
- •Iteration over associations detected
Key Checks
1. N+1 Query Detection
Problem Pattern:
ruby
# app/controllers/posts_controller.rb
def index
@posts = Post.all # 1 query
@posts.each do |post|
puts post.author.name # N queries (one per post)
end
end
Skill Output:
code
⚠️ Performance: Potential N+1 query Location: app/controllers/posts_controller.rb:15 Issue: Accessing 'author' association in loop without eager loading Fix: Add includes to eager load: @posts = Post.includes(:author).all
Solution:
ruby
def index @posts = Post.includes(:author).all # 2 queries total end
2. Missing Indexes
Checks:
- •Foreign keys have indexes
- •Commonly queried columns indexed
- •Unique constraints have indexes
Example:
ruby
# db/migrate/xxx_create_posts.rb create_table :posts do |t| t.references :user # ✅ Auto-creates index t.string :slug # ❌ Missing index if queried often end
Skill Output:
code
⚠️ Performance: Missing index recommendation Location: app/models/post.rb:5 Issue: slug column used in where clauses without index Add migration: add_index :posts, :slug, unique: true
3. Pagination Missing
Problem:
ruby
def index @products = Product.all # ❌ Loads all 100k+ products end
Skill Output:
code
⚠️ Performance: Large result set without pagination Location: app/controllers/products_controller.rb:10 Issue: Loading all Product records (estimated 100k+ rows) Recommendation: Add pagination # Use kaminari or pagy @products = Product.page(params[:page]).per(20)
4. Counter Cache Opportunities
Pattern:
ruby
# Without counter cache @user.posts.count # Runs COUNT(*) query every time # With counter cache @user.posts_count # Reads from cached column
Skill Output:
code
ℹ️ Performance: Counter cache opportunity Location: app/views/users/show.html.erb:12 Pattern: Frequently accessing post count Add to migration: add_column :users, :posts_count, :integer, default: 0 Update association: belongs_to :user, counter_cache: true
Configuration
yaml
# .rails-performance.yml n1_detection: enabled: true severity: warning indexes: check_foreign_keys: true check_query_columns: true pagination: warn_threshold: 1000 require_for_large_tables: true counter_cache: suggest_threshold: 3 # Suggest if accessed 3+ times
Monitoring Integration
Add instrumentation:
ruby
# config/initializers/query_monitoring.rb
ActiveSupport::Notifications.subscribe('sql.active_record') do |*args|
event = ActiveSupport::Notifications::Event.new(*args)
if event.duration > 100 # Log slow queries
Rails.logger.warn("Slow query (#{event.duration}ms): #{event.payload[:sql]}")
end
end
Common Optimizations
Eager Loading:
ruby
# N+1
Post.all.each { |p| p.author.name }
# Fixed: includes (left join)
Post.includes(:author).each { |p| p.author.name }
# Fixed: preload (separate queries)
Post.preload(:author).each { |p| p.author.name }
# Fixed: eager_load (always joins)
Post.eager_load(:author).each { |p| p.author.name }
Select Specific Columns:
ruby
# Loads all columns Post.all # Loads only needed columns Post.select(:id, :title, :created_at)
Batch Processing:
ruby
# Loads all at once
Post.all.each { |p| process(p) }
# Loads in batches of 1000
Post.find_each(batch_size: 1000) { |p| process(p) }
References
- •Rails Performance Guide: https://guides.rubyonrails.org/performance_testing.html
- •Bullet Gem: https://github.com/flyerhzm/bullet
- •Pattern Library: /patterns/caching-patterns.md
This skill helps you build fast Rails applications from the start.