À quelle vitesse devrais-je m'attendre à ce que PostGIS géocode des adresses bien formatées?
J'ai installé PostgreSQL 9.3.7 et PostGIS 2.1.7, chargé les données nationales et toutes les données des États, mais j'ai trouvé que le géocodage était beaucoup plus lent que prévu. Ai-je placé mes attentes trop haut? J'obtiens en moyenne 3 géocodages individuels par seconde. Je dois en faire environ 5 millions et je ne veux pas attendre trois semaines pour cela.
Il s'agit d'une machine virtuelle pour le traitement des matrices R géantes et j'ai installé cette base de données sur le côté afin que la configuration puisse paraître un peu maladroite. Si une modification majeure de la configuration de la VM peut aider, je peux modifier la configuration.
Spécifications matérielles
Mémoire: processeurs de 65 Go: 6
lscpu
me donne ceci:
# 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 is centos, uname -rv
donne ceci:
# uname -rv
2.6.32-504.16.2.el6.x86_64 #1 SMP Wed Apr 22 06:48:29 UTC 2015
Configuration 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"
Sur la base des suggestions précédentes concernant ces types de requêtes, j'ai augmenté shared_buffers
le postgresql.conf
fichier à environ 1/4 de la RAM disponible et la taille du cache efficace à la moitié de la RAM:
shared_buffers = 16096MB
effective_cache_size = 31765MB
J'ai installed_missing_indexes()
et (après avoir résolu les insertions en double dans certaines tables) je n'ai eu aucune erreur.
Exemple de géocodage SQL # 1 (batch) ~ le temps moyen est de 2,8 / sec
Je suis l'exemple de http://postgis.net/docs/Geocode.html , qui m'a fait créer une table contenant l'adresse à géocoder, puis faire 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";
J'utilise une taille de lot de 1000 au-dessus et elle revient en 337,70 secondes. C'est un peu plus lent pour les petits lots.
Exemple de géocodage SQL # 2 (ligne par ligne) ~ le temps moyen est de 1,2 / sec
Lorsque je fouille dans mes adresses en faisant les géocodes un à la fois avec une déclaration qui ressemble à ceci (btw, l'exemple ci-dessous a pris 4,14 secondes),
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;
c'est un peu plus lent (2,5 fois par enregistrement) mais je peux regarder la distribution des temps de requête et voir que c'est une minorité de longues requêtes qui ralentissent le plus (seuls les premiers 2600 sur 5 millions ont des temps de recherche). Autrement dit, les 10% supérieurs prennent en moyenne environ 100 ms, les 10% inférieurs en moyenne 3,69 secondes, tandis que la moyenne est de 754 ms et la médiane est de 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]
D'autres pensées
Si je ne peux pas obtenir un ordre de grandeur d'augmentation des performances, je pensais que je pourrais au moins faire une supposition éclairée sur la prévision des temps de géocodage lents, mais il n'est pas évident pour moi pourquoi les adresses plus lentes semblent prendre beaucoup plus de temps. J'exécute l'adresse d'origine via une étape de normalisation personnalisée pour m'assurer qu'elle est bien formatée avant que la geocode()
fonction ne l'obtienne:
sql=paste0("select pprint_addy(normalize_address('",myAddress,"'))")
où myAddress
est une [Address], [City], [ST] [Zip]
chaîne compilée à partir d'une table d'adresses utilisateur à partir d'une base de données non postgresql.
J'ai essayé (échoué) d'installer l' pagc_normalize_address
extension mais il n'est pas clair que cela apportera le type d'amélioration que je recherche.
Modifié pour ajouter des informations de surveillance selon la suggestion
Performance
Un processeur est indexé: [modifier, un seul processeur par requête, j'ai donc 5 processeurs inutilisés]
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
Exemple d'activité de disque sur la partition de données pendant qu'un proc est indexé à 100%: [modifier: un seul processeur utilisé par cette requête]
# 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
Analysez ce SQL
C'est à partir EXPLAIN ANALYZE
de cette requête:
"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"
Voir une meilleure répartition sur http://explain.depesz.com/s/vogS
Réponses:
J'ai passé beaucoup de temps à expérimenter cela, je pense qu'il vaut mieux publier séparément car ils sont sous un angle différent.
C'est vraiment un sujet complexe, voir plus de détails dans mon article de blog sur la configuration du serveur de géocodage et le script que j'ai utilisé ., Voici juste quelques brefs résumés:
Un serveur avec seulement 2 données d'états est toujours plus rapide qu'un serveur chargé avec les 50 données d'états.
J'ai vérifié cela avec mon ordinateur personnel à différents moments et avec deux serveurs Amazon AWS différents.
Mon serveur de niveau gratuit AWS avec 2 états de données n'a que 1 Go de RAM, mais il a des performances cohérentes de 43 à 59 ms pour les données avec 1 000 enregistrements et 45 000 enregistrements.
J'ai utilisé exactement la même procédure de configuration pour un serveur AWS 8G RAM avec tous les états chargés, exactement le même script et les mêmes données, et les performances ont chuté à 80 ~ 105 ms.
Ma théorie est que lorsque le géocodeur ne peut pas correspondre exactement à l'adresse, il a commencé à élargir la plage de recherche et à ignorer une partie, comme le code postal ou la ville. C'est pourquoi le document de géocodage se vante qu'il peut recoloniser l'adresse avec un mauvais code postal, bien qu'il ait fallu 3000 ms.
Avec seulement 2 états chargés, le serveur prendra beaucoup moins de temps dans une recherche infructueuse ou une correspondance avec un score très faible, car il ne peut rechercher que dans 2 états.
J'ai essayé de limiter cela en définissant le
restrict_region
paramètre sur les multipolygones d'état dans la fonction de géocodage, en espérant que cela éviterait la recherche infructueuse car je suis presque sûr que la plupart des adresses ont un état correct. Comparez ces deux versions:La seule différence apportée par la deuxième version est que, normalement, si j'exécute à nouveau la même requête immédiatement, cela sera beaucoup plus rapide car les données associées ont été mises en cache, mais la deuxième version a désactivé cet effet.
Donc, cela
restrict_region
ne fonctionne pas comme je le souhaitais, peut-être a-t-il simplement été utilisé pour filtrer le résultat de plusieurs hits, pas pour limiter les plages de recherche.Vous pouvez régler un peu votre conf postgregre.
Le suspect habituel d'installer des index manquants, l'analyse sous vide n'a pas fait de différence pour moi, car le script de téléchargement a déjà effectué la maintenance nécessaire, sauf si vous les avez gâchés.
Cependant, la configuration de postgre conf selon ce post a aidé. Mon serveur à grande échelle avec 50 états avait 320 ms avec une configuration par défaut pour certaines données de forme pire, il s'est amélioré à 185 ms avec 2G shared_buffer, 5G cache, et est allé à 100 ms plus loin avec la plupart des paramètres ajustés selon ce post.
Ceci est plus pertinent pour les postgis et leurs paramètres semblaient similaires.
La taille du lot de chaque commit importait peu pour mon cas. La documentation du géocodage a utilisé une taille de lot 3. J'ai expérimenté des valeurs de 1, 3, 5 à 10. Je n'ai trouvé aucune différence significative avec cela. Avec une taille de lot plus petite, vous effectuez plus de validations et de mises à jour, mais je pense que le vrai col de la bouteille n'est pas là. En fait, j'utilise maintenant la taille de lot 1. Parce qu'il y a toujours une adresse mal formée inattendue qui provoquera une exception, je définirai le lot entier avec erreur comme ignoré et continuerai pour les lignes restantes. Avec la taille de lot 1, je n'ai pas besoin de traiter la table une deuxième fois pour géocoder les bons enregistrements possibles du lot marqués comme ignorés.
Bien sûr, cela dépend de la façon dont fonctionne votre script de commandes. Je posterai mon script avec plus de détails plus tard.
Vous pouvez essayer d'utiliser normaliser l'adresse pour filtrer les mauvaises adresses si cela convient à votre utilisation. J'ai vu quelqu'un mentionner cela quelque part, mais je ne savais pas comment cela fonctionne puisque la fonction de normalisation ne fonctionne qu'au format, elle ne peut pas vraiment vous dire quelle adresse n'est pas valide.
Plus tard, j'ai réalisé que si l'adresse est manifestement en mauvais état et que vous souhaitez les ignorer, cela pourrait aider. Par exemple, il manque beaucoup d'adresses au nom de la rue ou même aux noms de rue. Normaliser toutes les adresses en premier sera relativement rapide, puis vous pouvez filtrer la mauvaise adresse évidente pour vous, puis les ignorer. Cependant, cela ne convenait pas à mon utilisation, car une adresse sans numéro de rue ou même nom de rue pouvait toujours être mappée sur la rue ou la ville, et ces informations sont toujours utiles pour moi.
Et la plupart des adresses qui ne peuvent pas être géocodées dans mon cas ont en fait tous les champs, mais il n'y a aucune correspondance dans la base de données. Vous ne pouvez pas filtrer ces adresses simplement en les normalisant.
EDIT Pour plus de détails, consultez mon article de blog sur la configuration du serveur de géocodage et le script que j'ai utilisé .
EDIT 2 J'ai fini de géocoder 2 millions d'adresses et j'ai fait beaucoup de nettoyage sur les adresses en fonction du résultat du géocodage. Avec une meilleure entrée nettoyée, le prochain travail par lots s'exécute beaucoup plus rapidement. Par nettoyer, je veux dire que certaines adresses sont manifestement erronées et doivent être supprimées, ou avoir un contenu inattendu pour que le géocodeur cause un problème de géocodage. Ma théorie est la suivante: la suppression des mauvaises adresses peut éviter de gâcher le cache, ce qui améliore considérablement les performances des bonnes adresses.
J'ai séparé l'entrée en fonction de l'état pour m'assurer que chaque travail peut avoir toutes les données nécessaires pour le géocodage mises en cache dans la RAM. Cependant, chaque mauvaise adresse dans le travail oblige le géocodeur à rechercher dans plus d'états, ce qui pourrait gâcher le cache.
la source
Selon ce fil de discussion , vous êtes censé utiliser la même procédure de normalisation pour traiter les données Tiger et votre adresse d'entrée. Étant donné que les données Tiger ont été traitées avec le normalisateur intégré, il est préférable d'utiliser uniquement le normalisateur intégré. Même si vous avez fait fonctionner pagc_normalizer, cela peut ne pas vous aider si vous ne l'utilisez pas pour mettre à jour les données Tiger.
Cela étant dit, je pense que geocode () appellera de toute façon le normalisateur, donc normaliser l'adresse avant que le géocodage ne soit pas vraiment utile. Une utilisation possible du normalisateur pourrait être de comparer l'adresse normalisée et l'adresse renvoyée par geocode (). Avec les deux normalisés, il pourrait être plus facile de trouver le mauvais résultat de géocodage.
Si vous pouvez filtrer la mauvaise adresse hors du géocodage par le normalisateur, cela sera vraiment utile. Cependant, je ne vois pas que le normalisateur ait quelque chose comme un score ou une note de match.
Le même fil de discussion a également mentionné un commutateur de débogage
geocode_address
pour afficher plus d'informations. Le nœud ageocode_address
besoin d'une entrée d'adresse normalisée.Le géocodeur est rapide pour une correspondance exacte mais prend beaucoup plus de temps pour les cas difficiles. J'ai trouvé qu'il y a un paramètre
restrict_region
et j'ai pensé que cela limiterait peut-être la recherche infructueuse si je définissais la limite en tant qu'état, car je suis pratiquement sûr de l'état dans lequel il se trouverait. l'adresse correcte, même si cela prend un certain temps.Alors peut-être que le géocodeur cherchera dans tous les endroits possibles si la première recherche exacte ne correspond pas. Cela permet de traiter les entrées avec quelques erreurs, mais aussi de ralentir la recherche.
Je pense que c'est bon pour un service interactif d'accepter des entrées avec des erreurs, mais parfois nous pouvons vouloir abandonner un petit ensemble de mauvaises adresses pour avoir de meilleures performances dans le géocodage par lots.
la source
restrict_region
sur le timing lorsque vous avez défini le bon état? De plus, à partir du fil de discussion postgis-users auquel vous avez lié ci-dessus, ils mentionnent spécifiquement avoir des problèmes avec des adresses comme celles1020 Highway 20
que j'ai rencontrées également.Je vais publier cette réponse mais j'espère qu'un autre contributeur aidera à décomposer les éléments suivants, ce qui, je pense, brossera un tableau plus cohérent:
Maintenant ma réponse, qui n'est qu'une anecdote:
Le meilleur que j'obtiens (basé sur une seule connexion) est une moyenne de 208 ms par
geocode
. Cela est mesuré en sélectionnant des adresses au hasard dans mon ensemble de données, qui s'étend aux États-Unis. Il comprend des données sales mais lesgeocode
s les plus anciens ne semblent pas être mauvais de manière évidente.L'essentiel est que je semble être lié au processeur et qu'une seule requête est liée à un seul processeur. Je peux paralléliser cela en faisant fonctionner plusieurs connexions avec
UPDATE
se produisant sur des segments complémentaires de laaddresses_to_geocode
table en théorie. En attendant, je reçoisgeocode
en moyenne 208 ms sur l'ensemble de données national. La distribution est biaisée à la fois en termes de localisation de la plupart de mes adresses et de durée (par exemple, voir l'histogramme ci-dessus) et dans le tableau ci-dessous.Jusqu'à présent, ma meilleure approche consiste à le faire par lots de 10000, avec une amélioration estimable par rapport à la production de plus par lot. Pour des lots de 100, j'obtenais environ 251 ms, avec 10000 j'obtiens 208 ms.
Je dois citer des noms de champs à cause de la façon dont RPostgreSQL crée les tables avec
dbWriteTable
C'est à peu près 4 fois plus vite que si je les enregistre un disque à la fois. Quand je les fais un à la fois, je peux obtenir une ventilation par état (voir ci-dessous). J'ai fait cela pour vérifier et voir si un ou plusieurs des états TIGER avaient une mauvaise charge ou un mauvais indice, ce qui, je m'attendais à entraîner de mauvaises
geocode
performances au niveau de l'état. J'ai évidemment de mauvaises données (certaines adresses sont même des adresses e-mail!), Mais la plupart sont bien formatées. Comme je l'ai déjà dit, certaines des requêtes les plus anciennes n'ont pas de lacunes évidentes dans leur format. Vous trouverez ci-dessous un tableau du nombre, du temps de requête minimum, du temps de requête moyen et du temps de requête maximum pour les états de 3000 adresses aléatoires de mon ensemble de données:la source