sql >> Database >  >> RDS >> PostgreSQL

PostgreSQL 12:Implementatie van K-Nearest Neighbor Space gepartitioneerde algemene zoekboomindexen

De waarde van indexeren

PostgreSQL biedt een eenvoudige lineaire afstandsoperator <-> (lineaire afstand). We zullen dit gebruiken om punten te vinden die zich het dichtst bij een bepaalde locatie bevinden.

PostgreSQL biedt een eenvoudige lineaire afstandsoperator de gegevens, en zonder optimalisaties en zonder indexen zien we het volgende uitvoeringsplan:

time psql -qtAc "
EXPLAIN (ANALYZE ON, BUFFERS ON)
SELECT name, location
FROM geonames
ORDER BY location <-> '(29.9691,-95.6972)'
LIMIT 5;
"  <-- closing quote
                                      QUERY PLAN
-----------------------------------------------------------------------------------------------------------
Limit  (cost=418749.15..418749.73 rows=5 width=38) 
        (actual time=2553.970..2555.673 rows=5 loops=1)
  Buffers: shared hit=100 read=272836
  ->  Gather Merge  (cost=418749.15..1580358.21 rows=9955954 width=38) 
                    (actual time=2553.969..2555.669 rows=5 loops=1)
        Workers Planned: 2
        Workers Launched: 2
        Buffers: shared hit=100 read=272836
        ->  Sort  (cost=417749.12..430194.06 rows=4977977 width=38)
                 (actual time=2548.220..2548.221 rows=4 loops=3)
              Sort Key: ((location <-> '(29.9691,-95.6972)'::point))
              Sort Method: top-N heapsort  Memory: 25kB
              Worker 0:  Sort Method: top-N heapsort  Memory: 26kB
              Worker 1:  Sort Method: top-N heapsort  Memory: 25kB
              Buffers: shared hit=100 read=272836
              ->  Parallel Seq Scan on geonames  (cost=0.00..335066.71 rows=4977977 width=38) 
                                        (actual time=0.040..1637.884 rows=3982382 loops=3)
                    Buffers: shared hit=6 read=272836
Planning Time: 0.493 ms
Execution Time: 2555.737 ms

real    0m2.595s
user    0m0.011s
sys    0m0.015s

en hier zijn de resultaten:(dezelfde resultaten voor alle verzoeken, dus we zullen ze later weglaten.)

name locatie
Cypress (29.96911,-95.69717)
Cypress Pointe Baptist Church (29.9732,-95.6873)
Cypress Postkantoor (29.9743,-95.67953)
Hot Wells (29.95689,-95.68189)
Dry Creek Airport (29.98571,-95.68597)

Dus 418749.73 is de OPTIMIZER-kost om te verslaan, en het duurde twee en een halve seconde (2555.673) om die query uit te voeren. Dit is eigenlijk een heel goed resultaat, met PostgreSQL zonder enige optimalisatie tegen een tabel met 11 miljoen rijen. Dit is ook de reden waarom we een grotere dataset hebben gekozen, omdat er een zeer minimaal verschil zou zijn bij het gebruik van indexen tegen minder dan 10 miljoen rijen. Parallelle sequentiële scans zijn fantastisch, maar dat is een ander artikel.

Gist-index toevoegen

We beginnen het optimalisatieproces door een GiST-index toe te voegen. Omdat onze voorbeeldquery een

. heeft
LIMIT

clausule van 5 items hebben we een zeer hoge selectiviteit. Dit zal de planner aanmoedigen om een ​​index te gebruiken, dus we zullen er een bieden die redelijk goed werkt met geometriegegevens.

time psql -qtAc "CREATE INDEX idx_gist_geonames_location ON geonames USING gist(location);"

Het maken van de index brengt wat kosten met zich mee.

CREATE INDEX
real    3m1.988s
user    0m0.011s
sys     0m0.014s

En voer dan dezelfde query opnieuw uit.

time psql -qtAc "
EXPLAIN (ANALYZE ON, BUFFERS ON)
SELECT name, location
FROM geonames
ORDER BY location <-> '(29.9691,-95.6972)'
LIMIT 5;
"
                                      QUERY PLAN
----------------------------------------------------------------------------------
Limit  (cost=0.42..1.16 rows=5 width=38) (actual time=0.797..0.881 rows=5 loops=1)
  Buffers: shared hit=5 read=15
  ->  Index Scan using idx_gist_geonames_location on geonames  
            (cost=0.42..1773715.32 rows=11947145 width=38) 
            (actual time=0.796..0.879 rows=5 loops=1)
        Order By: (location <-> '(29.9691,-95.6972)'::point)
        Buffers: shared hit=5 read=15
