fluent-plugin-geoipというIPアドレスから国や州・県などの情報を取得してレコードを加工するFluentdのプラグインがあります。
以前はGeoIP Legacyにあるデータベースを使ってIPアドレスから情報を取得していましたが、GeoIP2がリリースされてしばらく経過したのでGeoIP2に対応した話を書きます。
geoip2_cの開発
経緯はSupport GeoLite2 format · Issue #39 · y-ken/fluent-plugin-geoipに書いてありますが、少し抜粋します。
GeoIPについて調べているとGeoIP2を見つけ、さらにGeoIP2に対応したfluent-plugin-filter-geoipを見つけました。 そこで、fluent-plugin-filter-geoipの内部を調べ、どのようにしてGeoIP2に対応させているかを確認したところmaxminddbというピュアRuby実装のライブラリを使っていました。 他にGeoIP2を利用できるライブラリがあるかどうかを調査したところいくつか既存の実装がありました。
-
geoip2_compat: geoip互換のAPIを持つlibmaxminddbのバインディング
-
maxminddb: ピュアRuby実装のGeoIP2対応のライブラリ
-
hive_geoip2: libmaxminddbのバインディング
-
maxmind_geoip2: libmaxminddbのバインディング
新たにfluent-plugin-geoipにGeoIP2対応を追加するにあたって、GeoIPを使用していたときと同等の性能を維持できるかどうかを確認するためにベンチマークをとりました。
ベンチマークによると、geoip2_compatであれば性能に問題はなさそうなことがわかりましたが、geoip2_compatだとGeoIP Legacyと同等のデータしか取得することができません。GeoIP2にはそれ以外のデータも多数追加されているので、できれば全ての機能を使えるようにしたいと考えていました。maxminddbとhive_geoip2はGeoIPよりも遅くなってしまうので使えません。maxmind_geoip2は速度的には問題なさそうでしたがAPIが独特で使い辛い感じでした。
それぞれの拡張ライブラリのコードを読んでみたところ、遅くなっていた原因は全ての属性値を取得していたことでした。速くするためには、必要な属性値のみ取得するようにすればよいはずです。 この仮説を検証するためにgeoip2_cを開発しました。
先程のベンチマークにgeoip2_cを追加したベンチマークによるとgeoip2_cが最速です。
Rehearsal ---------------------------------------------------------
geoip 0.140000 0.000000 0.140000 ( 0.147379)
geoip2_compat 0.110000 0.010000 0.120000 ( 0.108135)
maxminddb (pure ruby) 4.310000 0.000000 4.310000 ( 4.320897)
hive 0.320000 0.000000 0.320000 ( 0.321934)
maxmind_geoip2 1.240000 0.320000 1.560000 ( 1.561630)
geoip2_c 0.070000 0.000000 0.070000 ( 0.067715)
------------------------------------------------ total: 6.520000sec
user system total real
geoip 0.140000 0.000000 0.140000 ( 0.142973)
geoip2_compat 0.160000 0.000000 0.160000 ( 0.162996)
maxminddb (pure ruby) 4.650000 0.000000 4.650000 ( 4.654088)
hive 0.310000 0.000000 0.310000 ( 0.308363)
maxmind_geoip2 1.350000 0.430000 1.780000 ( 1.780049)
geoip2_c 0.080000 0.010000 0.090000 ( 0.078209)
bundle exec ruby bench.rb 13.26s user 0.83s system 99% cpu 14.134 total
geoip2_cはIPアドレスでlookup
を実行しただけでは、実際の値を取得しません。他のライブラリはlookup
の時点で値を取得しています。GeoIP2のライブラリでは取得する値が多ければ多いほど処理に時間がかかります。geoip2_cでもGeoIP2で利用できる値を全て取得すると処理に時間がかかるようになります。
利用可能な属性数は、表の通りです。
ライブラリ | 利用可能な属性数 |
---|---|
geoip | 9 |
geoip2_compat | 8 |
geoip2_c | 7+17+(4*7)=52 |
hive_geoip2 | 7+17+(4*7)=52 |
geoip2_cでは例えば、以下のような属性を取得することができますが、実際のアプリケーションでは全ての属性を必要とすることは少ないでしょう。よって必要な属性を必要なときに取得するようにした方が効率がよいです。なお、GeoIP2ではIPアドレスによって取得できる属性に違いがあります。
{"city"=>{"geoname_id"=>10300919, "names"=>{"en"=>"Fort Huachuaca"}},
"continent"=>
{"code"=>"NA",
"geoname_id"=>6255149,
"names"=>
{"de"=>"Nordamerika",
"en"=>"North America",
"es"=>"Norteamérica",
"fr"=>"Amérique du Nord",
"ja"=>"北アメリカ",
"pt-BR"=>"América do Norte",
"ru"=>"Северная Америка",
"zh-CN"=>"北美洲"}},
"country"=>
{"geoname_id"=>6252001,
"iso_code"=>"US",
"names"=>
{"de"=>"USA",
"en"=>"United States",
"es"=>"Estados Unidos",
"fr"=>"États-Unis",
"ja"=>"アメリカ合衆国",
"pt-BR"=>"Estados Unidos",
"ru"=>"США",
"zh-CN"=>"美国"}},
"location"=>
{"accuracy_radius"=>1000,
"latitude"=>31.5273,
"longitude"=>-110.3607,
"metro_code"=>789,
"time_zone"=>"America/Phoenix"},
"postal"=>{"code"=>"85613"},
"registered_country"=>
{"geoname_id"=>6252001,
"iso_code"=>"US",
"names"=>
{"de"=>"USA",
"en"=>"United States",
"es"=>"Estados Unidos",
"fr"=>"États-Unis",
"ja"=>"アメリカ合衆国",
"pt-BR"=>"Estados Unidos",
"ru"=>"США",
"zh-CN"=>"美国"}},
"subdivisions"=>
[{"geoname_id"=>5551752,
"iso_code"=>"AZ",
"names"=>
{"de"=>"Arizona",
"en"=>"Arizona",
"es"=>"Arizona",
"fr"=>"Arizona",
"ja"=>"アリゾナ州",
"pt-BR"=>"Arizona",
"ru"=>"Аризона"}}]}
fluent-plugin-geoipのGeoIP2対応について
GeoIP2サポートする際、なるべくGeoIP Legacyと互換性を保つためにgeoip2_compatを利用し、GeoIP2で利用できる属性を全て使用するためにgeoip2_cを使用することにしました。 設定によってGeoIP Legacyも利用できるようにしました。
それぞれで利用できる属性は以下の通りです。
GeoIP Legacy:
placeholder attributes | output example | type | note |
---|---|---|---|
${city[lookup_field]} | "Ithaca" | varchar(255) | - |
${latitude[lookup_field]} | 42.4277992248535 | decimal | - |
${longitude[lookup_field]} | -76.4981994628906 | decimal | - |
${country_code3[lookup_field]} | "USA" | varchar(3) | - |
${country_code[lookup_field]} | "US" | varchar(2) | A two-character ISO 3166-1 country code |
${country_name[lookup_field]} | "United States" | varchar(50) | - |
${dma_code[lookup_field]} | 555 | unsigned int | only for US |
${area_code[lookup_field]} | 607 | char(3) | only for US |
${region[lookup_field]} | "NY" | char(2) | A two character ISO-3166-2 or FIPS 10-4 code |
geoip2_c backend:
placeholder attributes | output example | note |
---|---|---|
${city.names.en[lookup_field]} | "Mountain View" | - |
${location.latitude[lookup_field]} | 37.419200000000004 | - |
${location.longitude[lookup_field]} | -122.0574 | - |
${country.iso_code[lookup_field]} | "US" | - |
${country.names.en[lookup_field]} | "United States" | - |
${postal.code[lookup_field]} | "94043" | - |
${subdivisions.0.iso_code[lookup_field]} | "CA" | - |
${subdivisions.0.names.en[lookup_field]} | "California" | - |
geoip2_cバックエンドでは、上記の属性だけでなくGeoIP2のデータベースに含まれる全ての属性を使用可能です。
geoip2_compat backend:
placeholder attributes | output example | note |
---|---|---|
${city[lookup_field]} | "Mountain View" | - |
${latitude[lookup_field]} | 37.419200000000004 | - |
${longitude[lookup_field]} | -122.0574 | - |
${country_code[lookup_field]} | "US" | - |
${country_name[lookup_field]} | "United States" | - |
${postal_code[lookup_field]} | "94043" | |
${region[lookup_field]} | "CA" | - |
${region_name[lookup_field]} | "California" | - |
geoip2_compatバックエンドでは、上記の属性のみ使用可能です。
geoip2_c/geoip2_compatを利用するにはlibmaxminddbを事前にインストールする必要があります1。
libmaxminddb2は多くのLinuxディスリビューションでパッケージ化されていてそれぞれのパッケージマネージャで簡単にインストールすることができます。
-
Debian: libmaxminddb
-
Ubuntu: Ubuntu official package or PPA of MaxMind team
-
CentOS/Fedora: rpms/libmaxminddb
GeoIP2を利用できるfluent-plugin-geoip 0.7.0がリリース済みです。互換性のためにgeoip2_cとgeoip2_compatはdevelopment dependenciesになっているので、GeoIP2を利用したい場合は、利用したいバックエンドに対応したGemを事前にgem install
するかGemfileに記載してbundle install
するかしてください。
なおFluentd v0.14 APIへの対応はこのプルリクエストで進行中です。
類似プロダクト
調査過程で見つけたGeoIP Legacy/GeoIP2に対応したfluent-pluginの比較を載せておきます。おすすめはもちろんfluent-plugin-geoipです。
GeoIP Legacy | GeoIP2 | 速度 | 特徴 | |
---|---|---|---|---|
fluent-plugin-geoip | ○ | ○ | 速い | |
fluent-plugin-filter-geoip | × | ○ | 遅い | データベースを自動ダウンロードできる |
fluent-plugin-geoip-filter | ○ | × | 速い | LRUキャッシュ搭載 |
fluent-plugin-filter-geo | × | ○ | 遅い | fluent-plugin-filter-geoipのfork |
まとめ
fluent-plugin-geoipのGeoIP2対応を進めたときの流れをまとめてみました。
-
既存のライブラリが要件を満たしているかどうか調査した
-
既存のライブラリだと要件を満たせなさそうなので、自分で拡張ライブラリを書いた
-
ベンチマークで性能を確認
-
実際にfluent-plugin-geoipに組み込んでプルリクエストを出した
-
プルリクエストを出した後も、project ownerのy-kenさんやFluentd開発者のrepeatedlyさんと協力して改善しfluent-plugin-geoip 0.7.0をリリースしていただいた
-
今後はFluentd v0.14 API対応したfluent-plugin-geoipリリースを目指す
Rubyの拡張ライブラリは簡単に書ける3ので今後も機会があればどんどん書きたいです。