New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
question: Is it safe to use with unicorn-worker-killer? #92
Comments
Hello @kyontan I remember meeting you in Matsumoto :)
So you could do that, but it would need a bit more adjusting because this gems works by monkey patching Unicorn's However, Pitchfork do provide some additional callbacks that make this monkey patching unnecessary. See the You can simply check the memory usage or request count in it, and call after_request_complete do |_server, worker, _env|
if worker.requests_count > MAX_REQUESTS
exit
end
if too_much_memory?
exit
end
end You can then implement Hope this helps, I'll close as I answered, but feel free to respond if you need more information. |
Here's an example that more closely mimicks # Each worker will have its own copy of this data structure.
WorkerData = Data.define(:max_requests, :max_mem)
worker_data = nil
def worker_pss(pid)
data = File.read("/proc/#{pid}/smaps_rollup")
pss_line = data.lines.find { |line| line.start_with?("Pss:") }
pss_line.split(" ")[1].to_i * 1024
end
after_worker_ready do |server, worker|
max_requests = Random.rand(5_000..10_000)
max_mem = Random.rand((386 * (1024**2))..(768 * (1024**2)))
worker_data = WorkerData.new(max_requests: max_requests, max_mem: max_mem)
server.logger.info("worker=#{worker.nr} gen=#{worker.generation} ready, serving #{max_requests} requests, #{max_mem} bytes")
end
after_request_complete do |server, worker, _env|
if worker.requests_count > worker_data.max_requests
server.logger.info("worker=#{worker.nr} gen=#{worker.generation}) exit: request limit (#{worker_data.max_requests})")
exit # rubocop:disable Rails/Exit
end
if worker.requests_count % 16 == 0
pss_bytes = worker_pss(worker.pid)
if pss_bytes > worker_data.max_mem
server.logger.info("worker=#{worker.nr} gen=#{worker.generation}) exit: memory limit (#{pss_bytes} bytes > #{worker_data.max_mem} bytes), after #{worker.requests_count} requests")
exit # rubocop:disable Rails/Exit
end
end
end V2
# Each worker will have its own copy of this data structure
WorkerData = Data.define(:max_requests, :max_mem)
worker_data = nil
after_worker_ready do |server, worker|
max_requests = Random.rand(5_000..10_000)
max_mem = Random.rand((386 * (1024**2))..(768 * (1024**2)))
worker_data = WorkerData.new(max_requests: max_requests, max_mem: max_mem)
server.logger.info("worker=#{worker.nr} gen=#{worker.generation} ready, serving #{max_requests} requests, #{max_mem} bytes")
end
after_request_complete do |server, worker, _env|
if worker.requests_count > worker_data.max_requests
server.logger.info("worker=#{worker.nr} gen=#{worker.generation}) exit: request limit (#{worker_data.max_requests})")
exit
end
if worker.requests_count % 16 == 0
mem_info = Pitchfork::MemInfo.new(worker.pid)
if mem_info.pss > worker_data.max_mem
server.logger.info("worker=#{worker.nr} gen=#{worker.generation}) exit: memory limit (#{mem_info.pss} bytes > #{worker_data.max_mem} bytes)")
exit
end
end
end V1
after_worker_ready do |server, worker|
max_requests = Random.rand(5_000..10_000)
worker.instance_variable_set(:@_max_requests, max_requests)
max_mem = Random.rand((386*(1024**2))..(768*(1024**2)))
worker.instance_variable_set(:@_max_mem, max_mem)
server.logger.info("worker=#{worker.nr} gen=#{worker.generation} ready, serving #{max_requests} requests, #{max_mem} bytes")
end
after_request_complete do |server, worker, _env|
if worker.requests_count > worker.instance_variable_get(:@_max_requests)
server.logger.info("worker=#{worker.nr} gen=#{worker.generation}) exit: request limit (#{worker.instance_variable_get(:@_max_requests)})")
exit
end
if worker.requests_count % 16 == 0
mem_info = Pitchfork::MemInfo.new(worker.pid) # Copy this implementation
if mem_info.pss > worker.instance_variable_get(:@_max_mem) # pss over rss
server.logger.info("worker=#{worker.nr} #{worker.generation}) exit: memory limit (#{mem_info.pss} bytes > #{worker.instance_variable_get(:@_max_mem)} bytes)")
exit
end
end
end It's a bit ugly with the instance variables but since these aren't static I need to stash them somewhere. I looked around but didn't find some worker-specific date storage I could use. @casperisfine, is there some better way I can accomplish this? |
Honestly I'm not sure this randomization of the limits make sense. They probably did with Unicorn 5 and older, but in 6+ (or 6.1+?) and in Pitchfork, there is a distribution bias toward the lowest PID because of the API used, so you don't incurr the risk of all your workers restarting at the same time. The request limit also doesn't make that much sense with Pitchfork assuming you enabled reforking. If you didn't then yes, it may make sense.
There's none. You can use a weak map: WORKER_DATA = ObjectSpace::WeakKeyMap.new # Ruby 3.3+
WorkerData = Struct.new(:max_requests, :max_mem)
# ....
worker_info = (WORKER_DATA[worker] ||= WorkerData.new(...)) |
Oh, that's pretty nice with the I can't tell if the randomization is necessary, however for me it's safer to just go with it anyways since it's been that way for years already. It doesn't incur much cost to keep around, though the implementation without is no doubt simpler. I use |
What I'm observing with the |
Yeah, |
Hi, I'm new to the pitchfork, and hope this will work well and improve memory efficiency in our large Rails application.
Currently, we are using unicorn with kzk/unicorn-worker-killer, that is famous(?) middleware for Unicorn to provide following features to deal with memory-leak like issues:
Unicorn::WorkerKiller::Oom
Unicorn::WorkerKiller::MaxRequests
Some of our endpoints consumes much memory during request, and sometimes it causes OOM of whole process (and container), we currently utilizes these middlewares.
Now we are testing pitchfork, and I guess we should fork
unicorn-worker-killer
to support pitchfork also, but I'm not sure this middleware works or not by just replacingUnicorn
toPitchfork
in its source code.Have anyone tried this, or know some technical restricts on that?
Thanks!
The text was updated successfully, but these errors were encountered: