我期望PostGIS对格式正确的地址进行地理编码的速度有多快?


17

我期望PostGIS对格式正确的地址进行地理编码的速度有多快?

我已经安装了PostgreSQL 9.3.7和PostGIS 2.1.7,加载了国家数据和所有州数据,但是发现地理编码比我预期的要慢得多。我设定的期望过高吗?我平均每秒获得3个单独的地理编码。我需要做大约500万,我不想等待三周。

这是一个用于处理巨型R矩阵的虚拟机,我在侧面安装了此数据库,因此配置可能看起来有些愚蠢。如果对VM的配置进行重大更改会有所帮助,则可以更改配置。

硬件规格

内存:65GB处理器:6 lscpu给了我这个:

# lscpu
Architecture:          x86_64
CPU op-mode(s):        32-bit, 64-bit
Byte Order:            Little Endian
CPU(s):                6
On-line CPU(s) list:   0-5
Thread(s) per core:    1
Core(s) per socket:    1
Socket(s):             6
NUMA node(s):          1
Vendor ID:             GenuineIntel
CPU family:            6
Model:                 58
Stepping:              0
CPU MHz:               2400.000
BogoMIPS:              4800.00
Hypervisor vendor:     VMware
Virtualization type:   full
L1d cache:             32K
L1i cache:             32K
L2 cache:              256K
L3 cache:              30720K
NUMA node0 CPU(s):     0-5

操作系统是centos,uname -rv给出以下信息:

# uname -rv
2.6.32-504.16.2.el6.x86_64 #1 SMP Wed Apr 22 06:48:29 UTC 2015

PostgreSQL配置

> select version()
"PostgreSQL 9.3.7 on x86_64-unknown-linux-gnu, compiled by gcc (GCC) 4.4.7 20120313 (Red Hat 4.4.7-11), 64-bit"
> select PostGIS_Full_version()
POSTGIS="2.1.7 r13414" GEOS="3.4.2-CAPI-1.8.2 r3921" PROJ="Rel. 4.8.0, 6 March 2012" GDAL="GDAL 1.9.2, released 2012/10/08" LIBXML="2.7.6" LIBJSON="UNKNOWN" TOPOLOGY RASTER"

根据以往的建议,这些类型的查询,我调升shared_buffers了在postgresql.conf文件中有关可用RAM和有效的缓存大小的RAM 1/2 1/4:

shared_buffers = 16096MB     
effective_cache_size = 31765MB

我有installed_missing_indexes()(在解决了一些表中的重复插入之后)没有任何错误。

地理编码SQL示例#1(批量)〜平均时间为2.8 /秒

我正在遵循http://postgis.net/docs/Geocode.html中的示例,该示例使我创建了一个表,其中包含要进行地址解析的地址,然后执行SQL UPDATE

UPDATE addresses_to_geocode
              SET  (rating, longitude, latitude,geo) 
              = ( COALESCE((g.geom).rating,-1),
              ST_X((g.geom).geomout)::numeric(8,5), 
              ST_Y((g.geom).geomout)::numeric(8,5),
              geo )
              FROM (SELECT "PatientId" as PatientId
              FROM addresses_to_geocode 
              WHERE "rating" IS NULL ORDER BY PatientId LIMIT 1000) As a
              LEFT JOIN (SELECT "PatientId" as PatientId, (geocode("Address",1)) As geom
              FROM addresses_to_geocode As ag
              WHERE ag.rating IS NULL ORDER BY PatientId LIMIT 1000) As g ON a.PatientId = g.PatientId
              WHERE a.PatientId = addresses_to_geocode."PatientId";

我正在使用1000以上的批次大小,它在337.70秒内返回。对于较小的批次,它会稍微慢一些。

地理编码SQL示例#2(逐行)〜平均时间为1.2 /秒

当我用如下所示的语句一次对一个地址进行地理编码来挖掘地址时(顺便说一句,下面的示例花费了4.14秒),

SELECT g.rating, ST_X(g.geomout) As lon, ST_Y(g.geomout) As lat, 
    (addy).address As stno, (addy).streetname As street, 
    (addy).streettypeabbrev As styp, (addy).location As city, 
    (addy).stateabbrev As st,(addy).zip 
FROM geocode('6433 DROMOLAND Cir NW, MASSILLON, OH 44646',1) As g;

它的速度稍慢一些(每条记录是2.5倍),但我可以查看查询时间的分布情况,发现少数几个冗长的查询使查询速度最慢(只有500万中的前2600个具有查询时间)。也就是说,最高的10%的平均时间约为100毫秒,最低的10%的平均时间为3.69秒,而平均值为754毫秒,中位数为340毫秒。

