jsonb 열의 진 인덱스가 왜 쿼리 속도를 늦추고 어떻게 할 수 있습니까?


10

테스트 데이터를 초기화하십시오.

CREATE EXTENSION IF NOT EXISTS pgcrypto;
CREATE TABLE docs (data JSONB NOT NULL DEFAULT '{}');
-- generate 200k documents, ~half with type: "type1" and another half with type: "type2", unique incremented index and random uuid per each row
INSERT INTO docs (data)
SELECT json_build_object('id', gen_random_uuid(), 'type', (CASE WHEN random() > 0.5 THEN 'type1' ELSE 'type2' END) ,'index', n)::JSONB
FROM generate_series(1, 200000) n;
-- inset one more row with explicit uuid to query by it later
INSERT INTO docs (data) VALUES (json_build_object('id', '30e84646-c5c5-492d-b7f7-c884d77d1e0a', 'type', 'type1' ,'index', 200001)::JSONB);

첫 번째 쿼리-데이터-> 유형 및 제한으로 필터링 :

-- FAST ~19ms
EXPLAIN ANALYZE
SELECT * FROM docs
WHERE data @> '{"type": "type1"}'::JSONB
LIMIT 25;
/* "Limit  (cost=0.00..697.12 rows=25 width=90) (actual time=0.029..0.070 rows=25 loops=1)"
   "  ->  Seq Scan on docs  (cost=0.00..5577.00 rows=200 width=90) (actual time=0.028..0.061 rows=25 loops=1)"
   "        Filter: (data @> '{"type": "type1"}'::jsonb)"
   "        Rows Removed by Filter: 17"
   "Planning time: 0.069 ms"
   "Execution time: 0.098 ms" 
*/

두 번째 쿼리-데이터-> 유형, 데이터-> 색인 및 제한으로 필터링

-- SLOW ~250ms
EXPLAIN ANALYZE
SELECT * FROM docs
WHERE data @> '{"type": "type1"}'::JSONB
ORDER BY data->'index' -- added ORDER BY
LIMIT 25;

/* "Limit  (cost=5583.14..5583.21 rows=25 width=90) (actual time=236.750..236.754 rows=25 loops=1)"
   "  ->  Sort  (cost=5583.14..5583.64 rows=200 width=90) (actual time=236.750..236.750 rows=25 loops=1)"
   "        Sort Key: ((data -> 'index'::text))"
   "        Sort Method: top-N heapsort  Memory: 28kB"
   "        ->  Seq Scan on docs  (cost=0.00..5577.50 rows=200 width=90) (actual time=0.020..170.797 rows=100158 loops=1)"
   "              Filter: (data @> '{"type": "type1"}'::jsonb)"
   "              Rows Removed by Filter: 99842"
   "Planning time: 0.075 ms"
   "Execution time: 236.785 ms"
*/

세 번째 쿼리-두 번째 (이전)와 동일하지만 data-> index에 btree 인덱스가 있습니다.

CREATE INDEX docs_data_index_idx ON docs ((data->'index'));

-- FAST ~19ms
EXPLAIN ANALYZE
SELECT * FROM docs
WHERE data @> '{"type": "type1"}'::JSONB
ORDER BY data->'index' -- added BTREE index on this field
LIMIT 25;
/* "Limit  (cost=0.42..2473.98 rows=25 width=90) (actual time=0.040..0.125 rows=25 loops=1)"
   "  ->  Index Scan using docs_data_index_idx on docs  (cost=0.42..19788.92 rows=200 width=90) (actual time=0.038..0.119 rows=25 loops=1)"
   "        Filter: (data @> '{"type": "type1"}'::jsonb)"
   "        Rows Removed by Filter: 17"
   "Planning time: 0.127 ms"
   "Execution time: 0.159 ms"
*/

네 번째 쿼리-이제 data-> id 및 limit = 1로 필터링합니다.

-- SLOW ~116ms
EXPLAIN ANALYZE
SELECT * FROM docs
WHERE data @> ('{"id": "30e84646-c5c5-492d-b7f7-c884d77d1e0a"}')::JSONB -- querying by "id" field now
LIMIT 1;
/* "Limit  (cost=0.00..27.89 rows=1 width=90) (actual time=97.990..97.990 rows=1 loops=1)"
   "  ->  Seq Scan on docs  (cost=0.00..5577.00 rows=200 width=90) (actual time=97.989..97.989 rows=1 loops=1)"
   "        Filter: (data @> '{"id": "30e84646-c5c5-492d-b7f7-c884d77d1e0a"}'::jsonb)"
   "        Rows Removed by Filter: 189999"
   "Planning time: 0.064 ms"
   "Execution time: 98.012 ms"
*/ 

다섯 번째 쿼리-네 번째와 동일하지만 데이터에 진 (json_path_ops) 인덱스가있는 경우 :

