Claude Code Plugins

Community-maintained marketplace

Feedback

ecto-query-analysis

@layeddie/ai-rules
0
0

Analyzes Ecto queries for N+1 problems, missing preloads, and performance issues.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name ecto-query-analysis
description Analyzes Ecto queries for N+1 problems, missing preloads, and performance issues.

Ecto Query Analysis Skill

Use this skill to analyze Ecto queries for performance issues and optimization opportunities.

When to Use

  • Reviewing Ecto query code
  • Investigating slow database queries
  • Optimizing database access patterns
  • Designing database schemas for performance

Common Issues

N+1 Query Problem

Symptom: Multiple database queries executed in a loop to fetch associated data.

Example:

# ❌ Bad - N+1 query problem
def get_users_with_posts do
  users = Repo.all(User)
  Enum.map(users, fn user ->
    posts = Repo.all(from p in Post, where: p.user_id == ^user.id)
    %{user: user, posts: posts}
  end)
end

# ✅ Good - Preload associations
def get_users_with_posts do
  User
  |> preload([:posts])
  |> Repo.all()
end

Missing Indexes

Symptom: Frequent queries on non-indexed columns are slow.

Example:

# ❌ Bad - No index on frequently queried column
# Query: WHERE email = '...' on large table
# Result: Slow sequential scan

# ✅ Good - Add index
# CREATE INDEX users_email_idx ON users(email)
# Query becomes fast index scan

Large Result Sets

Symptom: Loading all records into memory unnecessarily.

Example:

# ❌ Bad - Loading all records
def list_users, do: Repo.all(User)

# ✅ Good - Pagination
def list_users(page, per_page \\ 20) do
  User
  |> limit(^per_page)
  |> offset((page - 1) * ^per_page)
  |> Repo.all()
end

Optimization Strategies

Preloading

Associations: Always preload associations to prevent N+1 queries.

# Single association
User |> preload([:posts]) |> Repo.one()

# Multiple associations
User |> preload([:posts, :profile, :settings]) |> Repo.one()

# Nested associations
User |> preload([profile: [:avatar, [:background]]) |> Repo.one()

Selective Preloading

Only Load Needed Fields:

# Instead of preload(:posts) which loads all fields
User
|> Ash.Query.for_read()
|> Ash.Query.load([:posts, published_posts: [:author]])
|> Ash.Query.filter(posts[:published] == true)
|> Ash.read!()

Query Optimization

Use Ash Aggregates:

# Instead of loading all posts then counting
def count_published_posts(user_id) do
  Post
  |> Ash.Query.aggregate([:count], :first)
  |> Ash.Query.filter(author_id == ^user_id)
  |> Ash.Query.filter(status == :published)
  |> Ash.read_one!()
end

Use Window Functions:

# Calculate stats efficiently
def get_user_stats(user_id) do
  stats = User
  |> Ash.Query.aggregate([:count, :max_age], :first)
  |> Ash.Query.filter(id == ^user_id)
  |> Ash.read_one!()
  stats
end

Indexing Strategy

Composite Index: Index multiple columns often queried together.

# For queries filtering by user_id and status
CREATE INDEX posts_user_id_status_idx ON posts(user_id, status)

Partial Index: Index on prefix for range queries.

# For queries filtering by email LIKE 'user%'
CREATE INDEX users_email_prefix_idx ON users(email text_pattern_ops)

Commands to Run

# Enable query logging
# In config/dev.exs:
config :my_app, MyApp.Repo,
  loggers: [{Ecto.LogEntry, :log, :info}],
  log_sql_queries: true

# Analyze query plans
EXPLAIN ANALYZE SELECT * FROM users WHERE email = 'test@example.com';

# Check for missing indexes
# In IEx:
Ecto.Adapters.SQL.explain(MyApp.Repo, "EXPLAIN SELECT * FROM users")

Best Practices

Do

  • Always preload associations
  • Use selective preloading
  • Use aggregates for efficient calculations
  • Add indexes on frequently queried columns
  • Use pagination for large result sets
  • Filter at database level, not in Elixir

Don't

  • Enumerate over associations (N+1 problem)
  • Load entire result sets into memory
  • Use SELECT * when you only need specific columns
  • Ignore query performance warnings
  • Skip adding indexes on slow queries

Tools

  • mgrep: Search for N+1 patterns: mgrep "N+1 query problems in codebase"
  • Serena: Analyze codebase for query optimization opportunities
  • Credo: Check for code smells in database access code

Use this skill to identify and fix Ecto performance issues.