2

我获得了 200 万个 IP 地址和 2500 万个 IP 范围,其中起始 IP、结束 IP 和地理位置存储在 PostgreSQL 中。有没有一种有效的方法可以从 2500 万个数据库中查找这 200 万个 IP 的地理位置?我所做的是比较一个IP地址是否在Start IP和End 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
 ......
4

3 回答 3

2

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

ON (ip.addr << geoloc.range)

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

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 操作所需的四行是不切实际的。

Start/End 在 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);

另一种不是很优雅实际上是 hack)的替代方法是基于例如 addr 和 range 的第一个字节将 ip 和 geoloc 表“分解”成单独的子表(我不希望你有一个具有不同首字节的 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 分区相匹配的程度。因此,如果不能使用 << CIDR 运算符,则只能将朴素的 Jordan 分区用作最后的解决方案。

也就是说,假设两个表都有一个平坦的分布(只是为了得到一个大概的数字)。

然后,在 2M x 25M 记录上运行 256 个 2M/256 x 25M/256 的 SELECT,而不是一个 SELECT。因此,您可以进行 256 x 2M/256 x 25M/256 = 192G 的比较,而不是 1 x 2M x 25M = 50 T,这应该比直接 JOIN 快 200 倍左右。

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

于 2012-08-21T21:15:04.037 回答
1

如果要查询该Loc列,则应为其添加索引。此外,由于这是一个 3 列的表,最好将 and 组合起来StartIPEndIP将其用作键,并将 theGeolocation用作值,然后从RedisMemcached等键值存储中读取所有内容。NoSQL/无表数据存储专为此类事情而设计,您在其中读取数百万个数据点。

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

于 2012-08-21T20:46:44.357 回答
1

您必须提供您的查询和查询计划,以便对此进行有意义的输入。例如:

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,仍处于测试阶段),较小的精简索引可能仍会通过每行额外的元组查找提供更快的结果。

于 2012-08-21T21:26:09.030 回答