CREATE INDEX docs_data_idx ON docs USING GIN (data jsonb_path_ops);

-- FAST ~17ms
EXPLAIN ANALYZE
SELECT * FROM docs
WHERE data @> '{"id": "30e84646-c5c5-492d-b7f7-c884d77d1e0a"}'::JSONB -- added gin index with json_path_ops
LIMIT 1;
/* "Limit  (cost=17.55..20.71 rows=1 width=90) (actual time=0.027..0.027 rows=1 loops=1)"
   "  ->  Bitmap Heap Scan on docs  (cost=17.55..649.91 rows=200 width=90) (actual time=0.026..0.026 rows=1 loops=1)"
   "        Recheck Cond: (data @> '{"id": "30e84646-c5c5-492d-b7f7-c884d77d1e0a"}'::jsonb)"
   "        Heap Blocks: exact=1"
   "        ->  Bitmap Index Scan on docs_data_idx  (cost=0.00..17.50 rows=200 width=0) (actual time=0.016..0.016 rows=1 loops=1)"
   "              Index Cond: (data @> '{"id": "30e84646-c5c5-492d-b7f7-c884d77d1e0a"}'::jsonb)"
   "Planning time: 0.095 ms"
   "Execution time: 0.055 ms"
*/

여섯 번째 (마지막) 쿼리-세 번째 쿼리 (데이터 별-> 유형, 데이터 별 순서-> 인덱스, 제한)와 동일 :

-- SLOW AGAIN! ~224ms
EXPLAIN ANALYZE
SELECT * FROM docs
WHERE data @> '{"type": "type1"}'::JSONB
ORDER BY data->'index'
LIMIT 25;
/* "Limit  (cost=656.06..656.12 rows=25 width=90) (actual time=215.927..215.932 rows=25 loops=1)"
   "  ->  Sort  (cost=656.06..656.56 rows=200 width=90) (actual time=215.925..215.925 rows=25 loops=1)"
   "        Sort Key: ((data -> 'index'::text))"
   "        Sort Method: top-N heapsort  Memory: 28kB"
   "        ->  Bitmap Heap Scan on docs  (cost=17.55..650.41 rows=200 width=90) (actual time=33.134..152.618 rows=100158 loops=1)"
   "              Recheck Cond: (data @> '{"type": "type1"}'::jsonb)"
   "              Heap Blocks: exact=3077"
   "              ->  Bitmap Index Scan on docs_data_idx  (cost=0.00..17.50 rows=200 width=0) (actual time=32.468..32.468 rows=100158 loops=1)"
   "                    Index Cond: (data @> '{"type": "type1"}'::jsonb)"
   "Planning time: 0.157 ms"
   "Execution time: 215.992 ms"
*/

따라서 데이터 열에 진 인덱스가 있으면 Sixth (세 번째와 같은) 쿼리가 훨씬 느려집니다. data-> type 필드에 고유 한 값이 많지 않기 때문일 수 있습니다 ( "type1"또는 "type2"만)? 내가 할 수있는 일은? 유익한 다른 쿼리를 만들려면 진 인덱스가 필요합니다 ...

답변:


5

jsonb의 통계 부족 문제를 해결하기 위해jsonb 여기에보고 된 것처럼 열에 1 %의 통계 비율이있는 문제가 발생했습니다 . . 쿼리 계획을 보면 추정치와 실제 실행 간의 차이가 엄청납니다. 추정치는 아마도 200 개의 행이 있고 실제 리턴은 100158 개의 행이 있으며, 이로 인해 플래너는 다른 행보다 특정 전략을 선호하게됩니다.

여섯 번째 쿼리의 선택은 인덱스 스캔보다 비트 맵 인덱스 스캔을 선호하는 것으로 보이므로 플래너를 함께 SET enable_bitmapscan=off이동하여 세 번째 예에서와 같은 동작으로 되돌릴 수 있습니다.

그것이 나를 위해 일한 방법은 다음과 같습니다.

postgres@[local]:5432:postgres:=# EXPLAIN (ANALYZE, BUFFERS)
SELECT * FROM docs
WHERE data @> '{"type": "type1"}'::JSONB
ORDER BY data->'index'
LIMIT 25;
                                                                QUERY PLAN                                                                 
