많은 중복 값과 함께 사용할 인덱스는 무엇입니까?


14

몇 가지 가정을 해보자.

다음과 같은 테이블이 있습니다.

 a | b
---+---
 a | -1
 a | 17
  ...
 a | 21
 c | 17
 c | -3
  ...
 c | 22

내 세트에 대한 사실 :

  • 전체 테이블의 크기는 ~ 10 10 행입니다.

  • 다른 값 (예 :) 과 비슷한 acolumn에 ~ 100k 개의 행이 있습니다.ac

  • 그것은 'a'열에서 ~ 100k 개의 고유 값을 의미합니다.

  • 내 쿼리의 대부분은 예를 들어에서 주어진 값에 대한 모든 또는 대부분의 값을 읽습니다 select sum(b) from t where a = 'c'.

  • 테이블은 연속 값이 물리적으로 가까운 방식으로 작성됩니다 (순서대로 작성되었거나 CLUSTER해당 테이블 및 열에 사용 되었다고 가정 a).

  • 테이블은 거의 업데이트되지 않지만 읽기 속도에만 관심이 있습니다.

  • 테이블은 상대적으로 좁습니다 (예 : 튜플 당 ~ 25 바이트, +23 바이트 오버 헤드).

이제 어떤 종류의 색인을 사용해야합니까? 내 이해는 :

  • BTree 여기서 문제는 중복 값을 저장한다는 것을 알기 때문에 BTree 인덱스가 커질 것입니다 (테이블이 실제로 정렬되어 있다고 가정 할 수 없기 때문에해야합니다). BTree가 크면 인덱스와 인덱스가 가리키는 테이블 부분을 모두 읽어야합니다. fillfactor = 100인덱스의 크기를 약간 줄이려면 사용할 수 있습니다 .

  • 나의 이해는 쓸모없는 페이지를 읽는 대신에 작은 인덱스를 가질 수 있다는 것입니다. 작은 값을 사용 pages_per_range한다는 것은 색인이 더 크다는 것을 의미하며 (전체 색인을 읽어야하기 때문에 BRIN에 문제가 있음) 큰 pages_per_range의미가 있으면 쓸모없는 페이지를 많이 읽게됩니다. pages_per_range이러한 절충점을 고려 하여 좋은 가치를 찾는 마술 공식이 있습니까?

  • GIN / GiST 그것들이 주로 전체 텍스트 검색에 사용되기 때문에 여기에 관련이 있는지 확실하지 않지만 중복 키를 다루는 데 능숙하다고 들었습니다. 겠습니까 중 하나 GIN또는 GiST여기에 인덱스 도움?

또 다른 질문은 Postgres CLUSTER가 쿼리 플래너에서 테이블이 업데이트 되었다는 사실을 사용한다는 것입니다 (예 : 관련 시작 / 종료 페이지를 이진으로 검색)? 다소 관련이 있습니다. 모든 열을 BTree에 저장하고 테이블을 완전히 삭제하거나 동등한 것을 달성 할 수 있습니까 (SQL 서버의 클러스터 인덱스라고 생각합니다)? 여기에 도움이 될 하이브리드 BTree / BRIN 지수가 있습니까?

내 쿼리가 그렇게 읽을 수 없기 때문에 값을 저장하기 위해 배열을 사용하지 않는 것이 좋습니다 (튜플 수를 줄임으로써 튜플 오버 헤드 당 23 바이트의 비용을 줄일 수 있음을 이해합니다).


"주로 전체 텍스트 검색에 사용"GiST는 PostGIS에서 상당히 광범위하게 사용됩니다.
jpmc26

답변:


15

BTree

여기서 내 문제는 BTree 인덱스가 중복 값을 저장하기 때문에 실패하기 때문에 커질 것입니다 (테이블이 실제로 정렬되어 있다고 가정 할 수 없으므로 너무 큽니다). BTree가 크면 인덱스와 인덱스가 가리키는 테이블의 부분을 모두 읽어야합니다 ...

