web-dev-qa-db-ja.com

Postgresを使用した1億レコードの空間クエリの高速化

私は現在Postgres/PostGIS(1つの単一インスタンス)を使用していくつかのgeoクエリを実行し、特定の形状内にあるポイントと相対列からの追加データをキャッチしています。

ただし、インデックス(およびデータベースのスケールアップ)を使用しても、一部のクエリは遅すぎます。

Postgresの上でスムーズに動作するBrytLytのようなGPUデータベースを使用してみましたが、問題を解決しましたが、残念ながら非常に高すぎます(1時間あたりのAWSのコスト)。

レコードの合計量は〜1億回で、現時点でのクエリはそれぞれ6/7秒かかります(〜0.1秒程度必要です)。

問題に適したデータベースを提案できますか? 「手動の」シャーディングと事前計算値を回避しようとしています。

たとえば、特定のエリアに1つのベッドルームがある家の平均価格です。

これは現在のテーブルスキーマです。

        Column         |       Type       | Collation | Nullable | Default 
-----------------------+------------------+-----------+----------+---------
 uprn                  | bigint           |           |          | 
 is_address_accurate   | boolean          |           |          | 
 postcode              | text             |           |          | 
 listing_type          | text             |           |          | 
 asking_price          | double precision |           |          | 
 bedrooms              | integer          |           |          | 
 property_type         | bpchar           |           |          | 
 description           | text             |           |          | 
 total_floor_area_sqft | double precision |           |          | 
 date_appeared         | date             |           |          | 
 date_removed          | date             |           |          | 
 sold_price            | double precision |           |          | 
 date_sold             | date             |           |          | 
 keywords              | text             |           |          | 
 asking_price_sqft     | double precision |           |          | 
 sold_price_sqft       | double precision |           |          | 
 days_on_market        | integer          |           |          | 
 images                | text[]           |           |          | 
 latitude              | double precision |           |          | 
 longitude             | double precision |           |          | 
 geom                  | geometry         |           |          | 
 sector_ind            | text             |           |          | 
 district_ind          | text             |           |          | 
 area_ind              | text             |           |          | 
 total_floor_area      | double precision |           |          | 
 transaction_type      | text             |           |          | 
 old_new               | character(1)     |           |          | 
 duration              | character(1)     |           |          | 
 floor_level           | text             |           |          | 
 floor_height          | double precision |           |          | 
 current_energy_rating | text             |           |          | 
 flat_top_storey       | text             |           |          | 
 flat_storey_count     | smallint         |           |          | 
 lodgement_date        | date             |           |          | 
 discount              | double precision |           |          | 
 import_date           | date             |           |          | 
 building_number       | smallint         |           |          | 
 sub_building_name     | text             |           |          | 
 building_name         | text             |           |          | 
 thoroughfare          | text             |           |          | 
 post_town             | text             |           |          | 
Indexes:
    "sales_core_area_ind_idx" btree (area_ind)
    "sales_core_area_ind_property_type_bedrooms_idx" btree (area_ind, property_type, bedrooms)
    "sales_core_geom_idx" Gist (geom)

私が実行しているクエリの例:

explain analyze 
select avg(sold_price) 
from sales_core sc 
where ST_Intersects(sc.geom, (select ST_Transform(ST_SetSRID(geometry, 4326), 2163) 
                              from postcodes 
                              where name = 'SW')  
                   ) 
  and bedrooms = 1 
  and property_type = 'F';

これは explain analyze

    QUERY PLAN                                                                                     
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=1656833.89..1656833.90 rows=1 width=8) (actual time=141286.194..141286.194 rows=1 loops=1)
   InitPlan 1 (returns $0)
     ->  Seq Scan on postcodes  (cost=0.00..3350.97 rows=1 width=2384) (actual time=14.079..19.155 rows=1 loops=1)
           Filter: ((name)::text = 'SW'::text)
           Rows Removed by Filter: 11836
   ->  Bitmap Heap Scan on sales_core sc  (cost=834695.55..1653222.45 rows=104190 width=8) (actual time=141286.187..141286.187 rows=0 loops=1)
         Recheck Cond: ((property_type = 'F'::bpchar) AND (bedrooms = 1))
         Rows Removed by Index Recheck: 4447029
         Filter: st_intersects(geom, $0)
         Rows Removed by Filter: 1338789
         Heap Blocks: exact=35516 lossy=241191
         ->  Bitmap Index Scan on sales_core_area_ind_property_type_bedrooms_idx  (cost=0.00..834669.50 rows=312570 width=0) (actual time=2062.949..2062.949 rows=1338789 loops=1)
               Index Cond: ((property_type = 'F'::bpchar) AND (bedrooms = 1))
 Planning time: 0.192 ms
 Execution time: 141307.039 ms
(15 rows)

[〜#〜]更新[〜#〜]

@Evan Carrollの回答からの提案に従って、いくつかの変更を適用しました。このようにして、「地理」タイプの新しい列を作成しました。

update listings set location_2 = ST_Point(longitude, latitude)::geography

クラスターの2つのステップでインデックスを作成しました(フィルターに追加のパラメーターはありません):

CREATE INDEX ON listings USING Gist(location_2);
CLUSTER listings ON using listings_location_2_idx;

地理に基づく純粋なルックアップのみを試行しているため、すべての余分なパラメーターを削除しました。

select count(*) from listings l join postcodes pc on ST_Intersects (l.location_2, ST_SetSRID(pc.geometry, 4326)::geography) where pc.name = 'SW';

最終結果:カウント| 929245時間:1661312.474ミリ秒(27:41.312)

カウントは正しいようですが(約100万レコード)、非常に遅すぎて実行できません(27分以上)。

私が何か間違ったことをしているかどうかはわかりません(ドキュメント全体を調べましたが、何かを失ったかもしれません)。

何か案が?

2
Randomize

指定されたクエリプランに基づいて、postcodes.nameのインデックスが必要です

 CREATE INDEX ON postcodes (name);

また、述語または複合によって、sales_coreのGistインデックスを修正することもできます。

CREATE EXTENSION btree_Gist;
CREATE INDEX ON sales_core USING Gist(property_type, bedrooms, geom);

私もhighly Geometry 4326として保存せず、代わりにGeography 4326として保存することをお勧めします。ST_IntersectsもJOINとして書き換える必要があります。

explain analyze 
select avg(sold_price) 
from sales_core sc
JOIN postcodes AS pc
  ON ST_Intersects( sc.geom, pc.geometry)
WHERE bedrooms = 1 
AND property_type = 'F'
AND pc.name = 'SW';

主な結合条件がGistの場合、クラスタリングは大きな違いをもたらします。おそらくPostGISのコンサルタントが必要です。助けてくれる私たちがたくさんいます。しかし、Pgからより多くのパフォーマンスを得ることができない理由はわかりません。

1
Evan Carroll