現象
ISP別のアクセス数を調べるために、td-agentにGeoIP Organizationを組み込んで当該IPアドレスのISPをログに落とすようにしたところ、td-agentプロセスがメモリリークする現象に遭遇した。
GeoIP Cityのみを使用していたときは問題なかった。
メモリリークを確認するために、ローカル環境でGeoIP::Organization#look_upを100万回ループしたときのメモリ使用量を10万回毎に見てみた。
require 'geoip'
db = GeoIP::Organization.new('GeoIPOrg.dat')
for i in 1..1000000 do
db.look_up('8.8.8.8')
puts `ps -o rss= -p #{Process.pid}`.to_i if i % 100000 == 0
end
結果
% ruby check_memoryleak.rb
44868
48228
49576
51300
53196
56084
57004
58308
59872
61080
回数が増えるにつれてメモリ使用量が増え続けている、つまりメモリリークしていることが分かる。
原因
ソースを読んでみると、geoip-cからGeoIP_name_by_addr()というGeoIPライブラリの関数を呼び出す箇所がある。
この関数はIPアドレスからISPもしくは組織名の文字列を返すもので、この文字列のメモリ領域はライブラリ内部でmallocして確保している。
このため、コール元(geoip-c側)でこの領域を解放する必要があるが、それが漏れているためメモリリークしていた。
対処法
Pull Requestを送って修正してもらったので、そのうちgeoip-c 0.9.2が出ると思うが、手っ取り早く対処するには以下のように修正すればよい。
- geoip-cのソースがあるディレクトリに移動する
% cd `find ~/.rbenv -name 'geoip-c-0.9.1' -type d`
% cd ext/geoip
- free()を追加
@@ -126,6 +126,7 @@
VALUE result = rb_hash_new();
if(value) {
rb_hash_sset(result, key, encode_to_utf8_and_return_rb_str(value));
+ free(value);
return result;
} else {
return Qnil;
- リコンパイル
% make clean
% make install
ここで、再度GeoIP::Organization#look_upを100万回ループしたときのメモリ使用量経過を見てみる。
% ruby check_memoryleak.rb
42140
43076
43076
44836
45708
45708
45720
45868
45868
45868
45868バイト以降は増えていないのでメモリリークが解消されたことが確認できた。
以上