ククログ

株式会社クリアコード > ククログ > fluent-plugin-geoip の geoip2 対応した話

fluent-plugin-geoip の geoip2 対応した話

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を利用できるライブラリがあるかどうかを調査したところいくつか既存の実装がありました。

新たに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ディスリビューションでパッケージ化されていてそれぞれのパッケージマネージャで簡単にインストールすることができます。

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対応を進めたときの流れをまとめてみました。

  1. 既存のライブラリが要件を満たしているかどうか調査した

  2. 既存のライブラリだと要件を満たせなさそうなので、自分で拡張ライブラリを書いた

  3. ベンチマークで性能を確認

  4. 実際にfluent-plugin-geoipに組み込んでプルリクエストを出した

  5. プルリクエストを出した後も、project ownerのy-kenさんやFluentd開発者のrepeatedlyさんと協力して改善しfluent-plugin-geoip 0.7.0をリリースしていただいた

  6. 今後はFluentd v0.14 API対応したfluent-plugin-geoipリリースを目指す

Rubyの拡張ライブラリは簡単に書ける3ので今後も機会があればどんどん書きたいです。

  1. geoip2_compatはlibmaxminddbのソースコードをバンドルしているのでgeoip2_compatを利用する場合は事前にインストールしなくてもよい

  2. geoip2_cで必要なのは開発用のファイルなのでlibmaxminddb-devまたはlibmaxminddb-develをインストールする

  3. geoip2_cは最初FFIを使って実装するつもりでしたがstructやunionのalignmentを考慮するのが辛かったので止めました