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