반드시 그럴 필요는 없다- '덮고있는' btree 인덱스를 갖는 것이 가장 빠른 읽기 시간이 될 것이며, 그것이 원하는 모든 것이라면 (즉, 추가 스토리지를 감당할 수있는 경우) 가장 좋은 방법입니다.

브린

내 이해는 쓸모없는 페이지를 읽는 대신에 작은 인덱스를 가질 수 있다는 것입니다. 작은 값을 사용 pages_per_range한다는 것은 색인이 더 크다는 것을 의미하며 (전체 색인을 읽어야하기 때문에 BRIN에 문제가 있음) 큰 pages_per_range의미가 있으면 쓸모없는 페이지를 많이 읽게됩니다.

당신이 커버 BTREE 인덱스의 저장 오버 헤드를 감당할 수없는 경우, BRIN 당신이 이미 클러스터링 때문에 (이, 당신을 위해 이상적입니다 중요하다 BRIN 유용 할 때까지). BRIN 색인 은 크기가 작 으므로 적절한 값을 선택하면 모든 페이지가 메모리에있을 수 있습니다 pages_per_range.

이러한 장단점을 고려하여 pages_per_range의 좋은 가치를 찾는 마술 공식이 있습니까?

마술 공식은 없지만 평균 값이 차지하는 평균 크기 (페이지 단위) pages_per_range 보다 약간 작은 것으로 시작 a합니다. 일반적인 쿼리에 대해 (검색된 BRIN 페이지 수) + (검색된 힙 페이지 수)를 최소화하려고합니다. 를 찾을 Heap Blocks: lossy=n에 대한 실행 계획 pages_per_range=1과에 대한 다른 값과 비교 pages_per_range- 즉, 스캔 얼마나 많은 불필요한 힙 블록을 참조하십시오.

진 / 소녀

그것들은 주로 전체 텍스트 검색에 사용되기 때문에 여기에 관련이 있는지 확실하지 않지만 중복 키를 다루는 데 능숙하다고 들었습니다. 겠습니까 중 하나 GIN/ GiST여기에 인덱스 도움?

GIN은 고려할 가치가 있지만 아마도 GiST는 아닐 것입니다. 그러나 자연 군집이 실제로 좋으면 BRIN이 더 나은 방법 일 것입니다.

다음은 더미 데이터에 대한 서로 다른 인덱스 유형 간의 샘플 비교입니다.

테이블 및 인덱스 :

create table foo(a,b,c) as
select *, lpad('',20)
from (select chr(g) a from generate_series(97,122) g) a
     cross join (select generate_series(1,100000) b) b
order by a;
create index foo_btree_covering on foo(a,b);
create index foo_btree on foo(a);
create index foo_gin on foo using gin(a);
create index foo_brin_2 on foo using brin(a) with (pages_per_range=2);
create index foo_brin_4 on foo using brin(a) with (pages_per_range=4);
vacuum analyze;

관계 크기 :

select relname "name", pg_size_pretty(siz) "size", siz/8192 pages, (select count(*) from foo)*8192/siz "rows/page"
from( select relname, pg_relation_size(C.oid) siz
      from pg_class c join pg_namespace n on n.oid = c.relnamespace
      where nspname = current_schema ) z;
이름 | 크기 | 페이지 | 행 / 페이지
: ----------------- | : ------ | ---- : | -------- :
foo | 149 MB | 19118 | 135
foo_btree_covering | 56MB | 7132 | 364
foo_btree | 56MB | 7132 | 364
foo_gin | 2928 킬로바이트 | 366 | 7103
foo_brin_2 | 264 킬로바이트 | 33 | 78787
foo_brin_4 | 136 킬로바이트 | 17 | 152941

취재 btree :

