0

もちろん、これらの問題を理解するのは複雑で、多くの情報が必要になる可能性があることは理解していますが、この特定のケースに対する既知の問題または回避策があることを願っています. 最適ではないクエリ プランを引き起こすクエリの変更を絞り込みました (これは Postgres 9.4 を実行しています)。

次のクエリは、約 50 ミリ秒で実行されます。テーブルは約 200 万のtag_deviceエントリを持つジャンクション テーブルで、devicesテーブルには約 150 万のエントリがあり、タグ テーブルには約 500,000 のエントリがあります (注: 実際の IP 値は作成されたものです)。

WITH inner_query AS (
  SELECT * FROM tag_device
  INNER JOIN tags  ON tag_device.tag_id = tags.id
  INNER JOIN devices ON tag_device.device_id = devices.id
  WHERE devices.device_ip <<= ANY(ARRAY[
    '10.0.0.1', '10.0.0.2', '10.0.0.5', '11.1.1.1', '12.2.2.35','13.0.0.1', '15.0.0.8', '1.160.0.1', '17.1.1.24', '18.2.2.1',
    '10.0.0.6', '10.0.0.21', '10.0.0.52', '11.1.1.2', '12.2.2.34','13.0.0.2', '15.0.0.7', '1.160.0.2', '17.1.1.23', '18.2.2.2',
    '10.0.0.7', '10.0.0.22', '10.0.0.53', '11.1.1.3', '12.2.2.33','13.0.0.3', '15.0.0.6', '1.160.0.3', '17.1.1.22', '18.2.2.3'
    ]::iprange[])
 ))
 SELECT * FROM inner_query LIMIT 100 OFFSET 0;

注意すべき点がいくつかあります。 device_ipip4r モジュール ( https://github.com/RhodiumToad/ip4r ) を使用して IP 範囲ルックアップを提供しており、この列には Gist インデックスがあります。上記のクエリは、次のクエリ プランを使用して約 50 ミリ秒で実行されます。

Limit  (cost=140367.19..140369.19 rows=100 width=239)
  CTE inner_query
    ->  Nested Loop  (cost=40147.63..140367.19 rows=56193 width=431)
          ->  Merge Join  (cost=40147.20..113345.15 rows=56193 width=261)
                Merge Cond: (tag_device.device_id = devices.id)
                ->  Index Scan using tag_device_device_id_idx on tag_device  (cost=0.43..67481.36 rows=1900408 width=51)
                ->  Materialize  (cost=40136.82..40402.96 rows=53228 width=210)
                      ->  Sort  (cost=40136.82..40269.89 rows=53228 width=210)
                            Sort Key: devices.id
                            ->  Bitmap Heap Scan on devices  (cost=1489.12..30498.45 rows=53228 width=210)
                                  Recheck Cond: (device_ip <<= ANY ('{10.0.0.1,10.0.0.2,10.0.0.5,11.1.1.1,12.2.2.2,13.0.0.1,15.0.0.2,1.160.0.5,17.1.1.1,18.2.2.2,10.0.0.1,10.0.0.2,10.0.0.5,11.1.1.1,12.2.2.2,13.0.0.1,15.0.0.2,1.160.0.5,17.1.1.1,18.2.2.2 (...)
                                  ->  Bitmap Index Scan on devices_iprange_idx  (cost=0.00..1475.81 rows=53228 width=0)
                                        Index Cond: (device_ip <<= ANY ('{10.0.0.1,10.0.0.2,10.0.0.5,11.1.1.1,12.2.2.2,13.0.0.1,15.0.0.2,1.160.0.5,17.1.1.1,18.2.2.2,10.0.0.1,10.0.0.2,10.0.0.5,11.1.1.1,12.2.2.2,13.0.0.1,15.0.0.2,1.160.0.5,17.1.1.1,18.2 (...)
          ->  Index Scan using tags_id_pkey on tags  (cost=0.42..0.47 rows=1 width=170)
                Index Cond: (id = tag_device.tag_id)
  ->  CTE Scan on inner_query  (cost=0.00..1123.86 rows=56193 width=239)

検索対象の ARRAY 内の IP アドレスの数を増やすと、クエリ プランが変更され、大幅に遅くなります。したがって、クエリの高速バージョンでは、配列に 30 個の項目があります。これを配列内の 80 項目に増やすと、クエリ プランが変更され、大幅に遅くなります (10 倍以上)。クエリは、他のすべての点で同じままです。新しいクエリ プランでは、マージ結合とネストされたループの代わりにハッシュ結合を使用します。以下は、配列に 30 個ではなく 80 個の項目がある場合の新しい (はるかに遅い) クエリ プランです。

Limit  (cost=204482.39..204484.39 rows=100 width=239)
  CTE inner_query
    ->  Hash Join  (cost=85839.13..204482.39 rows=146180 width=431)
          Hash Cond: (tag_device.tag_id = tags.id)
          ->  Hash Join  (cost=51368.40..145023.34 rows=146180 width=261)
                Hash Cond: (tag_device.device_id = devices.id)
                ->  Seq Scan on tag_device  (cost=0.00..36765.08 rows=1900408 width=51)
                ->  Hash  (cost=45580.57..45580.57 rows=138466 width=210)
                      ->  Bitmap Heap Scan on devices  (cost=3868.31..45580.57 rows=138466 width=210)
                            Recheck Cond: (device_ip <<= ANY ('{10.0.0.1,10.0.0.2,10.0.0.5,11.1.1.1,12.2.2.35,13.0.0.1,15.0.0.8,1.160.0.1,17.1.1.24,18.2.2.1,10.0.0.6,10.0.0.21,10.0.0.52,11.1.1.2,12.2.2.34,13.0.0.2,15.0.0.7,1.160.0.2,17.1.1.23,18.2.2.2 (...)
                            ->  Bitmap Index Scan on devices_iprange_idx  (cost=0.00..3833.70 rows=138466 width=0)
                                  Index Cond: (device_ip <<= ANY ('{10.0.0.1,10.0.0.2,10.0.0.5,11.1.1.1,12.2.2.35,13.0.0.1,15.0.0.8,1.160.0.1,17.1.1.24,18.2.2.1,10.0.0.6,10.0.0.21,10.0.0.52,11.1.1.2,12.2.2.34,13.0.0.2,15.0.0.7,1.160.0.2,17.1.1.23,18.2 (...)
          ->  Hash  (cost=16928.88..16928.88 rows=475188 width=170)
                ->  Seq Scan on tags  (cost=0.00..16928.88 rows=475188 width=170)
  ->  CTE Scan on inner_query  (cost=0.00..2923.60 rows=146180 width=239)

デフォルトのクエリ プランを使用した上記のクエリは、約 500 ミリ秒で実行されます (10 倍以上遅くなります)。を使用してハッシュ結合をオフにするSET enable_hashjoin= OFF;と、クエリ プランはマージ結合の使用に戻り、配列内の 80 個のアイテムで約 50 ミリ秒で再び実行されます。

繰り返しますが、ここでの唯一の変更は、検索される ARRAY 内のアイテムの数です。

プランナーがなぜ大規模なスローダウンをもたらす悪い選択をしているのか、誰か考えている人はいますか?

データベースはメモリに完全に収まり、SSD 上にあります。

また、クエリに制限を追加したときにプランナーが tag_device テーブルのインデックスを使用しないという問題に遭遇したため、CTE を使用していることも指摘したいと思います。基本的にここで説明されている問題: http://thebuild.com/blog/2014/11/18/when-limit-attacks/

ありがとう!

4

1 に答える 1