批量地理定位数百万的IP

时间:2012-08-21 20:39:14

标签: algorithm postgresql geolocation mapping ip

我有2百万个IP地址和2500万个IP范围,其中包含存储在PostgreSQL中的Start IP,End IP和Geo-Locations。有没有一种有效的方法可以从2500万个数据库中查找这200万个IP的地理位置?我所做的是比较IP地址是否落在起始IP和结束IP之间,并查找相应的位置。然而,这似乎永远。据推测,这更像是从一组范围中查找一堆整数,例如搜索{7,13,31,42}:

Start End Loc
1     10  US
11    20  US
21    26  CN
29    32  SE
33    45  CA

并返回:

7  US
13 US
31 SE
42 CA

请注意,范围可能未必连接,且尺寸可能不同。谢谢!

修改

作为一个具体的例子,这是我正在处理的数据:

     start_ip     |      end_ip      | country |  region   |   city    | 
------------------+------------------+---------+-----------+-----------+-
 1.33.254.73/32   | 1.33.254.73/32   | jpn     | 33        | kurashiki | 
 1.39.1.0/32      | 1.39.4.255/32    | ind     | mh        | mumbai    | 
 1.40.144.0/32    | 1.40.145.255/32  | aus     | ns        | fairfield | 
 1.40.235.0/32    | 1.40.242.255/32  | aus     | ns        | sydney    | 
 1.44.28.0/32     | 1.44.29.255/32   | aus     | vi        | melbourne | 
 1.44.82.0/32     | 1.44.83.255/32   | aus     | vi        | melbourne | 
 1.44.92.0/32     | 1.44.93.255/32   | aus     | vi        | melbourne | 
 1.44.128.0/32    | 1.44.129.255/32  | aus     | vi        | melbourne | 
 1.44.220.0/32    | 1.44.221.255/32  | aus     | vi        | melbourne | 
 ......
 ......

查询类似于:

 75.149.219.61/32
 68.239.61.29/32
 96.41.50.165/32
 183.62.126.7/32
 ......

3 个答案:

答案 0 :(得分:2)

我认为,最佳和更优雅的解决方案是存储IP和范围 作为inet格式。 IP范围通常以网络/掩码格式发布, 不是开始/结束。这允许编写基于JOIN的

ON (ip.addr << geoloc.range)

当然ip表应该由addr和geoloc索引(范围,位置), 如果你没有CIDR格式并且需要从开始/结束构建它,那可能很昂贵(但是,之后的表会更容易使用)。

http://www.postgresql.org/docs/9.0/static/functions-net.html

编辑:不幸的是,这些开始/结束值看起来像“优化的”CIDR范围。换句话说,例如,

1.40.235.0     1.40.242.255

实际上是四个独立的连续范围的合并:

11101011   235.0-235.255
    11101100   236.0-239.255
    11101111   
    11110000   240.0-241.255   
    11110001
11110010   242.0-242.255

因此将行分解为CIDR操作所需的四行是不切实际的。

开始/结束看起来是cidr数据类型,所以将它们转换为inet(它们都是/ 32无论如何......)并保持查询的值也在inet数据类型中,索引在Start,End,应该给出合理的结果:

 SELECT query.ip, geoloc.country, geoloc.region, geoloc.city
     FROM query JOIN geoloc
     ON (query.ip >= geoloc.start_ip AND query.ip <= geoloc.end_ip);

另一种选择,不是非常优雅实际上是黑客),将基于例如“爆炸”ip和geoloc表。 addr和range的第一个字节分成不同的子表(我不希望你的IP范围与第一个字节不同)。

 SELECT * FROM geoloc
     WHERE start_ip >= inet '5.0.0.0' and end_ip <= inet '5.255.255.255'
     INTO TABLE geoloc_5;

 SELECT * FROM query
     WHERE start_ip >= inet '5.0.0.0' and end_ip <= inet '5.255.255.255'
     INTO TABLE query_5;

 Remember to CREATE INDEX on geoloc_5 start_ip, end_ip

这种方法几年前已经工作了,对于一个大的PostgreSQL批处理,但我希望,从那时起,一个更聪明的索引管理器 - 连同专用数据类型 - 将演变为超过匹配这个DIY分区。因此,如果&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;无法使用CIDR运算符。

那就是说,假设两张桌子分布均匀(只是为了得到一个大概的数字)。

然后,在2M x 25M记录上,而不是一个SELECT,运行256个2M / 256的SELECT 25M / 256。因此,不是1 x 2M x 25M = 50 T,而是256 x 2M / 256 x 25M / 256 = 192G 比较,相对于直接JOIN,应该快约200倍。

但我再说一遍,我希望PostgreSQL看到一个正确索引的CIDR字段,将不再真正执行“直接”JOIN,而是使用这个技巧(然后一些)。

答案 1 :(得分:1)

如果您要查询Loc列,则应该add an index。此外,由于这是一个3列表,因此将StartIPEndIP组合使用可能是明智之举,将其用作键,并使用Geolocation作为值,然后阅读此所有这些都来自键值存储,例如RedisMemcached。 NoSQL /无表格数据存储是为这类事物而设计的,您可以在这里读取数百万个数据点。

编辑:在阅读了一些评论之后,我想到了另一个解决方案是通过MapReduce之类的内容来并行搜索。分配线程以在Map步骤中查询一系列IP(例如,Thread1:1-10,Thread2:11-20等等),然后分配线程以在Reduce步骤中将分段查询减少为一个结果。您显然需要一种单独的编程语言来编写脚本,但并发性有助于减少总体加载时间,但缺点是对数据库进行多次查询。

答案 2 :(得分:1)

您必须提供有关此方面有意义输入的查询和查询计划。 E.g:

explain select hits.ip, locations.loc
 from hits left outer join locations
   on (hits.ip >= locations.start and hits.ip <= locations.stop);
                                  QUERY PLAN                                   
-------------------------------------------------------------------------------
 Nested Loop Left Join  (cost=0.00..245.06 rows=2400 width=36)
   Join Filter: ((hits.ip >= locations.start) AND (hits.ip <= locations.stop))
   ->  Seq Scan on hits  (cost=0.00..34.00 rows=2400 width=4)
   ->  Materialize  (cost=0.00..1.07 rows=5 width=40)
         ->  Seq Scan on locations  (cost=0.00..1.05 rows=5 width=40)
(5 rows)

我不确定您是否想要像您提供的其他答案之一那样向您的索引添加位置数据。这只是死数据增加膨胀,查找行没有用。

即使您使用支持仅索引扫描的pg版本(9.2,仍然处于测试阶段),较小的更精简的索引可能仍会提供更快的结果,每行一次额外的元组查找。