explain analyze select sum(b) from foo where a='a';
| 쿼리 계획 |
| : ------------------------------------------------- -------------------------------------------------- ------------------------------------------- |
| 집계 (비용 = 3282.57..3282.58 행 = 1 너비 = 8) (실제 시간 = 45.942..45.942 행 = 1 루프 = 1) |
| -> 인덱스 전용 foo에서 foo_btree_covering을 사용하여 스캔 (비용 = 0.43..3017.80 rows = 105907 너비 = 4) (실제 시간 = 0.038..27.286 rows = 100000 루프 = 1) |
| 인덱스 조건 : (a = 'a':: text) |
| 힙 페치 : 0 |
| 계획 시간 : 0.099 ms |
| 실행 시간 : 45.968 ms |

평범한 나무 :

drop index foo_btree_covering;
explain analyze select sum(b) from foo where a='a';
| 쿼리 계획 |
| : ------------------------------------------------- -------------------------------------------------- ----------------------------- |
| 집계 (비용 = 4064.57..4064.58 rows = 1 width = 8) (실제 시간 = 54.242..54.242 rows = 1 루프 = 1) |
| -> foo에서 foo_btree를 사용한 인덱스 스캔 (비용 = 0.43..3799.80 rows = 105907 너비 = 4) (실제 시간 = 0.037..33.084 rows = 100000 루프 = 1) |
| 인덱스 조건 : (a = 'a':: text) |
| 계획 시간 : 0.135 ms |
| 실행 시간 : 54.280 ms |

BRIN pages_per_range = 4 :

drop index foo_btree;
explain analyze select sum(b) from foo where a='a';
| 쿼리 계획 |
| : ------------------------------------------------- -------------------------------------------------- ----------------------------- |
| 집계 (비용 = 21595.38..21595.39 rows = 1 너비 = 8) (실제 시간 = 52.455..52.455 rows = 1 루프 = 1) |
| -> foo에서 비트 맵 힙 스캔 (cost = 888.78..21330.61 rows = 105907 width = 4) (실제 시간 = 2.738..31.967 rows = 100000 루프 = 1) |
| Cond : (a = 'a':: text)를 다시 확인하십시오. |
| 인덱스 재검사로 제거 된 행 : 96 |
| 힙 블록 : lossy = 736 |
| -> foo_brin_4의 비트 맵 인덱스 스캔 (비용 = 0.00..862.30 행 = 105907 너비 = 0) (실제 시간 = 2.720..2.720 행 = 7360 루프 = 1) |
| 인덱스 조건 : (a = 'a':: text) |
| 계획 시간 : 0.101 ms |
| 실행 시간 : 52.501ms |

BRIN pages_per_range = 2 :

drop index foo_brin_4;
explain analyze select sum(b) from foo where a='a';
| 쿼리 계획 |
| : ------------------------------------------------- -------------------------------------------------- ----------------------------- |
| 집계 (비용 = 21659.38..21659.39 행 = 1 너비 = 8) (실제 시간 = 53.971..53.971 행 = 1 루프 = 1) |
| -> foo의 비트 맵 힙 스캔 (cost = 952.78..21394.61 rows = 105907 width = 4) (실제 시간 = 5.286..33.492 rows = 100000 루프 = 1) |
| Cond : (a = 'a':: text)를 다시 확인하십시오. |
| 인덱스 재검사로 제거 된 행 : 96 |
| 힙 블록 : lossy = 736 |
| -> foo_brin_2의 비트 맵 인덱스 스캔 (비용 = 0.00..926.30 행 = 105907 너비 = 0) (실제 시간 = 5.275..5.275 행 = 7360 루프 = 1) |
| 인덱스 조건 : (a = 'a':: text) |
| 계획 시간 : 0.095 ms |
| 실행 시간 : 54.016 ms |

진:

drop index foo_brin_2;
explain analyze select sum(b) from foo where a='a';
| 쿼리 계획 |
| : ------------------------------------------------- -------------------------------------------------- ------------------------------ |
| 집계 (비용 = 21687.38..21687.39 행 = 1 너비 = 8) (실제 시간 = 55.331..55.331 행 = 1 루프 = 1) |
| -> foo에서의 비트 맵 힙 스캔 (cost = 980.78..21422.61 rows = 105907 width = 4) (실제 시간 = 12.377..33.956 rows = 100000 루프 = 1) |
| Cond : (a = 'a':: text)를 다시 확인하십시오. |
| 힙 블록 : exact = 736 |
| -> foo_gin에서 비트 맵 인덱스 스캔 (비용 = 0.00..954.30 행 = 105907 너비 = 0) (실제 시간 = 12.271..12.271 행 = 100000 루프 = 1) |
| 인덱스 조건 : (a = 'a':: text) |
| 계획 시간 : 0.118 ms |
| 실행 시간 : 55.366 ms |

여기 dbfiddle


따라서 커버링 인덱스는 디스크 공간을 희생하여 테이블을 읽는 것을 완전히 생략합니까? 좋은 트레이드 오프 인 것 같습니다. 나는 우리가 '전체 색인을 읽습니다'(B 틀린 경우 수정하십시오)를 통해 BRIN 색인에 대해 동일한 것을 의미한다고 생각합니다. 나는 dbfiddle.uk/ 에서 발생하는 것으로 생각되는 전체 BRIN 색인을 스캔하는 것을 의미했습니다 .
foo

" 에 대해 @foo "(테이블이 물리적으로 정렬되어 있다고 가정 할 수 없기 때문에 그것도있다) " 테이블의 물리적 순서 (클러스터 여부)는 관련이 없습니다. 인덱스는 올바른 순서로 값을 갖습니다. 그러나 Postgres B- 트리 인덱스는 모든 값을 저장해야합니다 (예, 여러 번). 그것이 그들이 설계 한 방식입니다. 각각의 고유 한 값을 한 번만 저장하면 좋은 기능 / 개선이됩니다. Jack은 Postgres 개발자에게 제안 할 수 있으며 심지어 구현하는 데 도움이 될 수도 있습니다. Jack은 오라클의 b-tree 구현이 그렇게한다고 생각합니다.
ypercubeᵀᴹ

1
@foo — BRIN 인덱스 스캔은 항상 전체 인덱스 ( pgcon.org/2016/schedule/attachments/… , 마지막 슬라이드 2 개)를 스캔 하지만 바이올린의 Explain Plan에는 표시되지 않습니다. 입니까?
잭 topanswers.xyz 시도라고

2
@ ypercubeᵀᴹ 당신은 각 고유 접두사를 블록 당 한 번 저장하는 Oracle에서 COMPRESS를 사용할 수 있습니다.
잭 topanswers.xyz 시도라고

@ JackDouglas 나는 Bitmap Index Scan'전체 brin 지수를 읽는다'라는 의미로 읽었지만 어쩌면 그것은 잘못 읽었습니다. 오라클 COMPRESS은 B- 트리의 크기를 줄이므로 여기에 유용한 것으로 보이지만 pg에 붙어 있습니다!
foo

6

