Jak szybko powinienem oczekiwać, że PostGIS geokoduje dobrze sformatowane adresy?
Zainstalowałem PostgreSQL 9.3.7 i PostGIS 2.1.7, załadowałem dane narodu i wszystkie dane stanów, ale odkryłem, że geokodowanie jest znacznie wolniejsze niż się spodziewałem. Czy moje oczekiwania były zbyt wysokie? Otrzymuję średnio 3 indywidualne geokody na sekundę. Muszę zrobić około 5 milionów i nie chcę na to czekać trzy tygodnie.
To jest maszyna wirtualna do przetwarzania gigantycznych macierzy R i zainstalowałem tę bazę danych z boku, aby konfiguracja wyglądała trochę głupio. Jeśli poważna zmiana w konfiguracji maszyny wirtualnej pomoże, mogę zmienić konfigurację.
Specyfikacja sprzętu
Pamięć: 65 GB procesory: 6
lscpu
daje mi to:
# 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 to centos, uname -rv
daje to:
# uname -rv
2.6.32-504.16.2.el6.x86_64 #1 SMP Wed Apr 22 06:48:29 UTC 2015
Konfiguracja 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"
Na podstawie wcześniejszych sugestii do tych typów kwerend, I podniósł shared_buffers
w postgresql.conf
pliku do około 1/4 dostępnej pamięci RAM i skutecznej rozmiar pamięci podręcznej do 1/2 RAM:
shared_buffers = 16096MB
effective_cache_size = 31765MB
Mam installed_missing_indexes()
i (po rozwiązaniu zduplikowanych wstawek w niektórych tabelach) nie wystąpiły żadne błędy.
Przykład geokodowania SQL # 1 (partia) ~ średni czas wynosi 2,8 / s
Postępuję zgodnie z przykładem z http://postgis.net/docs/Geocode.html , w którym utworzyłem tabelę zawierającą adres do geokodu, a następnie wykonuję 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";
Używam wielkości partii 1000 powyżej i zwraca ona za 337,70 sekund. Jest trochę wolniejszy dla mniejszych partii.
Przykład geokodowania SQL # 2 (rząd po rzędzie) ~ średni czas wynosi 1,2 / s
Kiedy kopie moje adresy, wykonując geokody pojedynczo z instrukcją, która wygląda tak (tak przy okazji, poniższy przykład zajął 4,14 sekundy),
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;
jest nieco wolniejszy (2,5x na rekord), ale mogę spojrzeć na rozkład czasów zapytań i zobaczyć, że jest to niewielka część długich zapytań, które najbardziej to spowalniają (tylko pierwsze 2600 z 5 milionów ma czasy wyszukiwania). Oznacza to, że górne 10% zajmuje średnio około 100 ms, dolne 10% średnio 3,69 sekundy, podczas gdy średnia wynosi 754 ms, a mediana wynosi 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]
Inne przemyślenia
Jeśli nie uda mi się zwiększyć wydajności o rząd wielkości, doszedłem do wniosku, że mogę przynajmniej zgadywać co do przewidywania wolnych czasów geokodów, ale nie jest dla mnie oczywiste, dlaczego wolniejsze adresy wydają się trwać dłużej. Korzystam z oryginalnego adresu w niestandardowym kroku normalizacji, aby upewnić się, że jest on ładnie sformatowany, zanim geocode()
funkcja go uzyska
sql=paste0("select pprint_addy(normalize_address('",myAddress,"'))")
gdzie myAddress
jest [Address], [City], [ST] [Zip]
ciągiem skompilowanym z tabeli adresów użytkownika z bazy danych innej niż postgresql.
Próbowałem (nie udało się) zainstalować pagc_normalize_address
rozszerzenia, ale nie jest jasne, czy przyniesie to rodzaj ulepszenia, którego szukam.
Edytowane w celu dodania informacji o monitorowaniu zgodnie z sugestią
Występ
Jeden procesor jest powiązany: [edytuj, tylko jeden procesor na zapytanie, więc mam 5 nieużywanych procesorów]
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
Próbka aktywności dysku na partycji danych, gdy jeden proces jest powiązany w 100%: [edycja: tylko jeden procesor jest używany przez to zapytanie]
# 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
Przeanalizuj ten SQL
To jest z EXPLAIN ANALYZE
tego zapytania:
"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"
Zobacz lepszy podział na http://explain.depesz.com/s/vogS
Odpowiedzi:
Spędziłem dużo czasu eksperymentując z tym, myślę, że lepiej pisać osobno, ponieważ są one z innej perspektywy.
To naprawdę skomplikowany temat, więcej szczegółów w moim poście na blogu na temat konfiguracji serwera geokodowania i skryptu, którego użyłem . Oto kilka krótkich streszczeń:
Serwer z tylko 2 danymi stanów jest zawsze szybszy niż serwer załadowany wszystkimi danymi 50 stanów.
Sprawdziłem to na moim komputerze domowym w różnym czasie i na dwóch różnych serwerach Amazon AWS.
Mój darmowy serwer warstwy AWS z danymi 2 stanów ma tylko 1G RAM, ale ma spójną wydajność 43 ~ 59 ms dla danych z 1000 rekordów i 45 000 rekordów.
Użyłem dokładnie tej samej procedury konfiguracji dla serwera AWS 8G RAM z załadowanymi wszystkimi stanami, dokładnie tym samym skryptem i danymi, a wydajność spadła do 80 ~ 105 ms.
Moja teoria jest taka, że gdy geokoder nie może dokładnie dopasować adresu, zaczął rozszerzać zakres wyszukiwania i ignorować niektóre elementy, takie jak kod pocztowy lub miasto. Dlatego dokument geokodowy może pochwalić się tym, że może ponownie skolonizować adres przy użyciu nieprawidłowego kodu pocztowego, chociaż zajęło to 3000 ms.
Po załadowaniu danych tylko z 2 stanów serwer zajmie znacznie mniej czasu w bezowocnym wyszukiwaniu lub dopasowaniu z bardzo niskim wynikiem, ponieważ może wyszukiwać tylko w 2 stanach.
Próbowałem to ograniczyć, ustawiając
restrict_region
parametr na stan wieloboków w funkcji geokodu, mając nadzieję, że uniknie to bezowocnego wyszukiwania, ponieważ jestem pewien, że większość adresów ma poprawny stan. Porównaj te dwie wersje:Jedyną różnicą wprowadzoną przez drugą wersję jest to, że normalnie, jeśli ponownie uruchomię to samo zapytanie, będzie to znacznie szybsze, ponieważ powiązane dane zostały zapisane w pamięci podręcznej, ale druga wersja wyłączyła ten efekt.
Więc
restrict_region
nie działa tak, jak chciałem, może po prostu został użyty do filtrowania wyniku wielokrotnego trafienia, a nie do ograniczenia zakresów wyszukiwania.Możesz trochę dostroić swój postgre conf.
Zwykły podejrzany o brakujące indeksy instalacji, analiza próżniowa nie zrobiły dla mnie żadnej różnicy, ponieważ skrypt pobierania wykonał już niezbędną konserwację, chyba że się z nimi pomyliłeś.
Jednak ustawienie postgre conf zgodnie z tym postem pomogło. Mój serwer w pełnej skali z 50 stanami miał 320 ms z domyślną konfiguracją dla niektórych gorszych danych, poprawił się do 185 ms z buforowaniem 2G shared_buffer, 5G pamięci podręcznej i poszedł do 100 ms dalej z większością ustawień dostosowanych do tego postu.
Jest to bardziej odpowiednie dla postgis, a ich ustawienia wydawały się podobne.
Wielkość partii każdego zatwierdzenia nie miała większego znaczenia dla mojej sprawy. W dokumentacji geokodu użyto wielkości partii 3. Eksperymentowałem z wartościami od 1, 3, 5 do 10. Nie znalazłem z tym żadnej znaczącej różnicy. Przy mniejszym rozmiarze partii wprowadzasz więcej zmian i aktualizacji, ale myślę, że prawdziwej szyjki butelki nie ma tutaj. Właściwie używam teraz wielkości partii 1. Ponieważ zawsze pojawia się jakiś nieoczekiwany źle sformatowany adres spowoduje wyjątek, ustawię całą partię z błędem jako ignorowaną i przejdę do pozostałych wierszy. Przy wielkości partii 1 nie muszę przetwarzać tabeli po raz drugi, aby geokodować możliwe dobre rekordy w partii oznaczonej jako ignorowane.
Oczywiście zależy to od sposobu działania skryptu wsadowego. Później opublikuję mój skrypt ze szczegółami.
Możesz spróbować użyć znormalizowanego adresu, aby odfiltrować zły adres, jeśli pasuje do twojego zastosowania. Widziałem gdzieś o tym wspominał, ale nie byłem pewien, jak to działa, ponieważ funkcja normalizacji działa tylko w formacie, nie może tak naprawdę powiedzieć, który adres jest nieprawidłowy.
Później zdałem sobie sprawę, że jeśli adres jest w oczywisty sposób złym stanie i chcesz je pominąć, może to pomóc. Na przykład w wielu adresach brakuje nazwy ulicy, a nawet nazwy ulicy. Normalizacja wszystkich adresów najpierw będzie względnie szybka, następnie możesz odfiltrować dla ciebie oczywisty zły adres, a następnie je pominąć. Nie odpowiadało to jednak mojemu użytkowaniu, ponieważ adres bez numeru ulicy, a nawet nazwy ulicy nadal można zmapować na ulicę lub miasto, a ta informacja wciąż jest dla mnie przydatna.
I większość adresów, które nie mogą być geokodowane w moim przypadku, faktycznie mają wszystkie pola, po prostu nie ma zgodności w bazie danych. Nie można filtrować tych adresów tylko poprzez ich normalizację.
EDYCJA Aby uzyskać więcej informacji, zobacz mój wpis na blogu na temat konfiguracji serwera geokodowania i skryptu, którego użyłem .
EDYCJA 2 Skończyłem geokodowanie 2 milionów adresów i dużo wyczyściłem adresy oparte na wyniku geokodowania. Dzięki lepiej oczyszczonemu wejściowi następne zadanie wsadowe działa znacznie szybciej. Przez czysty rozumiem, że niektóre adresy są oczywiście błędne i powinny zostać usunięte, lub mieć nieoczekiwaną zawartość, aby geokoder mógł powodować problemy z geokodowaniem. Moja teoria jest taka: usunięcie złych adresów może uniknąć zepsucia pamięci podręcznej, co znacznie poprawia wydajność dobrych adresów.
Oddzieliłem dane wejściowe w zależności od stanu, aby upewnić się, że każde zadanie może mieć wszystkie dane potrzebne do geokodowania w pamięci podręcznej. Jednak każdy zły adres w pracy zmusza geokoder do wyszukiwania w większej liczbie stanów, co może zepsuć pamięć podręczną.
źródło
Zgodnie z tym wątkiem do dyskusji powinieneś zastosować tę samą procedurę normalizacji do przetwarzania danych Tiger i adresu wejściowego. Ponieważ dane Tiger zostały przetworzone za pomocą wbudowanego normalizatora, lepiej używać tylko wbudowanego normalizatora. Nawet jeśli uruchomiłeś pagc_normalizer, może ci to nie pomóc, jeśli nie użyjesz go do aktualizacji danych Tiger.
Biorąc to pod uwagę, myślę, że geocode () i tak wywoła normalizator, więc normalizacja adresu przed geokodowaniem może być naprawdę nieprzydatna. Jednym z możliwych zastosowań normalizatora może być porównanie znormalizowanego adresu i adresu zwróconego przez geocode (). Po ich znormalizowaniu łatwiej byłoby znaleźć niewłaściwy wynik geokodowania.
Jeśli możesz odfiltrować zły adres z geokodu za pomocą normalizatora, to naprawdę pomoże. Jednak nie widzę, aby normalizator miał coś takiego jak wynik meczu lub ocena.
W tym samym wątku dyskusyjnym wspomniano również o przełączniku debugowania,
geocode_address
aby wyświetlić więcej informacji. Węzełgeocode_address
wymaga znormalizowanego wprowadzania adresu.Geocoder jest szybki do dokładnego dopasowania, ale w trudnych przypadkach zajmuje dużo więcej czasu. Odkryłem, że istnieje parametr
restrict_region
i pomyślałem, że może ograniczy to bezowocne wyszukiwanie, jeśli ustawię limit jako stan, ponieważ jestem całkiem pewien, w jakim stanie. Okazało się, że ustawienie go na zły stan nie zatrzymało geokodu, aby uzyskać poprawny adres, choć zajmuje to trochę czasu.Więc może geokoder będzie szukał we wszystkich możliwych miejscach, jeśli pierwsze dokładne wyszukiwanie nie będzie pasować. Dzięki temu jest w stanie przetwarzać dane wejściowe z pewnymi błędami, ale także spowalnia niektóre wyszukiwanie.
Myślę, że dobrze jest, aby usługa interaktywna akceptowała dane wejściowe z błędami, ale czasami możemy chcieć zrezygnować z małego zestawu złego adresu, aby uzyskać lepszą wydajność w geokodowaniu wsadowym.
źródło
restrict_region
czas na ustawienie prawidłowego stanu? Ponadto z wątku użytkowników postgis, do którego link prowadziłeś powyżej, wspominają oni konkretnie o problemach z adresami, takimi jak te,1020 Highway 20
które napotkałem.Zamierzam opublikować tę odpowiedź, ale mam nadzieję, że inny autor pomoże w rozbiciu następujących, co, jak sądzę, pozwoli nam stworzyć bardziej spójny obraz:
Teraz moja odpowiedź, która jest tylko anegdotą:
Najlepsze, jakie otrzymuję (w oparciu o pojedyncze połączenie) to średnio 208 ms na
geocode
. Mierzy się to poprzez losowe wybieranie adresów z mojego zbioru danych, który obejmuje całe USA. Zawiera niektóre brudne dane, ale najdłużej działającegeocode
nie wydają się złe w oczywisty sposób.Istotą tego jest to, że wydaje się, że jestem związany z procesorem i że pojedyncze zapytanie jest powiązane z jednym procesorem. Mogę to zrównoleglić, mając wiele połączeń działających z teoretycznie
UPDATE
na uzupełniających się segmentachaddresses_to_geocode
tabeli. W międzyczasiegeocode
zabieram średnio 208 ms na zbiór danych w całym kraju. Dystrybucja jest zniekształcona zarówno pod względem tego, gdzie znajduje się większość moich adresów, jak i pod względem czasu, jaki zajmują (np. Patrz histogram powyżej) oraz poniższej tabeli.Moje najlepsze podejście do tej pory polega na robieniu tego w partiach po 10000 sztuk, z pewną możliwą do oszacowania poprawą dzięki większej wydajności na partię. Dla partii 100 otrzymywałem około 251ms, przy 10000 dostaję 208ms.
Muszę zacytować nazwy pól, ponieważ RPostgreSQL tworzy tabele
dbWriteTable
To około 4x tak szybko, jakbym nagrywał jedną płytę na raz. Kiedy robię je pojedynczo, mogę uzyskać podział według stanu (patrz poniżej). Zrobiłem to, aby sprawdzić, czy jeden lub więcej stanów TIGER ma złe obciążenie lub indeks, co, jak się spodziewałem, spowoduje
geocode
pogorszenie wydajności. Mam oczywiście złe dane (niektóre adresy to nawet adresy e-mail!), Ale większość z nich jest dobrze sformatowana. Jak powiedziałem wcześniej, niektóre z najdłużej działających zapytań nie mają oczywistych braków w swoim formacie. Poniżej znajduje się tabela liczbowa, minimalny czas zapytania, średni czas zapytania i maksymalny czas zapytania dla stanów z 3000 - niektóre losowe adresy z mojego zestawu danych:źródło