A hands-on Rails application for learning about N+1 query problems and their solutions in both ActiveRecord and GraphQL contexts.
Learn to identify, understand, and solve N+1 query problems using:
- Rails ActiveRecord:
includes
,preload
,eager_load
- GraphQL: Batch Loader pattern with
graphql-batch
- Detection Tools: Bullet gem for automatic N+1 detection
- Docker and Docker Compose
- Git
# Clone the repository
git clone git@github.com:mretzak/workshop.git
cd workshop
# Start everything with one command
./start-workshop.sh
That's it! The script will:
- π³ Build Docker containers
- ποΈ Set up PostgreSQL database
- πββοΈ Run migrations automatically
- π± Seed sample data
- π Start the Rails server
The application will be available at http://localhost:3000
# Manual Docker approach
docker compose up --build
# Database setup is automatic on first run!
For the quickest start and a zero-setup experience for workshop participants:
- Ensure Docker Desktop is running.
- Clone this repository.
- Open your terminal, navigate to the project directory.
- Run the startup script:
./start-workshop.sh
- Wait for the script to build the Docker containers and set up the database.
- Once ready, you can access:
- Rails API (N+1 demo):
http://localhost:3000/posts/n_plus_one
- Rails API (Optimized demo):
http://localhost:3000/posts/optimized
- GraphQL Endpoint (for programmatic access):
http://localhost:3000/graphql
- GraphiQL UI (for interactive GraphQL queries):
http://localhost:3000/graphiql
- Rails API (N+1 demo):
Once the Docker containers are up and the database is initialized (either manually or via the start-workshop.sh
script):
- Rails API (N+1 demo):
http://localhost:3000/posts/n_plus_one
- Rails API (Optimized demo):
http://localhost:3000/posts/optimized
- GraphQL Endpoint (for programmatic access):
http://localhost:3000/graphql
- GraphiQL UI (for interactive GraphQL queries):
http://localhost:3000/graphiql
- Bullet Gem Logs: Check
log/bullet.log
or your browser's console for N+1 notifications.
- Ruby 3.3.1
- PostgreSQL
- Bundler
# Install dependencies
bundle install
# Configure database (update config/database.yml if needed)
rails db:create db:migrate db:seed
# Start the server
rails server
The seed file creates:
- 20 users with realistic names and emails
- 60-100 blog posts with random content
- 200-400 comments on posts
- 10 tags with many-to-many relationships
# This endpoint demonstrates N+1 queries
curl http://localhost:3000/posts/n_plus_one
Check your Rails logs - you'll see:
SELECT "posts".* FROM "posts" -- 1 query
SELECT "users".* FROM "users" WHERE "users"."id" = 1 -- N queries
SELECT "users".* FROM "users" WHERE "users"."id" = 2 -- (one per post)
-- ... many more queries
# Different eager loading strategies
curl http://localhost:3000/posts/includes # Let Rails decide
curl http://localhost:3000/posts/preload # Separate queries
curl http://localhost:3000/posts/eager_load # LEFT OUTER JOIN
curl http://localhost:3000/posts/optimized # Optimized version
Access GraphQL playground at http://localhost:3000/graphiql
query {
posts {
title
user {
name
}
comments {
content
}
commentsCount
}
}
query {
optimizedPosts {
title
user {
name
}
comments {
content
}
commentsCount
}
}
Identify and fix N+1 queries in a Rails controller using appropriate eager loading.
Create a Batch Loader to batch-load user data in GraphQL resolvers.
app/
βββ controllers/
β βββ posts_controller.rb # N+1 examples & solutions
β βββ users_controller.rb # More N+1 examples
βββ graphql/
β βββ loaders/ # Batch Loader implementations
β βββ types/ # GraphQL types (problem & optimized)
βββ models/ # User, Post, Comment, Tag models
config/
βββ environments/
βββ development.rb # Bullet gem configuration
- Rails 7.1 - Web framework
- PostgreSQL - Database
- GraphQL-Ruby - GraphQL implementation
- GraphQL-Batch - Batch Loader pattern
- Bullet - N+1 query detection
- Faker - Sample data generation
The Bullet gem is configured to detect N+1 queries automatically:
- Console alerts
- Rails log warnings
- Browser notifications (in development)
Watch your Rails logs while making requests to see:
- SQL query counts
- Query execution times
- N+1 detection warnings
includes(:association)
- Smart loading (separate queries OR joins)preload(:association)
- Always use separate querieseager_load(:association)
- Always use LEFT OUTER JOIN
- Batch loading to reduce database queries
- Per-request caching
- Lazy evaluation for optimal performance
- Introduction (10 min): N+1 problem explanation
- Rails Examples (30 min): Live coding and challenges
- GraphQL Examples (35 min): Batch Loader implementation
- Discussion (15 min): Best practices and Q&A
# Reset database
docker compose exec web rails db:drop db:create db:migrate db:seed
# Or without Docker
rails db:reset
# Rebuild containers
docker compose down
docker compose up --build
This project is designed for educational purposes as part of the N+1 workshop.