¿Qué tan rápido debo esperar que PostGIS geocodifique direcciones bien formateadas?
Instalé PostgreSQL 9.3.7 y PostGIS 2.1.7, cargué los datos de la nación y todos los datos de los estados, pero he encontrado que la geocodificación es mucho más lenta de lo que esperaba. ¿Puse mis expectativas demasiado altas? Estoy obteniendo un promedio de 3 códigos geográficos individuales por segundo. Necesito hacer unos 5 millones y no quiero esperar tres semanas para esto.
Esta es una máquina virtual para procesar matrices R gigantes e instalé esta base de datos en el lateral para que la configuración pueda parecer un poco tonta. Si una alteración importante en la configuración de la VM ayudará, puedo alterar la configuración.
Especificaciones de hardware
Memoria: procesadores de 65 GB: 6
lscpu
me da esto:
# 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 es centos, uname -rv
da esto:
# uname -rv
2.6.32-504.16.2.el6.x86_64 #1 SMP Wed Apr 22 06:48:29 UTC 2015
Postgresql config
> 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"
Según las sugerencias anteriores para este tipo de consultas, aumenté shared_buffers
el postgresql.conf
archivo a aproximadamente 1/4 de RAM disponible y el tamaño efectivo de caché a 1/2 de RAM:
shared_buffers = 16096MB
effective_cache_size = 31765MB
Tengo installed_missing_indexes()
y (después de resolver inserciones duplicadas en algunas tablas) no tuve ningún error.
Ejemplo de codificación geográfica SQL # 1 (lote) ~ el tiempo medio es 2.8 / seg
Estoy siguiendo el ejemplo de http://postgis.net/docs/Geocode.html , que me hace crear una tabla que contiene la dirección para geocodificar, y luego hacer un 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";
Estoy usando un tamaño de lote de 1000 arriba y regresa en 337.70 segundos. Es un poco más lento para lotes más pequeños.
Ejemplo de SQL de codificación geográfica # 2 (fila por fila) ~ el tiempo medio es 1.2 / seg
Cuando busco en mis direcciones haciendo los códigos geográficos uno a la vez con una declaración que se ve así (por cierto, el ejemplo a continuación tomó 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;
es un poco más lento (2.5x por registro), pero puedo ver la distribución de los tiempos de consulta y ver que es una minoría de consultas largas las que lo ralentizan más (solo los primeros 2600 de 5 millones tienen tiempos de búsqueda). Es decir, el 10% superior está tomando un promedio de aproximadamente 100 ms, el 10% inferior promedio 3.69 segundos, mientras que la media es 754 ms y la mediana es 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]
Otros pensamientos
Si no puedo obtener un aumento de orden de magnitud en el rendimiento, pensé que al menos podría hacer una suposición educada sobre la predicción de tiempos lentos de geocodificación, pero no es obvio para mí por qué las direcciones más lentas parecen estar tardando mucho más. Estoy ejecutando la dirección original a través de un paso de normalización personalizado para asegurarme de que esté bien formateado antes de que la geocode()
función lo obtenga:
sql=paste0("select pprint_addy(normalize_address('",myAddress,"'))")
donde myAddress
es una [Address], [City], [ST] [Zip]
cadena compilada de una tabla de direcciones de usuario de una base de datos no postgresql.
Intenté (fallé) instalar la pagc_normalize_address
extensión, pero no está claro que esto traerá el tipo de mejora que estoy buscando.
Editado para agregar información de monitoreo según la sugerencia
Actuación
Una CPU está vinculada: [editar, solo un procesador por consulta, por lo que tengo 5 CPU no 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
Muestra de actividad de disco en la partición de datos mientras un proceso está vinculado al 100%: [editar: solo un procesador en 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
Analiza ese SQL
Esto es de EXPLAIN ANALYZE
esa 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"
Vea un mejor desglose en http://explain.depesz.com/s/vogS
Respuestas:
He pasado mucho tiempo experimentando con esto, creo que es mejor publicar por separado ya que son desde un ángulo diferente.
Este es realmente un tema complejo, vea más detalles en la publicación de mi blog sobre la configuración del servidor de geocodificación y el script que utilicé . Aquí hay algunos breves resúmenes:
Un servidor con datos de solo 2 estados siempre es más rápido que un servidor cargado con los datos de 50 estados.
Verifiqué esto con mi PC doméstica en diferentes momentos y dos servidores diferentes de Amazon AWS.
Mi servidor de nivel gratuito AWS con datos de 2 estados tiene solo 1G de RAM, pero tiene un rendimiento constante de 43 ~ 59 ms para datos con 1000 registros y 45,000 registros.
Utilicé exactamente el mismo procedimiento de configuración para un servidor AWS 8G RAM con todos los estados cargados, exactamente el mismo script y datos, y el rendimiento se redujo a 80 ~ 105 ms.
Mi teoría es que cuando el geocodificador no puede coincidir exactamente con la dirección, comenzó a ampliar el rango de búsqueda e ignorar alguna parte, como el código postal o la ciudad. Es por eso que el documento de geocodificación se jacta de que puede recolonizar la dirección con un código postal incorrecto, aunque tomó 3000 ms.
Con solo 2 datos de estados cargados, el servidor tomará mucho menos tiempo en búsquedas infructuosas o una coincidencia con puntaje muy bajo, ya que solo puede buscar en 2 estados.
Intenté limitar esto estableciendo el
restrict_region
parámetro en los multipolígonos de estado en la función de geocodificación, con la esperanza de evitar la búsqueda infructuosa, ya que estoy bastante seguro de que la mayoría de las direcciones tienen el estado correcto. Compare estas dos versiones:La única diferencia que hace la segunda versión es que, normalmente, si ejecuto la misma consulta de inmediato, será mucho más rápido porque los datos relacionados se almacenaron en caché, pero la segunda versión deshabilitó este efecto.
Entonces,
restrict_region
no funciona como deseaba, tal vez solo se usó para filtrar el resultado de múltiples aciertos, no para limitar los rangos de búsqueda.Puede ajustar un poco su configuración de postgre.
El sospechoso habitual de instalar índices faltantes, el análisis de vacío no hizo ninguna diferencia para mí, porque la secuencia de comandos de descarga ya ha realizado el mantenimiento necesario, a menos que se haya equivocado con ellos.
Sin embargo, establecer postgre conf de acuerdo con esta publicación ayudó. Mi servidor de escala completa con 50 estados tenía 320 ms con la configuración predeterminada para algunos datos con peor forma, mejoró a 185 ms con 2G shared_buffer, 5G cache, y fue a 100 ms más con la mayoría de las configuraciones ajustadas de acuerdo con esa publicación.
Esto es más relevante para los postgis y su configuración parece ser similar.
El tamaño del lote de cada confirmación no importó mucho para mi caso. La documentación de geocodificación utilizaba un tamaño de lote 3. Experimenté valores desde 1, 3, 5 hasta 10. No encontré ninguna diferencia significativa con esto. Con un tamaño de lote más pequeño, realiza más confirmaciones y actualizaciones, pero creo que el cuello de botella real no está aquí. En realidad estoy usando el tamaño de lote 1 ahora. Como siempre hay alguna dirección mal formada inesperada que causará una excepción, estableceré todo el lote con error como ignorado y procederé para las filas restantes. Con el tamaño de lote 1 no necesito procesar la tabla la segunda vez para geocodificar los posibles registros buenos en el lote marcado como ignorado.
Por supuesto, esto depende de cómo funcione su script por lotes. Publicaré mi script con más detalles más adelante.
Puede intentar utilizar la dirección normalizada para filtrar la dirección incorrecta si se ajusta a su uso. Vi que alguien mencionó esto en alguna parte, pero no estaba seguro de cómo funciona, ya que la función normalizar solo funciona en formato, realmente no puede decir qué dirección es inválida.
Más tarde, me di cuenta de que si la dirección está obviamente en mal estado y desea omitirla, esto podría ayudar. Por ejemplo, tengo muchas direcciones que faltan nombres de calles o incluso nombres de calles. Normalizar todas las direcciones primero será relativamente rápido, luego puede filtrar la dirección incorrecta obvia para usted y luego omitirlas. Sin embargo, esto no se ajustaba a mi uso, ya que una dirección sin número de calle o incluso nombre de la calle aún podría mapearse a la calle o la ciudad, y esa información todavía es útil para mí.
Y la mayoría de las direcciones que no se pueden geocodificar en mi caso en realidad tienen todos los campos, solo que no hay coincidencia en la base de datos. No puede filtrar estas direcciones simplemente normalizándolas.
EDITAR Para obtener más detalles, consulte la publicación de mi blog sobre la configuración del servidor de geocodificación y el script que utilicé .
EDITAR 2 He terminado de geocodificar 2 millones de direcciones e hice muchas limpiezas de direcciones en función del resultado de geocodificación. Con una entrada mejor limpia, el siguiente trabajo por lotes se ejecuta mucho más rápido. Por limpio quiero decir que algunas direcciones son obviamente incorrectas y deben eliminarse, o tener contenido inesperado para que el geocodificador cause problemas en la geocodificación. Mi teoría es: eliminar las direcciones incorrectas puede evitar estropear la memoria caché, lo que mejora significativamente el rendimiento de las direcciones correctas.
Separé la entrada según el estado para asegurarme de que cada trabajo pueda tener todos los datos necesarios para la geocodificación en caché en la RAM. Sin embargo, cada dirección incorrecta en el trabajo hace que el geocodificador busque en más estados, lo que podría dañar el caché.
fuente
De acuerdo con este hilo de discusión , se supone que debe utilizar el mismo procedimiento de normalización para procesar los datos de Tiger y su dirección de entrada. Dado que los datos de Tiger se han procesado con el normalizador incorporado, es mejor usar solo el normalizador incorporado. Incluso si consiguió que pagc_normalizer funcionara, es posible que no lo ayude si no lo usa para actualizar los datos de Tiger.
Dicho esto, creo que geocode () llamará al normalizador de todos modos, por lo que normalizar la dirección antes de la geocodificación puede no ser realmente útil. Un posible uso del normalizador podría ser comparar la dirección normalizada y la dirección devuelta por geocode (). Con ambos normalizados, podría ser más fácil encontrar el resultado de geocodificación incorrecto.
Si puede filtrar la dirección incorrecta del geocódigo por normalizador, eso realmente ayudará. Sin embargo, no veo que el normalizador tenga nada como un puntaje o calificación de partido.
El mismo hilo de discusión también mencionó un cambio de depuración
geocode_address
para mostrar más información. El nodogeocode_address
necesita una entrada de dirección normalizada.Geocoder es rápido para la coincidencia exacta, pero toma mucho más tiempo para casos difíciles. Descubrí que hay un parámetro
restrict_region
y pensé que tal vez limitaría la búsqueda infructuosa si configuro el límite como estado, ya que estoy bastante seguro de en qué estado estará. Resultó que configurarlo en un estado incorrecto no detuvo el geocodificación para obtener la dirección correcta, aunque lleva algún tiempo.Entonces, tal vez el geocodificador buscará en todos los lugares posibles si la primera búsqueda exacta no tiene coincidencia. Esto hace que pueda procesar la entrada con algunos errores, pero también hace que la búsqueda sea muy lenta.
Creo que es bueno que un servicio interactivo acepte entradas con errores, pero a veces es posible que deseemos renunciar a un pequeño conjunto de direcciones incorrectas para tener un mejor rendimiento en la geocodificación por lotes.
fuente
restrict_region
tiempo cuando configuró el estado correcto? Además, del hilo postgis-users al que vinculaste anteriormente, mencionan específicamente tener problemas con direcciones como las1020 Highway 20
que encontré también.Voy a publicar esta respuesta, pero espero que otro colaborador ayude a desglosar lo siguiente, que creo que pintará una imagen más coherente:
Ahora mi respuesta, que es solo una anécdota:
Lo mejor que obtengo (basado en una sola conexión) es un promedio de 208 ms por
geocode
. Esto se mide seleccionando direcciones al azar de mi conjunto de datos, que se extiende a través de los EE. UU. Incluye algunos datos sucios, pero los correos electrónicos más antiguosgeocode
no parecen ser malos de manera obvia.La esencia de esto es que parezco estar vinculado a la CPU y que una sola consulta está vinculada a un único procesador. Puedo paralelizar esto al tener múltiples conexiones ejecutándose
UPDATE
en segmentos complementarios de laaddresses_to_geocode
tabla en teoría. Mientras tanto, voygeocode
a tomar un promedio de 208 ms en el conjunto de datos a nivel nacional. La distribución está sesgada tanto en términos de dónde están la mayoría de mis direcciones como en términos de cuánto tiempo están tomando (por ejemplo, vea el histograma anterior) y la tabla a continuación.Mi mejor enfoque hasta ahora es hacerlo en lotes de 10000, con alguna mejora estimable de hacer más por lote. Para lotes de 100 obtenía unos 251 ms, con 10000 obtenía 208 ms.
Tengo que citar nombres de campo debido a cómo RPostgreSQL crea las tablas con
dbWriteTable
Eso es aproximadamente 4 veces más rápido que si los hago un registro a la vez. Cuando los hago uno a la vez, puedo obtener un desglose por estado (ver más abajo). Hice esto para verificar y ver si uno o más de los estados TIGER tenían una carga o índice incorrectos, lo que esperaba que tuviera un bajo
geocode
rendimiento en el estado. Obviamente tengo algunos datos incorrectos (¡algunas direcciones son incluso direcciones de correo electrónico!), Pero la mayoría de ellos están bien formateados. Como dije antes, algunas de las consultas más largas no tienen deficiencias obvias en su formato. A continuación se muestra una tabla del número, el tiempo mínimo de consulta, el tiempo medio de consulta y el tiempo máximo de consulta para estados de 3000-algunas direcciones aleatorias de mi conjunto de datos:fuente