Rails 5中Middleware/Rack/ActionDispatch::Routing::RouteSet#call耗时过高问题解决
ActionDispatch::Routing::RouteSet#call Time in Rails 5 Hey there, let's break down why your routing layer is taking so long and how to optimize it based on your routes.rb setup.
Common Causes for High Routing Latency
Looking at your route configuration, a few key things stand out that could be driving up the RouteSet#call time:
- Route traversal order: Rails matches routes top-to-bottom. If your high-traffic API routes are buried behind low-traffic admin/tooling routes, every API request has to skip all those earlier routes first—adding unnecessary overhead.
- Overly broad catch-all route: Your
get "*path" => "home#index"at the end catches all unhandled GET requests. If you have lots of SPA frontend routes or 404s hitting this, Rails has to verify all preceding routes don't match before falling back here, which adds per-request latency. - Large number of generated routes:
devise_forcreates dozens of routes for authentication flows. If these are nested in scopes without constraints, Rails has to check each one during matching.
Step-by-Step Fixes
1. Reorder Routes for High-Traffic Paths
Move your most frequently used routes (like the API v1 endpoints) to the top of the file. This way, high-volume requests don't waste time traversing admin, Sidekiq, or development-only routes first:
require 'sidekiq/web' mount ActionCable.server => '/cable' # High-traffic API routes first scope :api do scope :v1 do devise_for :users, controllers: { registrations: 'users/registrations', confirmations: 'users/confirmations', sessions: 'users/sessions', passwords: 'users/passwords' } get 'confirm_user_email', to: 'user_emails#confirm_email', as: :confirm_user_email get 'forgot_password', to: 'landing_page#forgot_password' post 'send_invitation', to: 'users#send_invitation', as: :send_invitation end end # Lower-traffic admin/tooling routes next devise_for :admins mount RailsAdmin::Engine => '/admin', as: 'rails_admin' unless Rails.env.production? mount LetterOpenerWeb::Engine, at: '/letter_opener' end mount Sidekiq::Web => '/sidekiq' # Catch-all route last (with constraints) get "*path" => "home#index"
2. Add Constraints to the Catch-All Route
If the catch-all is for your SPA frontend, narrow it down to only paths that shouldn't match your API/admin routes. This prevents Rails from checking the catch-all for every API/admin request:
get "*path", to: "home#index", constraints: ->(req) { !req.path.start_with?('/api', '/admin', '/sidekiq', '/cable') }
3. Add Constraints to API Routes
Filter API requests early by adding format constraints—this lets Rails skip the API route block entirely for non-JSON requests:
scope :api, constraints: { format: :json } do scope :v1 do # Your existing API routes here end end
4. Trim Unused Devise Routes
devise_for generates more routes than most apps need. Use the skip option to remove any unused routes and reduce the total number Rails has to check:
devise_for :users, skip: [:omniauth_callbacks, :unlock], # Example: skip unused routes controllers: { registrations: 'users/registrations', ... }
Run rake routes to see all generated routes and identify which ones you don't use.
5. Verify Routing Caching is Enabled
In production, Rails should automatically cache route matches, but double-check your config/environments/production.rb has:
config.action_controller.perform_caching = true
This ensures Rails doesn't recompile the route set on every request.
Debugging Further
If you still see high latency after these changes:
- Use
rack-mini-profilerto get granular timing data for route matching on individual requests. - Run
rake routes | wc -lto count total routes—if you have 300+, reducing the number will have a noticeable impact.
内容的提问来源于stack exchange,提问作者Shruthi R