Planning Time: 0.768 ms
Execution Time: 0.939 ms

real    0m0.033s
user    0m0.011s
sys     0m0.013s

In dit geval zien we een behoorlijk dramatische verbetering. De geschatte kosten van de zoekopdracht zijn slechts 1,16! Vergelijk dat met de oorspronkelijke kosten van de niet-geoptimaliseerde zoekopdracht op 418749.73. De werkelijke tijd die nodig was, was 0,939 milliseconden (negen tienden van een milliseconde), wat overeenkomt met de 2,5 seconden van de oorspronkelijke zoekopdracht. Dit resultaat kostte minder tijd om te plannen, kreeg een veel betere schatting en kostte ongeveer 3 ordes van grootte minder runtime.

Laten we kijken of we het beter kunnen doen.

Een SP-GiST-index toevoegen

time psql -qtAc "CREATE INDEX idx_spgist_geonames_location ON geonames USING spgist(location);"
CREATE INDEX 

real    1m25.205s
user    0m0.010s
sys        0m0.015s

En dan voeren we dezelfde query opnieuw uit.

time psql -qtAc "
EXPLAIN (ANALYZE ON, BUFFERS ON)
SELECT name, location
FROM geonames
ORDER BY location <-> '(29.9691,-95.6972)'
LIMIT 5;
"
                                      QUERY PLAN
-----------------------------------------------------------------------------------
 Limit  (cost=0.42..1.09 rows=5 width=38) (actual time=0.066..0.323 rows=5 loops=1)
   Buffers: shared hit=47
   ->  Index Scan using idx_spgist_geonames_location on geonames  
            (cost=0.42..1598071.32 rows=11947145 width=38) 
            (actual time=0.065..0.320 rows=5 loops=1)
         Order By: (location <-> '(29.9691,-95.6972)'::point)
         Buffers: shared hit=47
 Planning Time: 0.122 ms
 Execution Time: 0.358 ms
(7 rows)

real    0m0.040s
user    0m0.011s
sys        0m0.015s

Wauw! Met behulp van een SP-GiST-index kostte de query slechts 1,09 en werd uitgevoerd in 0,358 milliseconden (een derde van een milliseconde).

Laten we eens kijken naar enkele dingen over de indexen zelf, en kijken hoe ze zich op de schijf met elkaar verhouden.

Indexvergelijkingen

indexnaam aanmaaktijd schatting opvraagtijd indexsize tijd plannen
niet geïndexeerd 0S 418749.73 2555.673 0 .493
idx_gist_geonames_location 3M 1S 1.16 ,939 ms 868 MB .786
idx_spgist_geonames_location 1M 25S 1,09 .358 ms 523 MB .122

Conclusies

We zien dus dat SP-GiST tweemaal zo snel is als GiST in uitvoering, 8x sneller te plannen en ongeveer 60% van de grootte op schijf. En (relevant voor dit artikel) het ondersteunt ook zoeken op KNN-index vanaf PostgreSQL 12. Voor dit type bewerking hebben we een duidelijke winnaar.

Bijlagen

De gegevens instellen

Voor dit artikel gaan we de gegevens gebruiken die worden verstrekt door de GeoNames Gazetteer.
Dit werk is in licentie gegeven onder een Creative Commons Attribution 4.0-licentie
De gegevens worden geleverd "zoals ze zijn" zonder garantie of enige vertegenwoordiging van nauwkeurigheid, tijdigheid of volledigheid.

Maak de structuur

We beginnen het proces door een werkmap en een klein beetje ETL te maken.

# change to our home directory
cd
mkdir spgist
cd spgist
# get the base data.  
# This file is 350MB.  It will unpack to 1.5GB
# It will expand to 2GB in PostgreSQL,
#    and then you will still need some room for indexes
#  All together, you will need about 
#  3GB of space for this exercise
#  for about 12M rows of data.

psql -qtAc "
CREATE TABLE IF NOT EXISTS geonames (
geonameid           integer primary key
,name               text 
,asciiname          text 
,alternatenames     text 
,latitude           numeric(13,5) 
,longitude          numeric(13,5)
,feature_class      text 
,feature_code       text 
,country            text 
,cc2                text 
,admin1             text 
,admin2             bigint 
,admin3             bigint 
,admin4             bigint 
,population         bigint 
,elevation          bigint 
,dem                bigint 
,timezone           text 
,modification date  );

