Avoid parsing Rails controller params by using Metal
Need more metal!
I was looking at this controller today, which was using lots of memory:
class WebhooksController < ApplicationController
def create
if SomeModel.exists?(params[:model_id])
ProcessJSONinSidekiq.perform_later(params.to_json)
end
render json: {}
end
end
Most of the memory (and time) was coming from the fact that this was receiving a large amount of JSON, which gets converted to the params Hash (actually ActionController::Parameters) which we then convert back to JSON for a Sidekiq job.
We can do better.
First, I needed to move the SomeModel.exists? check into the job, which was pretty easy.
Next, I can send the raw JSON to the Sidekiq job using request.raw_post, rather than calling params.to_json :
class WebhooksController < ApplicationController
def create
ProcessJSONinSidekiq.perform_later(request.raw_post)
render json: {}
end
end
Rails will still automatically convert the JSON to the params Hash though, and that is still unnecessary for us.
I realized at this point we really should be using ActionController::API instead of ApplicationController because it is leaner and more efficient for API-related requests (no view rendering, etc.). That didn’t go far enough here though. I needed metal. 🤘🏻
ActionController::Metal is lower level than ActionController::API and removes pretty much everything. It does have a concept of params, but they aren’t loaded unless you use the method. I had to tweak my return a bit, but it was pretty simple. Now I never touch the JSON. I simply send it along.
class WebhooksController < ActionController::Metal
def create
ProcessJSONinSidekiq.perform_later(request.raw_post)
[200, { "Content-Type" => "application/json" }, "{}"]
end
end
Comments