Introduction
Profiling tools reveal where your application spends time and memory. Without profiling, optimization is guesswork. Rails provides built-in instrumentation and integrates well with third-party profiling gems.
Key Concepts
- rack-mini-profiler: A gem that displays a timing badge on every page showing request duration, SQL queries, and memory usage.
- ActiveSupport::Notifications: Rails' built-in instrumentation system that fires events for SQL queries, controller actions, view rendering, and more.
- Benchmark: Ruby's standard library for measuring execution time of code blocks.
Real World Context
A slow page might feel like a view rendering problem, but rack-mini-profiler reveals that 80% of the time is spent in 47 SQL queries. Without profiling, you'd waste time optimizing the wrong thing.
Deep Dive
Rack Mini Profiler
ruby# Gemfile gem 'rack-mini-profiler' gem 'memory_profiler' # For memory profiling gem 'stackprof' # For CPU profiling
Shows a timing badge on every page with total request time, SQL queries and times, and memory allocation.
Benchmark Module
rubyrequire 'benchmark' Benchmark.bm do |x| x.report('includes:') { Post.includes(:author).to_a } x.report('preload:') { Post.preload(:author).to_a } end # In Rails console Benchmark.measure { User.all.to_a }
ActiveSupport::Notifications
ruby# config/initializers/instrumentation.rb ActiveSupport::Notifications.subscribe('sql.active_record') do |*args| event = ActiveSupport::Notifications::Event.new(*args) if event.duration > 100 Rails.logger.warn "Slow query (#{event.duration}ms): #{event.payload[:sql]}" end end ActiveSupport::Notifications.subscribe('process_action.action_controller') do |*args| event = ActiveSupport::Notifications::Event.new(*args) Rails.logger.info "Request: #{event.payload[:path]} took #{event.duration}ms" end
Custom Instrumentation
rubyclass OrderService def process(order) ActiveSupport::Notifications.instrument('process.order_service', order_id: order.id) do # Processing logic end end end
Common Pitfalls
- Profiling only in development — Development and production have vastly different data sizes and configurations. Profile with production-like data.
- Optimizing without measuring — Always measure before and after changes. A "optimization" that doesn't show improvement in profiling was wasted effort.
Best Practices
- Use rack-mini-profiler in development — It gives instant feedback on every page load, catching regressions as you develop.
- Subscribe to sql.active_record in staging — Log all queries over 100ms to identify slow queries before they hit production.
Summary
- rack-mini-profiler shows timing, SQL, and memory on every page.
- ActiveSupport::Notifications lets you subscribe to Rails internal events.
- Use Benchmark for comparing alternative implementations.
- Always measure before and after optimizing — don't guess.
Code Examples
ruby
# Log slow SQL queries automatically
ActiveSupport::Notifications.subscribe('sql.active_record') do |*args|
event = ActiveSupport::Notifications::Event.new(*args)
if event.duration > 100 # milliseconds
Rails.logger.warn(
"SLOW QUERY (#{event.duration.round(1)}ms): #{event.payload[:sql]}"
)
end
end