Com que rapidez devo esperar que o PostGIS codifique geograficamente endereços bem formatados?
Eu instalei o PostgreSQL 9.3.7 e o PostGIS 2.1.7, carreguei os dados de nação e todos os dados de estados, mas descobrimos que o geocodificação é muito mais lento do que eu imaginava. Estabeleci minhas expectativas muito altas? Estou recebendo uma média de 3 geocódigos individuais por segundo. Preciso fazer cerca de 5 milhões e não quero esperar três semanas por isso.
Esta é uma máquina virtual para processar matrizes R gigantes e eu instalei esse banco de dados na lateral para que a configuração possa parecer um pouco boba. Se uma grande alteração na configuração da VM ajudar, eu posso alterar a configuração.
Especificações de hardware
Memória: processadores de 65GB: 6
lscpu
me fornece o seguinte:
# 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
OS é centos, uname -rv
dá o seguinte:
# uname -rv
2.6.32-504.16.2.el6.x86_64 #1 SMP Wed Apr 22 06:48:29 UTC 2015
Configuração do 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"
Com base nas sugestões anteriores para esses tipos de consultas, eu atualizei shared_buffers
o postgresql.conf
arquivo para cerca de 1/4 da RAM disponível e o tamanho do cache efetivo para 1/2 da RAM:
shared_buffers = 16096MB
effective_cache_size = 31765MB
Eu tenho installed_missing_indexes()
e (depois de resolver inserções duplicadas em algumas tabelas) não tive nenhum erro.
Exemplo de geocodificação do SQL nº 1 (lote) ~ o tempo médio é de 2,8 / s
Estou seguindo o exemplo de http://postgis.net/docs/Geocode.html , que me permite criar uma tabela contendo o endereço para geocodificar e, em seguida, executando um 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";
Estou usando um tamanho de lote de 1000 acima e ele retorna em 337,70 segundos. É um pouco mais lento para lotes menores.
Exemplo 2 de geocodificação do SQL (linha por linha) ~ o tempo médio é de 1,2 / s
Quando eu cavo em meus endereços, executando os códigos geográficos, um de cada vez, com uma declaração parecida com esta (btw, o exemplo abaixo levou 4,14 segundos),
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;
é um pouco mais lento (2,5x por registro), mas posso observar a distribuição dos tempos de consulta e ver que são uma minoria de consultas demoradas que diminuem mais esse número (apenas os primeiros 2600 de 5 milhões têm tempos de pesquisa). Ou seja, os 10% principais estão demorando em média cerca de 100 ms, os 10% inferiores em média 3,69 segundos, enquanto a média é 754 ms e a mediana é 340 ms.
# 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]
Outros pensamentos
Se eu não conseguir um aumento de ordem de magnitude no desempenho, imaginei que poderia pelo menos adivinhar sobre a previsão de tempos de geocódigo lentos, mas não me é óbvio por que os endereços mais lentos parecem demorar muito mais. Estou executando o endereço original por meio de uma etapa de normalização personalizada para garantir que ele esteja bem formatado antes da geocode()
função:
sql=paste0("select pprint_addy(normalize_address('",myAddress,"'))")
onde myAddress
é uma [Address], [City], [ST] [Zip]
cadeia de caracteres compilada a partir de uma tabela de endereços do usuário a partir de um banco de dados não postgresql.
Eu tentei (falhei) instalar a pagc_normalize_address
extensão, mas não está claro se isso trará o tipo de melhoria que estou procurando.
Editado para adicionar informações de monitoramento conforme a sugestão
atuação
Uma CPU está vinculada: [editar, apenas um processador por consulta, então eu tenho 5 CPUs não utilizadas]
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
Amostra de atividade do disco na partição de dados enquanto um proc está indexado a 100%: [editar: apenas um processador em uso por esta consulta]
# 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
Analise esse SQL
Isto é EXPLAIN ANALYZE
dessa consulta:
"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"
Veja melhor detalhamento em http://explain.depesz.com/s/vogS
Respostas:
Passei muito tempo experimentando isso, acho melhor postar separadamente, pois eles são de ângulos diferentes.
Este é realmente um tópico complexo. Veja mais detalhes no meu post sobre a configuração do servidor de geocodificação e o script que usei . Aqui estão apenas alguns breves resumos:
Um servidor com apenas dados de 2 estados é sempre mais rápido que um servidor carregado com todos os dados de 50 estados.
Verifiquei isso com meu PC doméstico em momentos diferentes e em dois servidores Amazon AWS diferentes.
Meu servidor de camada livre da AWS com dados de 2 estados possui apenas 1G de RAM, mas possui um desempenho consistente de 43 a 59 ms para dados com 1000 registros e 45.000 registros.
Usei exatamente o mesmo procedimento de configuração para um servidor AWS 8G RAM com todos os estados carregados, exatamente o mesmo script e dados, e o desempenho caiu para 80 ~ 105 ms.
Minha teoria é que, quando o geocoder não pode corresponder exatamente ao endereço, ele começou a ampliar o alcance da pesquisa e a ignorar parte, como CEP ou cidade. É por isso que o documento de geocódigo se vangloria de que pode recolonizar o endereço com CEP incorreto, embora demorasse 3000 ms.
Com apenas 2 dados de estados carregados, o servidor levará muito menos tempo na pesquisa infrutífera ou em uma correspondência com pontuação muito baixa, porque só pode pesquisar em 2 estados.
Tentei limitar isso definindo o
restrict_region
parâmetro para multipolígonos de estado na função de geocódigo, esperando evitar a pesquisa infrutífera, pois tenho certeza de que a maioria dos endereços tem o estado correto. Compare estas duas versões:A única diferença feita pela segunda versão é que, normalmente, se eu executar a mesma consulta imediatamente novamente, será muito mais rápido porque os dados relacionados foram armazenados em cache, mas a segunda versão desativou esse efeito.
Portanto, o
restrict_region
não está funcionando como eu desejava, talvez apenas tenha sido usado para filtrar o resultado de vários hits, não para limitar os intervalos de pesquisa.Você pode ajustar um pouco o seu postgre conf.
O suspeito habitual de instalar índices ausentes, a análise de vácuo, não fez nenhuma diferença para mim, porque o script de download já fez a manutenção necessária, a menos que você tenha mexido com eles.
No entanto, definir o postgre conf de acordo com este post ajudou. Meu servidor em escala real com 50 estados estava com 320 ms com configuração padrão para alguns dados com pior formato, melhorou para 185 ms com 2G shared_buffer, cache 5G e foi para 100 ms ainda mais com a maioria das configurações ajustadas de acordo com esse post.
Isso é mais relevante para o postgis e suas configurações pareciam semelhantes.
O tamanho do lote de cada confirmação não importa muito para o meu caso. A documentação do geocódigo usava um tamanho de lote 3. Experimentei valores de 1, 3, 5 a 10. Não encontrei nenhuma diferença significativa com isso. Com um tamanho de lote menor, você faz mais confirmações e atualizações, mas acho que o gargalo real não está aqui. Na verdade, estou usando o lote 1 agora. Como sempre há algum endereço incorreto e inesperado causará exceção, definirei o lote inteiro com o erro como ignorado e continuarei pelas linhas restantes. Com o tamanho do lote 1, não preciso processar a tabela na segunda vez para codificar geograficamente os possíveis bons registros no lote marcado como ignorado.
Obviamente, isso depende de como o script em lote funciona. Vou postar meu script com mais detalhes mais tarde.
Você pode tentar usar o endereço de normalização para filtrar o endereço incorreto, se for adequado ao seu uso. Eu vi alguém mencionar isso em algum lugar, mas não tinha certeza de como isso funciona, pois a função normalizar funciona apenas no formato. Na verdade, não posso dizer qual endereço é inválido.
Mais tarde, percebi que se o endereço estiver obviamente em mau estado e você quiser ignorá-lo, isso poderá ajudar. Por exemplo, tenho muitos endereços com nomes de ruas ausentes ou mesmo nomes de ruas. A normalização de todos os endereços primeiro será relativamente rápida e, em seguida, você poderá filtrar o endereço incorreto óbvio e ignorá-los. No entanto, isso não combina com o meu uso, pois um endereço sem número de rua ou nome de rua ainda pode ser mapeado para a rua ou cidade, e essas informações ainda são úteis para mim.
E a maioria dos endereços que não podem ser geocodificados no meu caso realmente tem todos os campos, apenas não há correspondência no banco de dados. Você não pode filtrar esses endereços apenas normalizando-os.
EDITAR Para obter mais detalhes, consulte minha postagem no blog sobre a configuração do servidor de geocodificação e o script que eu usei .
EDIÇÃO 2 Concluí a geocodificação de 2 milhões de endereços e fiz muita limpeza nos endereços com base no resultado da geocodificação. Com uma entrada melhor limpa, o próximo trabalho em lotes está sendo executado muito mais rápido. Por limpeza, quero dizer que alguns endereços estão obviamente errados e devem ser removidos ou com conteúdo inesperado para o geocoder causar problemas na geocodificação. Minha teoria é: a remoção de endereços incorretos pode evitar atrapalhar o cache, o que melhora significativamente o desempenho em bons endereços.
Separei a entrada com base no estado para garantir que todo trabalho possa ter todos os dados necessários para geocodificação em cache na RAM. No entanto, todo endereço incorreto no trabalho faz com que o geocodificador procure em mais estados, o que pode atrapalhar o cache.
fonte
De acordo com este tópico de discussão , você deve usar o mesmo procedimento de normalização para processar os dados do Tiger e seu endereço de entrada. Como os dados do Tiger foram processados com o normalizador interno, é melhor usar apenas o normalizador interno. Mesmo que você tenha funcionado com o pagc_normalizer, ele pode não ser útil se você não o usar para atualizar os dados do Tiger.
Dito isto, acho que geocode () chamará o normalizador de qualquer maneira, para normalizar o endereço antes que a geocodificação possa não ser realmente útil. Um uso possível do normalizador pode ser comparar o endereço normalizado e o endereço retornado pelo geocode (). Com os dois normalizados, seria mais fácil encontrar o resultado incorreto de geocodificação.
Se você pode filtrar o endereço incorreto do geocode pelo normalizador, isso realmente ajudará. No entanto, não vejo que o normalizador tenha algo como pontuação ou classificação da partida.
O mesmo tópico de discussão também mencionou uma opção de depuração
geocode_address
para mostrar mais informações. O nógeocode_address
precisa de entrada de endereço normalizada.O geocoder é rápido para a correspondência exata, mas leva muito mais tempo para casos difíceis. Descobri que existe um parâmetro
restrict_region
e pensei que talvez ele limite a pesquisa infrutífera se eu definir o limite como estado, pois tenho certeza em qual estado ele estará. o endereço correto, embora demore algum tempo.Portanto, talvez o geocodificador procure em todos os lugares possíveis se a primeira pesquisa exata não corresponder. Isso permite processar entradas com alguns erros, mas também torna algumas pesquisas muito lentas.
Eu acho que é bom para um serviço interativo aceitar entradas com erros, mas às vezes podemos desistir de um pequeno conjunto de endereços errados para ter um melhor desempenho na geocodificação em lote.
fonte
restrict_region
no tempo quando você definiu o estado correto? Além disso, a partir do tópico postgis-users ao qual você vinculou acima, eles mencionam especificamente problemas com endereços como os1020 Highway 20
que eu encontrei também.Vou postar esta resposta, mas espero que outro colaborador ajude a detalhar o seguinte, o que acho que criará uma imagem mais coerente:
Agora minha resposta, que é apenas uma anedota:
O melhor que estou recebendo (com base em uma única conexão) é uma média de 208 ms por
geocode
. Isso é medido pela seleção aleatória de endereços do meu conjunto de dados, que se estende pelos EUA. Inclui alguns dados sujos, mas os mais antigosgeocode
não parecem ruins de maneiras óbvias.A essência disso é que pareço estar vinculado à CPU e que uma única consulta está vinculada a um único processador. Eu posso paralelizar isso tendo várias conexões em execução com
UPDATE
segmentos complementares daaddresses_to_geocode
tabela em teoria. Enquanto isso, estou tendogeocode
uma média de 208 ms no conjunto de dados em todo o país. A distribuição é distorcida tanto em termos de onde está a maioria dos meus endereços quanto em quanto tempo eles estão demorando (por exemplo, veja o histograma acima) e a tabela abaixo.Até agora, minha melhor abordagem é fazer isso em lotes de 10000, com algumas melhorias estimadas em fazer mais por lote. Para lotes de 100, eu estava recebendo cerca de 251ms, com 10000, obtendo 208ms.
Preciso citar nomes de campos por causa de como o RPostgreSQL cria as tabelas com
dbWriteTable
Isso é cerca de 4x mais rápido do que se eu fizesse um registro de cada vez. Quando eu os faço um de cada vez, posso obter uma divisão por estado (veja abaixo). Fiz isso para verificar e verificar se um ou mais dos estados do TIGER tinham uma carga ou índice ruim, o que eu esperava resultar em um
geocode
desempenho ruim no estado. Obviamente, tenho alguns dados ruins (alguns endereços são mesmo endereços de email!), Mas a maioria deles está bem formatada. Como eu disse antes, algumas das consultas mais antigas não apresentam deficiências óbvias em seu formato. Abaixo está uma tabela do número, tempo mínimo de consulta, tempo médio de consulta e tempo máximo de consulta para estados de 3000 - alguns endereços aleatórios do meu conjunto de dados:fonte