COMMENT ON COLUMN geonames.geonameid          
 IS ' integer id of record in geonames database';
COMMENT ON COLUMN geonames.name               
 IS ' name of geographical point (utf8) varchar(200)';
COMMENT ON COLUMN geonames.asciiname          
 IS ' name of geographical point in plain ascii characters, varchar(200)';
COMMENT ON COLUMN geonames.alternatenames     
 IS ' alternatenames, comma separated, ascii names automatically transliterated, 
    convenience attribute from alternatename table, varchar(10000)';
COMMENT ON COLUMN geonames.latitude           
 IS ' latitude in decimal degrees (wgs84)';
COMMENT ON COLUMN geonames.longitude          
 IS ' longitude in decimal degrees (wgs84)';
COMMENT ON COLUMN geonames.feature_class      
 IS ' http://www.geonames.org/export/codes.html, char(1)';
COMMENT ON COLUMN geonames.feature_code       
 IS ' http://www.geonames.org/export/codes.html, varchar(10)';
COMMENT ON COLUMN geonames.country            
 IS ' ISO-3166 2-letter country code, 2 characters';
COMMENT ON COLUMN geonames.cc2                
 IS ' alternate country codes, comma separated, ISO-3166 2-letter country code, 
    200 characters';
COMMENT ON COLUMN geonames.admin1             
 IS ' fipscode (subject to change to iso code), see exceptions below, 
    see file admin1Codes.txt for display names of this code; varchar(20)';
COMMENT ON COLUMN geonames.admin2             
 IS ' code for the second administrative division, a county in the US, 
    see file admin2Codes.txt; varchar(80) ';
COMMENT ON COLUMN geonames.admin3             
 IS ' code for third level administrative division, varchar(20)';
COMMENT ON COLUMN geonames.admin4             
 IS ' code for fourth level administrative division, varchar(20)';
COMMENT ON COLUMN geonames.population         
 IS ' bigint (8 byte int) ';
COMMENT ON COLUMN geonames.elevation          
 IS ' in meters, integer';
COMMENT ON COLUMN geonames.dem                
 IS ' digital elevation model, srtm3 or gtopo30, average elevation of 3''x3'' 
    (ca 90mx90m) or 30''x30'' (ca 900mx900m) area in meters, integer. 
    srtm processed by cgiar/ciat.';
COMMENT ON COLUMN geonames.timezone           
 IS ' the iana timezone id (see file timeZone.txt) varchar(40)';
COMMENT ON COLUMN geonames.modification       
 IS ' date of last modification in yyyy-MM-dd format';
"  #<-- Don't forget the closing quote

ETL

wget http://download.geonames.org/export/dump/allCountries.zip
unzip allCountries.zip

# do this, and go get a coffee.  This took nearly an hour
#   there will be a few lines that fail, they don't really matter much
IFS=$'\n'

for line in $(<allCountries.txt)
do

    echo -n "$line" | 
        psql -qtAc
    "COPY geonames FROM STDIN WITH CSV DELIMITER E'\t';"
2> errors.txt
done

Opruimen en instellen

Al het andere dat we doen vanuit psql:

psql
-- This command requires the installation
--  of postgis2 from your OS package manager.
-- For OS/X that was `port install postgresql12-postgis2`
-- it will be something similar on most platforms.
-- (e.g. apt-get install postgresql12-postgis2, 
--  yum -y install postgresql12-postgis2, etc.)
CREATE EXTENSION postgis;
CREATE EXTENSION postgis_topology;

ALTER TABLE geonames ADD COLUMN location point;

-- Go get another cup of coffee, this is going to rewrite the entire table with the new geo column.
UPDATE geonames SET location = ('(' || latitude || ', ' || longitude || ')')::point;

DELETE FROM geonames WHERE latitude IS NULL or longitude IS NULL;
-- DELETE 32   -- In my case, this ETL anomoly was too small
--  to bother fixing the records

-- Bloat removal from the update and delete operations
CLUSTER geonames USING geonames_pkey;

  1. VOORBEELD:SentryOne Plan Explorer-extensie voor Azure Data Studio

  2. Tabel kopiëren in MySQL

  3. Fout bij het toewijzen van postgres-arrays in Spring JPA

  4. Reset het root-wachtwoord van de MySQL-server