Stop using `Repo.preload` in loops - use `preload` in queries

almirsarajcic

almirsarajcic

2 days ago

0 comments

Calling Repo.preload/2 inside Enum.map or comprehensions creates N+1 queries - one query to fetch the parent records, then one additional query for each parent to load associations. Use Ecto’s query preload to load all associations in a single database round-trip.

# ❌ N+1 QUERIES - Database hit for EACH user (1 + N queries)
users = Repo.all(User)

Enum.map(users, fn user ->
  user_with_posts = Repo.preload(user, :posts)  # Query for every iteration!
  %{name: user_with_posts.name, post_count: length(user_with_posts.posts)}
end)

# ✅ SINGLE QUERY - Preload in the query loads all at once (2 queries total)
import Ecto.Query

users =
  User
  |> preload(:posts)
  |> Repo.all()

Enum.map(users, fn user ->
  %{name: user.name, post_count: length(user.posts)}
end)

Why this matters

Performance impact: With 100 users, the ❌ version executes 101 queries (1 for users + 100 for posts). The ✅ version executes 2 queries (1 for users + 1 for all posts).

Database load: N+1 queries create network round-trip overhead and database connection pressure under load.

When to use each approach

Query preload (in the query pipeline):

  • Loading associations for multiple records
  • Building dashboards, lists, or index pages
  • Any scenario where you’re working with collections

Repo.preload/2 (on individual structs):

  • Loading associations for a single record after fetching
  • Conditionally loading based on runtime logic
  • Adding associations to structs you already have
# Query preload - for collections
posts = Post |> preload([:author, :comments]) |> Repo.all()

# Repo.preload - for single records or conditional loading
post = Repo.get!(Post, id)
post = if include_author?, do: Repo.preload(post, :author), else: post

Catching N+1 queries in development

Add a query logger to config/dev.exs to spot N+1 patterns:

config :my_app, MyApp.Repo,
  log: :debug  # Shows all queries in the console

Watch for repeated similar queries - that’s your N+1 indicator.

Links

Comments (0)

Sign in with GitHub to join the discussion