現在、Ruby 製 worker を作成することを業務で行っているのですが、継続的にメモリ使用量が増加する現象に遭遇してました。 (グラフの値が下がったタイミングが数回ありますが、それぞれでデプロイが行われてリセットされただけ)
ObjectSpace.allocation_sourcefile
や ObjectSpace.allocation_sourceline
を利用してどのファイルのどの行でメモリ確保が多くなるのか調べれるようにしました。
以下のようなコードを worker に追加しました。
require 'objspace' class MemoryProfiler INTERVAL = 5 * 60 DISPLAY_COUNT = 20 def self.start Thread.start do loop do memory_usages = {} ObjectSpace.trace_object_allocations_start sleep INTERVAL # メモリ割当を表示する箇所でも Object の allocation が行われ、それが表示されてしまうため # 一旦 trace を止める ObjectSpace.trace_object_allocations_stop ObjectSpace.each_object.each do |obj| # Ruby 本体など C 言語で実装されている部分については、どのファイルで allocation が行われたか取得できない # またそれらで大量に allocation されるので、いったん除外する if ObjectSpace.allocation_sourcefile(obj) key = "#{ObjectSpace.allocation_sourcefile(obj)}:#{ObjectSpace.allocation_sourceline(obj)}" memory_usages[key] ||= Hash.new { |hash, key| hash[key] = 0 } memory_usages[key][:count] += 1 memory_usages[key][:bytes] += ObjectSpace.memsize_of(obj) end end memory_usages.sort_by { |_, v| -v[:bytes] }.take(DISPLAY_COUNT).each do |key, value| puts "#{key} retain #{value[:bytes]} bytes, #{value[:count]} allocations" end end end end end MemoryProfiler.start # 以下は動作検証用のコード @store = [] @hoge = [] loop do 100.times { |i| @store << i.to_s } 1000.times { |i| @hoge << i.to_s } sleep 1 * 60 end
これを動かすと、以下のように<ファイル名:行番号 メモリ確保したバイト数 オブジェクト生成回数> というフォーマットでログが出力されます。
/app/vendor/bundle/ruby/3.0.0/gems/timeout-0.3.2/lib/timeout.rb:101 retain 1049136 bytes, 1 allocations /app/vendor/bundle/ruby/3.0.0/gems/json-2.6.3/lib/json/common.rb:216 retain 909308 bytes, 2159 allocations /app/vendor/bundle/ruby/3.0.0/gems/rdkafka-0.10.0/lib/rdkafka/consumer.rb:424 retain 240414 bytes, 45 allocations /app/vendor/bundle/ruby/3.0.0/gems/activesupport-7.0.4.3/lib/active_support/code_generator.rb:15 retain 123523 bytes, 2298 allocations /app/vendor/bundle/ruby/3.0.0/gems/graphql-2.0.21/lib/graphql/schema/loader.rb:102 retain 76112 bytes, 67 allocations /app/vendor/bundle/ruby/3.0.0/gems/graphql-2.0.21/lib/graphql/schema/introspection_system.rb:132 retain 48912 bytes, 36 allocations :
しばらく動かしたまま放置しておくと
/app/vendor/bundle/ruby/3.0.0/gems/graphql-2.0.21/lib/graphql/schema/loader.rb:102 retain 536192 bytes, 472 allocations
でどんどんメモリ確保したバイト数が増えていくことがわかりました。
この問題は使用しているライブラリである graphql-ruby の v2.0.24 でPerformance: Reduce memory usage when adding types to a schema #4533 とあり、どうやら修正されていそうだということだった。
graphql-ruby を v2.0.24 にバージョンアップしメモリ使用量は落ち着いた感じになりました。
追記 (2023/08/06)
笹田さんが作っている GitHub - ko1/allocation_tracer: Add ObjectSpace::AllocationTracer module. を使うと、さらに有益な情報が取得できそう。