Project

mm_geoip

0.0
No commit activity in last 3 years
No release in over 3 years
A proxy object around the geoip gem for lazy lookup. Includes a Rack middleware.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Development

Runtime

>= 0
 Project Readme

MMGeoip

A proxy object around the geoip gem for lazy lookup. Includes a Rack middleware.

  • MMGeoip exposes all Maxmind properties: hostname, ip, country_code, country_code3, country_name, country_continent, region, city, postal_code, lat, lng, dma_code, area_code, timezone
  • Rack::MMGeoip pins a MMGeoip proxy to your environment and does the lookup lazy when you first access a geoip property.
  • Rack::MMGeoip looks for the standard geoip headers and wraps their access, too. So if you’re adding these header with mod_geoip in Apache or with http_geoip_module in NGinX these headers are used. So you can use this middleware even in production without compromising performance if NGinX or Apache already adds these headers.

Synopsis

Pure Ruby

require 'mm_geoip'
geo = MMGeoip.new :ip => '134.34.3.2'
geo.city        # => "Konstanz"
geo.region      # => "01" 
geo.region_name # => "Baden-Wurttemberg"
geo.lat         # => 47.66669999999999
geo.lng         # => 9.183300000000003

Rack middleware

Easy as pie. Just require 'rack/mm_geoip' and then use Rack::MMGeoip.

Sinatra

There is a helper for Sinatra. You just require it:

require 'sinatra/mm_geoip'

Then in your app you must tell Rack to use the provided middleware (see above) and tell Sinatra to include the helper:

use Rack::MMGeoip         # To stuff the MMGeoip proxy object into the env hash.
helpers Sinatra::MMGeoip  # Only if you want to use the convinient #geoip helper method.

You can then use the provided geoip helper anywhere in your routes or your views.

There also is a demo application with Sinatra available at https://github.com/niko/mm_geoip_with_sinatra. Try it at http://mmgeoip.herokuapp.com/.

Command line

$ mm_geoip 134.34.3.2
Using database: data/GeoLiteCity.dat
hostname: 134.34.3.2
ip: 134.34.3.2
country_code: DE
country_code3: DEU
country_name: Germany
country_continent: EU
region: 01
city: Konstanz
postal_code: 
lat: 47.66669999999999
lng: 9.183300000000003
dma_code: 
area_code: 
timezone: Europe/Berlin
region_name: Baden-Wurttemberg
$

The database file

The direct link to the free version of database is http://geolite.maxmind.com/download/geoip/database/GeoLiteCity.dat.gz

To specify the database file to be used, you have three possibilities:

  • First you set an environment variable: MMGeoipDatabase=/your/database.dat mm_geoip 134.34.3.2
  • Or you pass the location as a second argument: mm_geoip 134.34.3.2 /your/database.dat
  • Lastly you can put or link one into the gem so mm_geoip finds it automatically: cp /your/database.dat /location/of/the/gem/data/GeoLiteCity.dat

By default, the database is no longer included in the gem. It has made the gem huge and slow to install. In production use you will always want to update the database independently of the gem anyway.

So the last possibility is mainly still working for people rebuilding the gem with the database included for distribution on multiple servers. A build script for that would look somewhat like this:

git clone git://github.com/niko/mm_geoip.git
cd mm_geoip
curl http://geolite.maxmind.com/download/geoip/database/GeoLiteCity.dat.gz | gunzip > data/GeoLiteCity.dat
gem build mm_geoip.gemspec

When using the commercial version MaxMind provides a tool for updating it automatically.

Performance

MMGeoip uses the geoip gem. On my laptop a single lookup takes about 0.6ms. Without actually querying it’s somewhere between 0.15ms and 0.2ms. That means in all but the ultra fastest webapps you won’t be able to measue any difference when using the MMGeoip rack middleware.

But don’t take my word for it, see for yourself. Get the demo app, run it and measure:

ab -c 5 -n 1000 -H “X-FORWARDED-FOR: 95.113.5.17” http://127.0.0.1:4567/

Lies, damn lies and my results:

Without the middleware and any geoip lookup

Server Software: thin
Server Hostname: 127.0.0.1
Server Port: 3000

Document Path: /do_nothing
Document Length: 657 bytes

Concurrency Level: 5
Time taken for tests: 4.536 seconds
Complete requests: 1000
Failed requests: 0
Write errors: 0
Total transferred: 800800 bytes
HTML transferred: 657657 bytes
Requests per second: 220.46 [#/sec] (mean)
Time per request: 22.680 [ms] (mean)
Time per request: 4.536 [ms] (mean, across all concurrent requests)
Transfer rate: 172.41 [Kbytes/sec] received

Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 1.0 0 13
Processing: 4 22 13.3 16 89
Waiting: 3 21 13.0 15 75
Total: 4 23 13.3 16 89

Percentage of the requests served within a certain time (ms)
50% 16
66% 23
75% 25
80% 26
90% 50
95% 54
98% 62
99% 70
100% 89 (longest request)

With the middleware but without any geoip lookup

Server Software: thin
Server Hostname: 127.0.0.1
Server Port: 3000

Document Path: /do_nothing
Document Length: 657 bytes

Concurrency Level: 5
Time taken for tests: 4.652 seconds
Complete requests: 1000
Failed requests: 0
Write errors: 0
Total transferred: 800800 bytes
HTML transferred: 657657 bytes
Requests per second: 214.96 [#/sec] (mean)
Time per request: 23.260 [ms] (mean)
Time per request: 4.652 [ms] (mean, across all concurrent requests)
Transfer rate: 168.10 [Kbytes/sec] received

Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.8 0 15
Processing: 7 23 14.1 17 75
Waiting: 3 22 13.9 16 71
Total: 7 23 14.1 17 75

Percentage of the requests served within a certain time (ms)
50% 17
66% 23
75% 26
80% 27
90% 53
95% 59
98% 70
99% 72
100% 75 (longest request)

With the middleware and with a geoip lookup

Server Software: thin
Server Hostname: 127.0.0.1
Server Port: 3000

Document Path: /
Document Length: 662 bytes

Concurrency Level: 5
Time taken for tests: 5.599 seconds
Complete requests: 1000
Failed requests: 0
Write errors: 0
Total transferred: 806610 bytes
HTML transferred: 663324 bytes
Requests per second: 178.61 [#/sec] (mean)
Time per request: 27.994 [ms] (mean)
Time per request: 5.599 [ms] (mean, across all concurrent requests)
Transfer rate: 140.69 [Kbytes/sec] received

Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.7 0 11
Processing: 4 28 14.3 21 75
Waiting: 4 26 14.5 20 74
Total: 4 28 14.3 21 75

Percentage of the requests served within a certain time (ms)
50% 21
66% 28
75% 30
80% 31
90% 58
95% 60
98% 66
99% 71
100% 75 (longest request)

Summary

4.5ms vs. 4.7ms vs. 5.6ms means to me:

  • Include MMGeoip whenever you need it.
  • The penalty on actions where you don’t do an actual lookup is negligible.
  • The penalty for an actual lookup is 1ms.
  • Installing mod_geoip in Apache or http_geoip_module in NGinX is not worth it for any speed considerations. There may be others.