Initial commit

This commit is contained in:
Zhongwei Li
2025-11-30 08:42:29 +08:00
commit bece5178ef
31 changed files with 9410 additions and 0 deletions

View File

@@ -0,0 +1,207 @@
---
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.**