-------------------------------------------------------------------------------------------------------------------------------------------
 Limit  (cost=656.06..656.12 rows=25 width=90) (actual time=117.338..117.343 rows=25 loops=1)
   Buffers: shared hit=3096
   ->  Sort  (cost=656.06..656.56 rows=200 width=90) (actual time=117.336..117.338 rows=25 loops=1)
         Sort Key: ((data -> 'index'::text))
         Sort Method: top-N heapsort  Memory: 28kB
         Buffers: shared hit=3096
         ->  Bitmap Heap Scan on docs  (cost=17.55..650.41 rows=200 width=90) (actual time=12.838..80.584 rows=99973 loops=1)
               Recheck Cond: (data @> '{"type": "type1"}'::jsonb)
               Heap Blocks: exact=3077
               Buffers: shared hit=3096
               ->  Bitmap Index Scan on docs_data_idx  (cost=0.00..17.50 rows=200 width=0) (actual time=12.469..12.469 rows=99973 loops=1)
                     Index Cond: (data @> '{"type": "type1"}'::jsonb)
                     Buffers: shared hit=19
 Planning time: 0.088 ms
 Execution time: 117.405 ms
(15 rows)

Time: 117.813 ms
postgres@[local]:5432:postgres:=# SET enable_bitmapscan = off;
SET
Time: 0.130 ms
postgres@[local]:5432:postgres:=# EXPLAIN (ANALYZE, BUFFERS)
SELECT * FROM docs
WHERE data @> '{"type": "type1"}'::JSONB
ORDER BY data->'index'
LIMIT 25;
                                                               QUERY PLAN                                                               
----------------------------------------------------------------------------------------------------------------------------------------
 Limit  (cost=0.42..1320.48 rows=25 width=90) (actual time=0.017..0.050 rows=25 loops=1)
   Buffers: shared hit=4
   ->  Index Scan using docs_data_index_idx on docs  (cost=0.42..10560.94 rows=200 width=90) (actual time=0.015..0.045 rows=25 loops=1)
         Filter: (data @> '{"type": "type1"}'::jsonb)
         Rows Removed by Filter: 27
         Buffers: shared hit=4
 Planning time: 0.083 ms
 Execution time: 0.071 ms
(8 rows)

Time: 0.402 ms
postgres@[local]:5432:postgres:=#

이 경로로 이동하려는 경우 이와 같은 동작을 나타내는 쿼리에 대해서만 해당 검색을 사용하지 않도록 설정해야합니다. 그렇지 않으면 다른 쿼리 계획에서도 잘못된 동작이 발생합니다. 이와 같은 작업을 수행하면 정상적으로 작동합니다.

BEGIN;
SET enable_bitmapscan=off;
SELECT * FROM docs
WHERE data @> '{"type": "type1"}'::JSONB
ORDER BY data->'index'
LIMIT 25;
SET enable_bitmapscan=on;
COMMIT;

희망이 =)


잘 이해하고 있는지 잘 모르겠습니다 (PG 내부에 익숙하지 않음).이 동작은 jsonb 열의 "type"필드에 대한 카디널리티가 낮기 때문에 발생합니다. 또한 쿼리를 최적화하려면 jsonb 필드의 대략적인 카디널리티를 알아야합니다. enable_bitmapscan을 사용할지 여부를 결정하기 위해 쿼리하고 있습니다.
user606521

1
예, 당신은 두 가지 측면에서 이것을 이해하는 것 같습니다. 기본 1 % 선택성은 WHERE진 인덱스에서 절의 필드를 보는 것을 선호합니다. 왜냐하면 그것이 더 적은 행을 리턴 할 것이라고 믿기 때문입니다. 행 수를 더 잘 추정 할 수 있기 때문에을 수행하고 있기 때문에 ORDER BY data->'index' LIMIT 25다른 인덱스의 처음 몇 개 항목 (행이 버려진 상태에서 50 개 정도)을 스캔하면 행 수가 줄어드는 것을 알 수 있습니다. 플래너는 실제로 비트 맵 스캔을 사용하지 않아야하므로 쿼리 계획이 더 빨라집니다. 희망이 정리됩니다. =)
Kassandry

1
여기에 추가 설명 정보가 있습니다 : databasesoup.com/2015/01/tag-all-things-part-3.html 및이 프리젠 테이션에서 thebuild.com/presentations/json2015-pgconfus.pdf 도 도움이됩니다.
Kassandry

1
내가 아는 유일한 작업은 Oleg Bartunov, Tedor Sigaev 및 Alexander Kotorov의 JsQuery 확장 기능과 선택성 향상입니다. 운이 좋으면 9.6 이상에서 PostgreSQL 코어로 만듭니다.
Kassandry

1
PostgreSQL 핵심 팀 멤버 인 Josh Berkus의 답변에서 이메일의 1 % 수치를 인용했습니다. 그것이 어디에서 왔는지, 현재 가지고있는 것보다 내부에 대한 훨씬 더 깊은 이해가 필요합니다. 죄송합니다. = (당신은 정확히 그 숫자가 어디에서 왔는지 pgsql-performance@postgresql.org에 관해 프리 노드 IRC에 답장을 하거나 체크를 시도 할 수 #postgresql있습니다.
Kassandry
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.