Wie schnell sollte ich damit rechnen, dass PostGIS gut formatierte Adressen geocodiert?
Ich habe PostgreSQL 9.3.7 und PostGIS 2.1.7 installiert, die Länderdaten und alle Zustandsdaten geladen, aber die Geokodierung ist viel langsamer als erwartet. Habe ich meine Erwartungen zu hoch gesetzt? Ich erhalte durchschnittlich 3 einzelne Geocodes pro Sekunde. Ich muss ungefähr 5 Millionen machen und ich möchte nicht drei Wochen darauf warten.
Dies ist eine virtuelle Maschine für die Verarbeitung von Riesen-R-Matrizen, und ich habe diese Datenbank auf der Seite installiert, damit die Konfiguration möglicherweise etwas doof aussieht. Wenn eine größere Änderung in der Konfiguration der VM hilfreich ist, kann ich die Konfiguration ändern.
Hardware-Spezifikationen
Speicher: 65 GB Prozessoren: 6 lscpu
gibt mir Folgendes
:
# 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 ist Centos, uname -rv
gibt dies:
# uname -rv
2.6.32-504.16.2.el6.x86_64 #1 SMP Wed Apr 22 06:48:29 UTC 2015
Postgresql-Konfig
> 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"
Basierend auf früheren Vorschlägen für diese Art von Abfragen habe ich shared_buffers
in der postgresql.conf
Datei ungefähr 1/4 des verfügbaren Arbeitsspeichers und die effektive Cache-Größe auf 1/2 des Arbeitsspeichers erhöht :
shared_buffers = 16096MB
effective_cache_size = 31765MB
Ich habe installed_missing_indexes()
und (nach dem Auflösen doppelter Einfügungen in einige Tabellen) keine Fehler gehabt.
Geocodierungs-SQL-Beispiel Nr. 1 (Batch) ~ Die durchschnittliche Zeit beträgt 2,8 / s
Ich folge dem Beispiel von http://postgis.net/docs/Geocode.html , bei dem ich eine Tabelle mit der Adresse zum Geocodieren erstelle und dann eine SQL durchführe 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";
Ich verwende eine Stapelgröße von 1000 über und es kehrt in 337.70 Sekunden zurück. Bei kleineren Chargen ist es etwas langsamer.
Geokodierungs-SQL-Beispiel Nr. 2 (Zeile für Zeile) ~ Die durchschnittliche Zeit beträgt 1,2 / Sek
Wenn ich in meine Adressen grabe, indem ich die Geokodierungen nacheinander mit einer Anweisung durchführe, die so aussieht (übrigens hat das folgende Beispiel 4,14 Sekunden gedauert),
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 ist etwas langsamer (2,5x pro Datensatz), aber ich kann die Verteilung der Abfragezeiten betrachten und feststellen, dass es eine Minderheit von langwierigen Abfragen ist, die dies am meisten verlangsamen (nur die ersten 2600 von 5 Millionen haben Nachschlagezeiten). Das heißt, die oberen 10% benötigen durchschnittlich etwa 100 ms, die unteren 10% durchschnittlich 3,69 Sekunden, während der Mittelwert 754 ms und der Median 340 ms beträgt.
# 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]
Andere Gedanken
Wenn ich die Leistung nicht um eine Größenordnung steigern kann, habe ich gedacht, ich könnte zumindest eine fundierte Vermutung über die Vorhersage langsamer Geocode-Zeiten anstellen, aber es ist mir nicht klar, warum die langsameren Adressen so viel länger zu dauern scheinen. Ich führe die ursprüngliche Adresse durch einen benutzerdefinierten Normalisierungsschritt, um sicherzustellen, dass sie ordnungsgemäß formatiert ist, bevor sie von der geocode()
Funktion abgerufen wird:
sql=paste0("select pprint_addy(normalize_address('",myAddress,"'))")
Dabei myAddress
handelt es sich um eine [Address], [City], [ST] [Zip]
Zeichenfolge, die aus einer Benutzeradressentabelle aus einer Nicht-Postgresql-Datenbank kompiliert wurde.
Ich habe versucht (gescheitert), die pagc_normalize_address
Erweiterung zu installieren, aber es ist nicht klar, dass dies die Art von Verbesserung bringt, die ich suche.
Bearbeitet, um Überwachungsinformationen gemäß Vorschlag hinzuzufügen
Performance
Eine CPU ist gekoppelt: [Bearbeiten, nur ein Prozessor pro Abfrage, daher habe ich 5 nicht verwendete CPUs]
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
Beispiel für die Festplattenaktivität auf der Datenpartition, während ein Prozessor zu 100% gebunden ist: [Bearbeiten: Nur ein Prozessor wird von dieser Abfrage verwendet.]
# 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
Analysieren Sie diese SQL
Dies ist aus EXPLAIN ANALYZE
dieser Abfrage:
"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"
Eine bessere Aufschlüsselung finden Sie unter http://explain.depesz.com/s/vogS
Antworten:
Ich habe viel Zeit damit verbracht, damit zu experimentieren. Ich denke, es ist besser, separat zu posten, da sie aus verschiedenen Blickwinkeln stammen.
Dies ist wirklich ein komplexes Thema. Weitere Informationen zum Geokodierungsserver-Setup und zum verwendeten Skript finden Sie in meinem Blog-Beitrag . Hier sind nur einige kurze Zusammenfassungen:
Ein Server mit nur 2 Statusdaten ist immer schneller als ein Server mit allen 50 Statusdaten.
Ich habe dies mit meinem Heimcomputer zu verschiedenen Zeiten und auf zwei verschiedenen Amazon AWS-Servern überprüft.
Mein AWS Free Tier-Server mit 2 Statusdaten hat nur 1 GB RAM, aber eine konsistente Leistung von 43 bis 59 ms für Daten mit 1000 Datensätzen und 45.000 Datensätzen.
Ich habe genau das gleiche Setup-Verfahren für einen 8G RAM AWS-Server mit allen geladenen Status verwendet, genau das gleiche Skript und die gleichen Daten, und die Leistung ist auf 80 ~ 105 ms gesunken.
Nach meiner Theorie hat der Geocodierer, wenn er nicht genau mit der Adresse übereinstimmen kann, den Suchbereich erweitert und einen Teil wie die Postleitzahl oder den Ort ignoriert. Aus diesem Grund kann das Geocode-Dokument Adressen mit falscher Postleitzahl rekolonisieren, obwohl es 3000 ms gedauert hat.
Wenn nur 2 Statusdaten geladen sind, benötigt der Server viel weniger Zeit für eine erfolglose Suche oder eine Übereinstimmung mit einer sehr niedrigen Punktzahl, da nur in 2 Status gesucht werden kann.
Ich habe versucht, dies
restrict_region
einzuschränken, indem ich den Parameter in der Geocode-Funktion auf das Statusmultipolygon gesetzt habe, in der Hoffnung, dass die erfolglose Suche vermieden wird, da ich mir ziemlich sicher bin, dass die meisten Adressen den korrekten Status haben. Vergleichen Sie diese beiden Versionen:Der einzige Unterschied, den die zweite Version macht, besteht darin, dass ich normalerweise die gleiche Abfrage sofort wieder starte, da die zugehörigen Daten im Cache gespeichert wurden. Die zweite Version hat diesen Effekt jedoch deaktiviert.
Das
restrict_region
funktioniert also nicht so, wie ich es mir gewünscht habe. Vielleicht wurde es nur verwendet, um das Ergebnis mehrerer Treffer zu filtern und nicht, um die Suchbereiche einzuschränken.Sie können Ihr Postgre-Conf ein wenig optimieren.
Der übliche Verdacht, fehlende Indizes zu installieren, machte für mich keinen Unterschied, da die Download-Skripte die notwendigen Wartungsarbeiten bereits durchgeführt haben, es sei denn, Sie haben sie durcheinander gebracht.
Es hat jedoch geholfen, postgre conf gemäß diesem Beitrag festzulegen . Mein Full-Scale-Server mit 50 Status hatte 320 ms mit Standardkonfiguration für einige schlecht geformte Daten, verbesserte sich auf 185 ms mit 2G shared_buffer, 5G Cache und ging auf 100 ms weiter, wobei die meisten Einstellungen gemäß diesem Beitrag angepasst wurden.
Dies ist für Postgis relevanter und ihre Einstellungen schienen ähnlich zu sein.
Die Stapelgröße jedes Commits war für meinen Fall nicht besonders wichtig. In der Geocode-Dokumentation wurde die Losgröße 3 verwendet. Ich habe Werte von 1, 3, 5 bis 10 experimentiert. Dabei habe ich keinen signifikanten Unterschied festgestellt. Mit kleinerer Batch-Größe machen Sie mehr Commits und Updates, aber ich denke, der wahre Flaschenhals ist hier nicht. Eigentlich verwende ich jetzt Losgröße 1. Da es immer unerwartete fehlerhafte Adressen gibt, die Ausnahmen verursachen, setze ich den gesamten fehlerhaften Stapel als ignoriert und fahre mit den verbleibenden Zeilen fort. Bei Stapelgröße 1 muss die Tabelle nicht das zweite Mal verarbeitet werden, um die möglichen fehlerfreien Datensätze im als ignoriert gekennzeichneten Stapel zu geokodieren.
Dies hängt natürlich davon ab, wie Ihr Batch-Skript funktioniert. Ich werde mein Skript später mit weiteren Details veröffentlichen.
Sie können versuchen, eine normale Adresse zu verwenden, um eine falsche Adresse zu filtern, wenn dies für Ihre Verwendung geeignet ist. Ich habe irgendwo jemanden gesehen, der das erwähnt hat, aber ich war mir nicht sicher, wie das funktioniert, da die Normalisierungsfunktion nur im Format funktioniert. Sie kann Ihnen nicht wirklich sagen, welche Adresse ungültig ist.
Später wurde mir klar, dass dies hilfreich sein kann, wenn die Adresse offensichtlich in einem schlechten Zustand ist und Sie sie überspringen möchten. Zum Beispiel habe ich viele Adressen ohne Straßennamen oder sogar Straßennamen. Normalisieren Sie alle Adressen zuerst wird relativ schnell sein, dann können Sie die offensichtliche falsche Adresse für Sie filtern und sie dann überspringen. Dies passte jedoch nicht zu meiner Verwendung, da eine Adresse ohne Hausnummer oder Straßennamen immer noch auf die Straße oder den Ort abgebildet werden konnte und diese Informationen für mich immer noch nützlich sind.
Und die meisten Adressen, die in meinem Fall nicht geokodiert werden können, haben tatsächlich alle Felder, nur gibt es keine Übereinstimmung in der Datenbank. Sie können diese Adressen nicht einfach durch Normalisieren filtern.
BEARBEITEN Weitere Informationen finden Sie in meinem Blogbeitrag über die Einrichtung des Geocodierungsservers und das von mir verwendete Skript .
BEARBEITEN 2 Ich habe die Geokodierung von 2 Millionen Adressen abgeschlossen und viele Adressen basierend auf dem Geokodierungsergebnis bereinigt. Mit einer besser bereinigten Eingabe wird der nächste Stapeljob viel schneller ausgeführt. Mit sauber meine ich, dass einige Adressen offensichtlich falsch sind und entfernt werden sollten oder unerwartete Inhalte für Geocoder haben, die Probleme bei der Geocodierung verursachen. Meine Theorie lautet: Durch das Entfernen fehlerhafter Adressen kann vermieden werden, dass der Cache durcheinander gebracht wird, wodurch die Leistung bei guten Adressen erheblich verbessert wird.
Ich habe die Eingabe basierend auf dem Status getrennt, um sicherzustellen, dass für jeden Job alle für die Geokodierung erforderlichen Daten im RAM zwischengespeichert werden können. Jede falsche Adresse im Job veranlasst den Geocodierer jedoch, in mehr Status zu suchen, wodurch der Cache durcheinander gebracht werden könnte.
quelle
Gemäß diesem Diskussionsthread sollten Sie dasselbe Normalisierungsverfahren verwenden, um die Tiger-Daten und Ihre Eingabeadresse zu verarbeiten. Da die Tiger-Daten mit dem eingebauten Normalizer verarbeitet wurden, ist es besser, nur den eingebauten Normalizer zu verwenden. Selbst wenn Sie den pagc_normalizer zum Laufen gebracht haben, kann es Ihnen nicht helfen, wenn Sie ihn nicht zum Aktualisieren der Tiger-Daten verwenden.
Abgesehen davon denke ich, dass geocode () den Normalizer trotzdem aufruft, also normalisiere die Adresse, bevor die Geocodierung nicht wirklich nützlich sein kann. Eine mögliche Verwendung des Normalisierers ist der Vergleich der normalisierten Adresse und der von Geocode () zurückgegebenen Adresse. Wenn beide normalisiert sind, ist es möglicherweise einfacher, das falsche Geokodierungsergebnis zu finden.
Wenn Sie fehlerhafte Adressen mit dem Normalizer aus dem Geocode herausfiltern können, hilft das wirklich. Ich sehe jedoch nicht, dass der Normalizer so etwas wie ein Match Score oder eine Wertung hat.
Im selben Diskussionsthread wurde auch ein Debug-Schalter erwähnt
geocode_address
, um weitere Informationen anzuzeigen . Der Knotengeocode_address
benötigt eine normalisierte Adresseingabe.Der Geocoder ist schnell, um eine genaue Übereinstimmung zu erzielen, nimmt sich jedoch in schwierigen Fällen viel mehr Zeit. Ich habe festgestellt, dass es einen Parameter gibt,
restrict_region
und dachte, dass er möglicherweise die erfolglose Suche einschränkt, wenn ich den Grenzwert als Status einstelle, da ich mir ziemlich sicher bin, in welchem Status er sich befinden wird die richtige Adresse, obwohl es einige Zeit dauert.Vielleicht sucht Geocoder an allen möglichen Stellen, wenn die erste exakte Suche nicht übereinstimmt. Dies macht es möglich, Eingaben mit einigen Fehlern zu verarbeiten, aber auch einige Suchen sehr langsam zu machen.
Ich denke, es ist gut, wenn ein interaktiver Dienst fehlerhafte Eingaben akzeptiert, aber manchmal möchten wir vielleicht einfach auf eine kleine Menge falscher Adressen verzichten, um eine bessere Leistung bei der Batch-Geokodierung zu erzielen.
quelle
restrict_region
auf das Timing ausgewirkt, als Sie den richtigen Status eingestellt haben? Aus dem Postgis-Benutzer-Thread, den Sie oben verlinkt haben, geht auch hervor, dass sie speziell Probleme mit Adressen haben, auf1020 Highway 20
die ich auch gestoßen bin.Ich werde diese Antwort posten, aber hoffentlich hilft ein anderer Mitwirkender dabei, die folgenden Punkte aufzuschlüsseln, von denen ich denke, dass sie ein kohärenteres Bild ergeben:
Nun meine Antwort, die nur eine Anekdote ist:
Das Beste, was ich (basierend auf einer einzelnen Verbindung) bekomme, ist ein Durchschnitt von 208 ms pro
geocode
. Dies wird gemessen, indem Adressen aus meinem Datensatz, der sich über die USA erstreckt, zufällig ausgewählt werden. Es enthält einige schmutzige Daten, aber die am längsten laufendengeocode
s scheinen auf offensichtliche Weise nicht schlecht zu sein.Das Wesentliche ist, dass ich anscheinend CPU-gebunden bin und dass eine einzelne Abfrage an einen einzelnen Prozessor gebunden ist. Ich kann dies parallelisieren, indem theoretisch mehrere Verbindungen ausgeführt werden,
UPDATE
die auf komplementären Segmenten deraddresses_to_geocode
Tabelle auftreten. In der Zwischenzeit kann ichgeocode
durchschnittlich 208 ms für einen landesweiten Datensatz verwenden. Die Verteilung ist sowohl in Bezug auf die Position der meisten meiner Adressen als auch auf die Dauer (siehe z. B. das Histogramm oben) und die nachstehende Tabelle verzerrt.Mein bisher bester Ansatz ist es, es in Chargen von 10000 zu tun, mit einer wahrscheinlichen Verbesserung, wenn ich mehr pro Charge mache. Für Chargen von 100 habe ich ungefähr 251 ms bekommen, bei 10000 habe ich 208 ms bekommen.
Ich muss Feldnamen zitieren, weil RPostgreSQL die Tabellen mit erstellt
dbWriteTable
Das ist ungefähr viermal so schnell, als würde ich sie einzeln aufnehmen. Wenn ich sie einzeln durchführe, kann ich eine Aufschlüsselung nach Bundesstaaten erhalten (siehe unten). Ich habe dies getan, um zu überprüfen, ob einer oder mehrere der TIGER-Zustände eine schlechte Last oder einen schlechten Index aufweisen, was
geocode
statistisch gesehen zu einer schlechten Leistung führen dürfte. Ich habe offensichtlich einige schlechte Daten (einige Adressen sind sogar E-Mail-Adressen!), Aber die meisten sind gut formatiert. Wie ich bereits sagte, weisen einige der am längsten laufenden Abfragen keine offensichtlichen Mängel in ihrem Format auf. Unten finden Sie eine Tabelle mit der Anzahl, der minimalen Abfragedauer, der mittleren Abfragedauer und der maximalen Abfragedauer für Status von 3000 - einige zufällige Adressen aus meinem Datensatz:quelle