Files
2025-11-30 08:42:29 +08:00

208 lines
4.4 KiB
Markdown
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
name: rails-performance-patterns
description: Detects N+1 queries, suggests eager loading, and recommends database indexes
auto_invoke: true
trigger_on: [file_modify]
file_patterns: ["**/models/**/*.rb", "**/controllers/**/*.rb"]
tags: [rails, performance, optimization, n+1, eager-loading, indexes]
priority: 2
version: 2.0
---
# 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:**
```
⚠️ 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:**
```
⚠️ 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:**
```
⚠️ 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:**
```
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.**