# Just some interaction with the data in R
> range(lookupTimes[1:2600])
[1]  0.00 11.54
> median(lookupTimes[1:2600])
[1] 0.34
> mean(lookupTimes[1:2600])
[1] 0.7541808
> mean(sort(lookupTimes[1:2600])[1:260])
[1] 0.09984615
> mean(sort(lookupTimes[1:2600],decreasing=TRUE)[1:260])
[1] 3.691269
> hist(lookupTimes[1:2600]

前2600行的地址解析时间

其他想法

如果我无法获得性能上一个数量级的提高,我认为我至少可以对预测慢速的地理编码时间做出有根据的猜测,但是对于我来说,为什么慢速的地址似乎要花费更长的时间并不清楚。我正在通过自定义规范化步骤运行原始地址,以确保在geocode()函数获取地址之前将其正确格式化:

sql=paste0("select pprint_addy(normalize_address('",myAddress,"'))")

这里myAddress是一个[Address], [City], [ST] [Zip]从非PostgreSQL数据库的用户地址表编译字符串。

我尝试(失败)安装pagc_normalize_address扩展程序,但是尚不清楚这是否会带来我所寻求的改进。 编辑以根据建议添加监视信息

性能

固定一个CPU:[编辑,每个查询仅一个处理器,因此我有5个未使用的CPU]

top - 14:10:26 up 1 day,  3:11,  4 users,  load average: 1.02, 1.01, 0.93
Tasks: 219 total,   2 running, 217 sleeping,   0 stopped,   0 zombie
Cpu(s): 15.4%us,  1.5%sy,  0.0%ni, 83.1%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Mem:  65056588k total, 64613476k used,   443112k free,    97096k buffers
Swap: 262139900k total,    77164k used, 262062736k free, 62745284k cached

  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND
 3130 postgres  20   0 16.3g 8.8g 8.7g R 99.7 14.2 170:14.06 postmaster
11139 aolsson   20   0 15140 1316  932 R  0.3  0.0   0:07.78 top
11675 aolsson   20   0  135m 1836 1504 S  0.3  0.0   0:00.01 wget
    1 root      20   0 19364 1064  884 S  0.0  0.0   0:01.84 init
    2 root      20   0     0    0    0 S  0.0  0.0   0:00.06 kthreadd

数据分区上的磁盘活动示例,其中一个proc固定为100%:[编辑:此查询仅使用一个处理器]

# dstat -tdD dm-3 1
----system---- --dsk/dm-3-
  date/time   | read  writ
12-06 14:06:36|1818k 3632k
12-06 14:06:37|   0     0
12-06 14:06:38|   0     0
12-06 14:06:39|   0     0
12-06 14:06:40|   0    40k
12-06 14:06:41|   0     0
12-06 14:06:42|   0     0
12-06 14:06:43|   0  8192B
12-06 14:06:44|   0  8192B
12-06 14:06:45| 120k   60k
12-06 14:06:46|   0     0
12-06 14:06:47|   0     0
12-06 14:06:48|   0     0
12-06 14:06:49|   0     0
12-06 14:06:50|   0    28k
12-06 14:06:51|   0    96k
12-06 14:06:52|   0     0
12-06 14:06:53|   0     0
12-06 14:06:54|   0     0 ^C

分析该SQL

这是来自EXPLAIN ANALYZE该查询:

"Update on addresses_to_geocode  (cost=1.30..8390.04 rows=1000 width=272) (actual time=363608.219..363608.219 rows=0 loops=1)"
"  ->  Merge Left Join  (cost=1.30..8390.04 rows=1000 width=272) (actual time=110.934..324648.385 rows=1000 loops=1)"
"        Merge Cond: (a.patientid = g.patientid)"
"        ->  Nested Loop  (cost=0.86..8336.82 rows=1000 width=184) (actual time=10.676..34.241 rows=1000 loops=1)"
"              ->  Subquery Scan on a  (cost=0.43..54.32 rows=1000 width=32) (actual time=10.664..18.779 rows=1000 loops=1)"
"                    ->  Limit  (cost=0.43..44.32 rows=1000 width=4) (actual time=10.658..17.478 rows=1000 loops=1)"
"                          ->  Index Scan using "addresses_to_geocode_PatientId_idx" on addresses_to_geocode addresses_to_geocode_1  (cost=0.43..195279.22 rows=4449758 width=4) (actual time=10.657..17.021 rows=1000 loops=1)"
"                                Filter: (rating IS NULL)"
"                                Rows Removed by Filter: 24110"
"              ->  Index Scan using "addresses_to_geocode_PatientId_idx" on addresses_to_geocode  (cost=0.43..8.27 rows=1 width=152) (actual time=0.010..0.013 rows=1 loops=1000)"
"                    Index Cond: ("PatientId" = a.patientid)"
"        ->  Materialize  (cost=0.43..18.22 rows=1000 width=96) (actual time=100.233..324594.558 rows=943 loops=1)"
"              ->  Subquery Scan on g  (cost=0.43..15.72 rows=1000 width=96) (actual time=100.230..324593.435 rows=943 loops=1)"
"                    ->  Limit  (cost=0.43..5.72 rows=1000 width=42) (actual time=100.225..324591.603 rows=943 loops=1)"
"                          ->  Index Scan using "addresses_to_geocode_PatientId_idx" on addresses_to_geocode ag  (cost=0.43..23534259.93 rows=4449758000 width=42) (actual time=100.225..324591.146 rows=943 loops=1)"
"                                Filter: (rating IS NULL)"
"                                Rows Removed by Filter: 24110"
"Total runtime: 363608.316 ms"

http://explain.depesz.com/s/vogS上查看更好的细分


1
运行查询时,计算机将执行什么操作?它会阻塞IO还是成为其他瓶颈?
til_b 2015年

1
您已载入多少个州。在带有4-8GB内存的Windows 64位计算机上,我通常每个地址30ms-150ms。通常,尽管我只在1个或2个州工作。尚未针对更多状态对性能的影响进行基准测试。
LR1234567 2015年

@ LR1234567 50个州
aaryno 2015年

1
@til_b CPU固定为99.7%
aaryno

听起来我们只是要等待几周才能完成,因为这是一次性的事情,一旦完成以应付每天100个地址的运行时负载,我们将剩下很多汁液我们正在经历。我将一直保持开放状态,直到完成为止,以防出现真正令人信服的事情,使我们能够解决与CPU挂钩的问题。
aaryno 2015年

Answers:


7

我已经花了很多时间对此进行试验,我认为最好分开发表,因为它们是从不同角度发表的。

这确实是一个复杂的主题,请在我的博客文章中了解有关地理编码服务器设置我使用的脚本的更多详细信息,这里只是一些简要摘要:

仅包含2个州数据的服务器总是比加载了全部50个州数据的服务器更快。

我在不同时间使用家用PC和两个不同的Amazon AWS服务器对此进行了验证。

我的具有2个状态数据的AWS免费套餐服务器只有1G RAM,但是对于具有1000条记录和45,000条记录的数据,它具有稳定的43〜59 ms性能。

我对8G RAM AWS服务器使用了完全相同的设置过程,其中加载了所有状态,完全相同的脚本和数据,并且性能降至80〜105 ms。

我的理论是,当地址解析器无法完全匹配地址时,它开始扩大搜索范围,而忽略了邮政编码或城市等某些部分。这就是为什么地理编码文档夸耀它可以用错误的邮政编码重新定位地址,尽管花费了3000毫秒。

仅加载2个状态的数据,服务器将花费更少的时间进行无结果的搜索或得分非常低的匹配,因为它只能在2个状态下搜索。

我试图通过restrict_region在地址解析功能中将参数设置为状态多面体来限制此情况,希望这样可以避免毫无用处的搜索,因为我确定大多数地址都具有正确的状态。比较这两个版本:

  select geocode('501 Fairmount DR , Annapolis, MD 20137',1); 
  select geocode('501 Fairmount DR , Annapolis, MD 20137', 1, the_geom) from tiger.state where statefp = '24';

第二个版本的唯一区别是,通常,如果我立即再次运行相同的查询,它将更快,因为已缓存了相关数据,但是第二个版本禁用了此效果。

因此,restrict_region它无法按我希望的那样工作,也许它只是用来过滤多次匹配结果,而不是限制搜索范围。

您可以稍微调整一下postgre conf。

通常怀疑安装索引丢失,真空分析对我没有任何影响,因为下载脚本已经进行了必要的维护,除非您弄乱了它们。

但是根据这篇文章设置postgre conf 确实有帮助。我的50个州的全尺寸服务器在默认配置下有320 ms,用于处理一些形状较差的数据,在使用2G shared_buffer,5G缓存的情况下,它提高到了185 ms,并根据该文章将大多数设置调整为100 ms。

与Postgis更相关,并且它们的设置似乎相似。

对于我的情况,每次提交的批处理大小都无关紧要。 地理编码文档使用的批次大小为3。我尝试了从1、3、5到10的值。我没有发现任何明显的不同。使用较小的批处理大小,您可以进行更多的提交和更新,但是我认为真正的瓶颈不在这里。实际上,我现在正在使用批量大小1。因为总会有一些意想不到的格式错误的地址会导致异常,所以我将整个批处理中的错误设置为忽略,然后继续处理剩余的行。批次大小为1时,我无需第二次处理该表,即可对标记为已忽略的批次中的可能良好记录进行地理编码。

当然,这取决于批处理脚本的工作方式。稍后我将发布脚本并提供更多详细信息。

如果适合您,您可以尝试使用规范化地址来过滤错误的地址。 我在某处看到有人提到过此消息,但我不确定该如何工作,因为normalize函数仅以格式起作用,它不能真正告诉您哪个地址无效。

后来我意识到,如果地址的格式明显错误,并且您想跳过它们,则可能会有所帮助。例如,我有很多地址缺少街道名称甚至街道名称。首先规范化所有地址将相对较快,然后可以为您过滤明显的错误地址,然后跳过它们。但是,这不适合我的用法,因为没有街道编号甚至街道名称的地址仍然可以映射到街道或城市,并且该信息对我仍然有用。

在我的情况下,大多数不能进行地理编码的地址实际上都具有所有字段,只是数据库中没有匹配项。您不能仅通过规范化它们来过滤这些地址。

编辑有关更多详细信息,请参阅有关地理编码服务器设置我使用的脚本的博客文章。

编辑2我已经完成了对200万个地址的地址解析,并根据地址解析结果对地址做了很多清理工作。通过更好地清除输入,下一个批处理作业的运行速度将大大提高。干净的意思是某些地址显然是错误的,应该删除,或者地址解析器包含意外内容,从而导致地址解析出现问题。我的理论是: 删除错误的地址可以避免混乱高速缓存,从而显着提高良好地址的性能。

我根据状态分离了输入,以确保每个作业都可以将地理编码所需的所有数据缓存在RAM中。但是,作业中的每个错误地址都会使地址解析器搜索更多状态,这可能会使高速缓存混乱。


很好的回应。在我的盒子上,碰巧的是,对状态的过滤将匹配速度提高了大约50(!),但是我怀疑我可能有索引问题。
ako

2
  1. 根据此讨论线程,您应该使用相同的规范化过程来处理Tiger数据和您的输入地址。由于Tiger数据已使用内置的规范化器进行处理,因此最好只使用内置的规范化器。即使您确实使pagc_normalizer工作正常,如果不使用它来更新Tiger数据,也可能无济于事。

    话虽如此,我认为geocode()仍然会调用规范化器,因此在进行地理编码之前规范化地址可能并不是真正有用。规范化程序的一种可能用法是将规范化的地址与geocode()返回的地址进行比较。将它们都标准化后,可能会更容易找到错误的地理编码结果。

    如果您可以通过规范化程序从地址解析中过滤出错误的地址,那将真正有帮助。但是我看不到规范化器具有匹配得分或评分之类的东西。

  2. 同一讨论线程还提到了调试开关,geocode_address以显示更多信息。节点geocode_address需要规范化的地址输入。

  3. Geocoder可以快速进行精确匹配,但在困难情况下则需要更多时间。我发现有一个参数,restrict_region并认为如果我将限制设置为状态,可能会限制无结果的搜索,因为我很确定它将处于哪种状态。结果是将其设置为错误状态并没有停止对地址进行地理编码正确的地址,尽管需要一些时间。

    因此,如果第一个精确搜索不匹配,则地理编码器可能会在所有可能的位置查找。这使得它能够处理带有一些错误的输入,但同时也会使某些搜索非常缓慢。

    我认为交互式服务接受有错误的输入是很好的,但是有时候我们可能只想放弃一小部分错误的地址,以在批处理地理编码中获得更好的性能。


restrict_region设置正确的状态对时序有什么影响?另外,从上面链接到的postgis-users线程中,他们特别提到在遇到类似1020 Highway 20我遇到的地址时遇到麻烦。
阿里亚诺,2015年

设置正确的状态可能不会改善,因为如果地址格式正确,则地理编码器无论如何都可以正确获取状态。
dracodoc

1

我将发布此答案,但希望其他贡献者将有助于分解以下内容,我认为这将描绘出更加连贯的画面:

加载的状态数量对地址解析有何影响?我已经拥有全部50个,并且看到的性能要比@ LR1234567低得多(即,每个的时间是8倍geocode)。

批量地理编码最有效的方法是什么?我正在运行一个串行过程,重复运行100个批处理,直到完成整个回载。多线程方法将是更好的选择,但建议使用哪种方法?

虚拟化对PostgreSQL地理编码有什么影响?根据其他一些帖子,我猜是10%,但是对此答案信心不足

现在,我的回答只是个轶事:

我获得的最佳效果(基于单个连接)平均为208毫秒geocode通过从我的数据集中随机选择地址(遍及美国)来衡量。它包含一些脏数据,但运行时间最长的geocodes 在明显的方面似乎并不

要点是,我似乎受CPU限制,并且单个查询绑定到单个处理器。从理论上讲,我可以通过使多个连接运行UPDATEaddresses_to_geocode表的互补段上来实现并行化。同时,我geocode在全国范围内的数据集上平均要花费208毫秒。无论是我的大部分地址在哪里,还是花了多长时间(例如,请参见上面的直方图)以及下表,分配都存在偏差。

到目前为止,我最好的方法是按10000个批处理,从每个批处理中可以得出一些改进。对于100批次,我大约需要251ms,而10000批次我需要208ms。

UPDATE addresses_to_geocode 
SET (rating, longitude, latitude, geo) = 
   (COALESCE((g.geom).rating,-1), 
            ST_X((g.geom).geomout)::numeric(8,5),   
            ST_Y((g.geom).geomout)::numeric(8,5), 
            geo) 
   FROM (
       SELECT "PatientId" as PatientId 
       FROM addresses_to_geocode  
       WHERE "rating" IS NULL 
       ORDER BY PatientId LIMIT 100) As a 
   LEFT JOIN (
       SELECT "PatientId" as PatientId, (geocode("Address",1)) As geom 
       FROM addresses_to_geocode As ag 
       WHERE ag.rating IS NULL 
       ORDER BY PatientId LIMIT 100) As g 
   ON a.PatientId = g.PatientId 
   WHERE a.PatientId = addresses_to_geocode."PatientId";

我必须引用字段名称,因为RPostgreSQL如何使用以下方式创建表 dbWriteTable

这大约是我一次记录一张专辑的速度的4倍。一次做一次,我可以按州分类(见下文)。我这样做是为了检查是否有一个或多个TIGER状态具有不良的负载或索引,我期望这会导致状态状态的geocode性能下降。我显然有一些不良数据(某些地址甚至是电子邮件地址!),但其中大多数格式正确。如前所述,一些运行时间最长的查询在格式上没有明显的缺陷。下表是我的数据集中3000个随机地址中的状态的数量,最小查询时间,平均查询时间和最大查询时间的表:

       state   n  min      mean   max
1          .   1 0.00 0.0000000  0.00
12        DC   6 0.07 0.0900000  0.10
9  CHIHUAHUA   1 0.16 0.1600000  0.16
2         00   1 0.18 0.1800000  0.18
6         AR   1 0.37 0.3700000  0.37
27        MT  17 0.14 0.4229412  1.01
14        GA  37 0.22 0.4340541  2.78
10        CO   1 0.54 0.5400000  0.54
16        IL 390 0.16 0.5448974  3.75
8         CA 251 0.17 0.5546614  3.58
5         AL   4 0.13 0.5575000  0.86
18        KS   3 0.43 0.5966667  0.75
23        ME 121 0.14 0.6266116  7.88
35        SC 390 0.14 0.6516923  6.88
24        MI  62 0.12 0.6524194  3.36
40        WA   3 0.23 0.7500000  1.41
32        OK 145 0.17 0.7538621  5.84
20        LA   1 0.76 0.7600000  0.76
31        OH 551 0.00 0.7623775 10.27
17        IN 108 0.19 0.7864815  3.64
43      <NA>  89 0.00 0.8152809  4.98
15        IA   1 0.82 0.8200000  0.82
30        NY 227 0.19 0.8227753 28.47
19        KY   3 0.56 0.8333333  1.36
36        TN 333 0.11 0.8566667  6.45
28        NC 129 0.24 0.8843411  4.07
13        FL  70 0.28 0.9131429  4.65
7         AZ 101 0.20 0.9498020  6.33
34        PA  56 0.14 0.9594643  3.61
29        NJ   1 1.03 1.0300000  1.03
33        OR 101 0.24 1.0966337 14.89
26        MS  28 0.25 1.1503571 11.89
3          9   6 0.58 1.2133333  1.93
4         AK   1 1.25 1.2500000  1.25
22        MD   9 0.50 1.3055556  4.17
25        MO  22 0.31 1.3381818  4.20
42        WY   1 1.38 1.3800000  1.38
38        VA 127 0.20 1.3873228  5.69
37        TX   4 0.53 1.4800000  3.28
21        MA   4 0.47 1.5725000  3.63
11        CT   5 0.38 1.6760000  4.68
39        VT   1 2.25 2.2500000  2.25
41        WI   2 2.27 2.2850000  2.30
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.