How can we force Rails to reload_routes on multiple servers/instances?
We have a multi-tenant platform in Google App-Engine running on 5+ instances and we want all
We finally found a solution that works pretty well and is also not affecting performance too much. We use the fact that Threads in production are keeping states across requests. So we decided to create a middleware that checks the latest timestamp of a routes change and in case the timestamp is not the same as the one saved in Thread.current
we force a Frontend::Application.reload_routes!
config/production.rb
Frontend::Application.configure do
...
config.middleware.use RoutesReloader
...
end
app/middleware/routes_reloader.rb
class RoutesReloader
SKIPPED_PATHS = ['/assets/', '/admin/']
def initialize(app)
@app = app
end
def call(env)
if reload_required?(env)
timestamp = Rails.cache.read(:routes_changed_timestamp)
if Thread.current[:routes_changed_timestamp] != timestamp
Frontend::Application.reload_routes!
Thread.current[:routes_changed_timestamp] = timestamp
end
end
@app.call(env)
end
private
def reload_required?(env)
SKIPPED_PATHS.none? { |word| env['PATH_INFO'].include?(word) }
end
end
app/model/routes.rb
class Routes < ActiveRecord::Base
after_save :save_timestamp
private
def save_timestamp
ts = Time.zone.now.to_i
Rails.cache.write(:routes_changed_timestamp, ts, expires_in: 30.minutes)
end
end
Benefits:
Caveats:
But overall we didn't recognise any performance drops.
We have been struggling with this now for years and the above solution is the first that really helped us reloading routes on multiple threads.
Assuming you have no shared storage: You could write an action that reloads the route for that particular instance. When you trigger DynamicRoutes::reload, you would make a request to the other instances' reload action.
If you do have shared storage, write a before_action that reloads the routes whenever a specific file has been "touched" and touch that file if you want to have all instances reload the routes.