In Part 1, we learned how to spot bottlenecks using Rails logs.
Now let’s go deeper โ using profiling tools, custom logging, and real-world fixes (including our Flipper case).
๐งฐ Profiling Tools for Rails Developers
1. Rack Mini Profiler ๐
Rack Mini Profiler is the go-to gem for spotting slow DB queries and views.
Add it to your Gemfile (development & staging only):
group :development do
gem 'rack-mini-profiler'
end
Then run:
bundle install
When you load a page, youโll see a little timing panel in the top-left corner:
- Total time per request.
- SQL queries count & time.
- Which queries are repeated.
- View rendering breakdown.
This makes N+1 queries immediately visible.
2. Bullet ๐ซ (for N+1 Queries)
Add to Gemfile:
group :development do
gem 'bullet'
end
Config in config/environments/development.rb:
config.after_initialize do
Bullet.enable = true
Bullet.alert = true
Bullet.bullet_logger = true
Bullet.rails_logger = true
end
Now, if you forget to eager load (includes), Bullet will warn:
N+1 query detected: Review => Product
Add to your query: .includes(:product)
3. Custom Logging for SQL Queries ๐
Sometimes you need to trace where in your code a slow query is triggered.
Rails lets you hook into ActiveRecord logging:
# config/initializers/query_tracer.rb
ActiveSupport::Notifications.subscribe("sql.active_record") do |_, start, finish, _, payload|
duration = (finish - start) * 1000
if duration > 100 # log queries > 100ms
Rails.logger.warn "SLOW QUERY (#{duration.round(1)}ms): #{payload[:sql]}"
Rails.logger.warn caller.select { |line| line.include?(Rails.root.to_s) }.first(5).join("\n")
end
end
This logs:
- The query.
- Execution time.
- Stack trace (where in Rails code it was triggered).
โก Real Example: Fixing Flipper Performance
In Part 1, we saw Flipper queries running 38 times per page:
SELECT "flipper_features"."key" AS feature_key,
"flipper_gates"."key",
"flipper_gates"."value"
FROM "flipper_features"
LEFT OUTER JOIN "flipper_gates"
ON "flipper_features"."key" = "flipper_gates"."feature_key"
Problem ๐ฅ
Each Flipper.enabled?(:feature_name, user) call hit the DB.
With dozens of flags per request โ repeated queries โ 6s page loads.
Solution โ : Redis Caching
Flipper supports caching with Redis.
# config/initializers/flipper.rb
require 'flipper/adapters/active_record'
require 'flipper/adapters/redis'
require 'flipper/adapters/operation_logger'
flipper_db_adapter = Flipper::Adapters::ActiveRecord.new
flipper_redis_adapter = Flipper::Adapters::Redis.new(Redis.new)
# wrap with memory cache to avoid repeat queries
flipper_caching_adapter = Flipper::Adapters::Cache.new(
flipper_db_adapter,
cache: flipper_redis_adapter,
expires_in: 5.minutes
)
Flipper.configure do |config|
config.default = Flipper.new(flipper_caching_adapter)
end
Now:
- First request โ fetches from DB, writes to Redis.
- Next requests โ served from Redis/memory.
- Page load dropped from 6s โ <500ms ๐.
Extra: Inspecting Redis Keys ๐
Check what’s being cached:
Rails.cache.redis.keys("*flipper*")
Or, if using connection pooling:
Rails.cache.redis.with { |r| r.keys("*flipper*") }
๐๏ธ Other Common Fixes
1. Add Missing Indexes
If you see slow queries with filters:
SELECT * FROM orders WHERE user_id = 123
Fix:
rails generate migration add_index_to_orders_user_id
rails db:migrate
2. Eager Load Associations
Instead of:
@orders = current_user.orders
@orders.each do |order|
puts order.user.email
end
Fix N+1:
@orders = current_user.orders.includes(:user)
3. Memoization / Request Caching
If you call the same method multiple times per request:
def expensive_query
User.where(active: true).to_a
end
Fix:
def expensive_query
@expensive_query ||= User.where(active: true).to_a
end
๐ Summary of Part 2
In this part, we covered:
- Using tools: Rack Mini Profiler, Bullet, and custom logging.
- A real-world Flipper caching fix (DB โ Redis).
- Other fixes: indexes, eager loading, memoization.
Together, these tools + fixes turn performance debugging from guesswork into a repeatable workflow.
๐ In Part 3, we’ll go even deeper into advanced techniques:
EXPLAIN ANALYZEin Postgres.- Profiling memory allocations with
stackprof. - Using
SkylightorDatadogin production.
One thought on “๐ ๏ธ Classic Performance Debugging Problems in Rails Apps โ Part 2: Tools & Fixes”