가장 합리적인 옵션 인 btreebrin 외에도 조사 할 가치가있는 다른 이국적인 옵션이 있습니다. 귀하의 경우 도움이 될 수도 있고 아닐 수도 있습니다.

  • INCLUDE색인 . 그들은 것입니다 - 희망 - 포스트 그레스의 다음 주요 버전 (10)에, 어딘가 9월 2017 지수의 주위에 (a) INCLUDE (b)의 인덱스와 동일한 구조를 가지고 (a)있지만, 모든 값 리프 페이지에 포함 b(단, 정렬되지 않은). 예를 들어에 사용할 수 없습니다 SELECT * FROM t WHERE a = 'a' AND b = 2 ;. 인덱스가 사용될 수 있지만 (a,b)인덱스가 단일 탐색으로 일치하는 행을 찾는 동안 포함 인덱스는 값과 일치 a = 'a'하고 값을 확인하는 b값 ( 경우에 따라 100K)을 통과 해야 합니다.
    반면에, 인덱스는 인덱스보다 약간 덜 넓으 므로 쿼리를 계산 (a,b)하기 위해 주문할 필요가 없습니다 . 예를 들어bSUM(b)(a) INCLUDE (b,c,d) 세 열 모두에서 집계되는 것과 유사한 검색어에 사용할 수 있습니다.

  • 필터링 된 (부분) 인덱스 . 힘이 조금 미친 소리 것을 제안 *을 처음 :

    CREATE INDEX flt_a  ON t (b) WHERE (a = 'a') ;
    ---
    CREATE INDEX flt_xy ON t (b) WHERE (a = 'xy') ;

    a값 에 대해 하나의 색인 . 귀하의 경우 약 100K 색인. 이것이 많이 들리지만 크기 (행 수)와 너비 ( b값만 저장하므로)에 따라 각 인덱스가 매우 작을 것입니다 . 그러나 다른 모든 측면에서 인덱스 공간 (100K 인덱스)은 인덱스 (a,b)공간을 사용하는 동안 b- 트리 인덱스 역할을합니다 (b).
    단점은 새로운 값 a이 테이블에 추가 될 때마다 직접 작성하고 유지 보수해야한다는 것 입니다. 많은 (또는 어떤) 삽입 / 업데이트없이 테이블이 다소 안정적이기 때문에 문제가되지 않습니다.

  • 요약 테이블. 테이블이 다소 안정적이므로 항상 가장 일반적인 집계 ( sum(b), sum(c), sum(d), avg(b), count(distinct b), 등) 로 요약 테이블을 작성하고 채울 수 있습니다 . 크기는 작고 (100K 행만) 한 번만 채워지고 기본 테이블에서 행이 삽입 / 업데이트 / 삭제 될 때만 업데이트되어야합니다.

* : 프로덕션 시스템에서 천만 개의 인덱스를 실행하는이 회사의 아이디어 : 힙 : 프로덕션에서 천만 개의 Postgresql 인덱스 실행 (및 계산) .


1은 흥미롭지 만 pg 10은 아직 나오지 않았습니다. 2 미친 듯이 들리거나 (또는 ​​적어도 '일반적인 지혜'에 반대), 거의없는 쓰기 워크 플로로 작동 할 수 있다고 지적한 이래로 읽을 것입니다. 3. 저를 위해 일하고, 내가 사용하지 않는 것 SUM(예를 들어,하지만 실제로 내 쿼리가 미리 계산 될 수없는 그들이있어 더 같은 select ... from t where a = '?' and ??wjere가 ??다른 사용자 정의 상태가 될 것입니다.
foo는

1
글쎄, 우리가 무엇인지 모른다면 도울 수 없다 ??.)
ypercubeᵀᴹ

필터링 된 인덱스를 언급했습니다. 테이블 파티셔닝은 어떻습니까?
jpmc26

@ jpmc26 funny, 필터링 된 인덱스의 제안이 일종의 파티셔닝이라는 대답을 추가하려고 생각했습니다. 파티셔닝도 도움이 될 수 있지만 확실하지 않습니다. 작은 인덱스 / 테이블이 많이 생깁니다.
ypercubeᵀᴹ

2
데이터가 거의 업데이트되지 않기 때문에 부분 커버링 btree 인덱스가 성능의 왕이 될 것으로 기대합니다. 그것이 100k 인덱스를 의미하더라도. 총 인덱스 크기는 가장 작습니다 (BRIN 인덱스는 제외하지만 Postgres는 힙 페이지를 추가로 읽고 필터링해야합니다). 동적 SQL을 사용하여 인덱스 생성을 자동화 할 수 있습니다. 이 관련 답변의 예문DO .
Erwin Brandstetter
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.