Monday, March 16, 2009

GeoIP backend, or "reinventing the wheel"

The first incantation of the Cacheboy CDN uses 100% GeoIP to redirect users. This is roughly how it goes:

  1. Take a GeoIP map to break up IPs into "country" regions (thanks!) ;
  2. Take the list of "up" CDN nodes;
  3. For each country in my redirection table, find the CDN node that is up with the highest weight;
  4. Generate a "geo-map" file consisting of the highest-weight "up" CDN node for each country in "3";
  5. Feed that to the PowerDNS geoip module (thanks Mark @ Wikipedia!)
This really is a good place to start - its simple, its tested and it provides me with some basic abilities for distributing traffic across multiple sites to both speed up transfer times to end-users and better use the bandwidth available. The trouble is that it knows very little about the current state of the "internet" at any point in time. But, as I said, as a first (coarse!) step to get the CDN delivering bits, it worked out.

My next step is to build a much easier "hackable" backend which I can start adding functionality to. I've reimplemented the geoip backend in Perl and glued it to the "pipe-backend" module in PowerDNS. This simply passes DNS requests to an external process which spits back DNS replies. The trouble is that multiple backend processes will be invoked regardless of whether you want to or not. This means that I can't simply load in large databases into the backend process as it'll take time to load, waste RAM, and generally make things scale (less) well.

So I broke out the first memory hungry bit - the "geoip" lookup - and stuffed it into a small C daemon. All the daemon does is take a client IP and answer the geoip information for that IP. It will periodically check and reload the GeoIP database file in the background if its changed - maintaining whatever request rate I'm throwing at it rather than pausing for a few seconds whilst things are loaded in.

I can then use the "geoip daemon" (lets call it "geoipd") by the PowerDNS pipe-backend process I'm writing. All this process has to do at the moment is load in the geo maps (which are small) and reload them as required. It sends all geoip requests to the geoipd and uses the reply. If there is a problem talking to the geoipd, the backend process will simply use a weighted round robin of well-connected servers as a last resort.

The aim is to build a flexible backend framework for processing redirection requests which can be used by a variety of applications. For example, when its time for the CDN proxy nodes to also do 302 redirections to "closer" nodes, I can simply reuse a large part of the modular libraries written. When I integrate BGP information into the DNS infrastructure, I can reuse all of those libraries in the CDN proxy redirection logic, or the webserver URL rewriting logic, or anywhere else where its needed.

The next step? Figuring out how to load balance traffic destined to the same AS / GeoIP region across multiple CDN end nodes. This should let me scale the CDN up to a gigabit of aggregate traffic given the kind of sponsored boxes I'm currently receiving. More to come..


Yejun said...

Have you checked this geoipdns ?
I wrote a geodns for myself yesterday which is based on python twisted. I used lat, long to find neareast ip.

Adrian said...

Cute. Its djbdns based, no?

A large motivation behind using powerdns and the pipe-backend is to try and make things modular and easy to modify.

I'm also trying to reuse code modules as much as possible - embedding python inside a busy C application is possible but tricky. :)

But cool. Where are you getting the lat/long details for IPs? I'd like to compare that against the geoip list.

Yejun said...

There is also a perl geo dns

Maxmind's city database has lat/long.

Adrian said...

I looked at the geoip dns server actually.

I may end up migrating to that if I reach limits on the pipebackend powerdns infrastructure. It shouldn't be difficult to do.

My next trick is to finish the bgp query daemon so various modules can do BGP lookups without each having a full table in memory.

I really need to get all of this stuff wrapped up and released..