큰 PostgresSQL 테이블에서 COUNT / GROUP-BY의 성능을 향상 시킵니까?


24

PostgresSQL 9.2를 실행 중이며 약 6,700,000 행의 12 열 관계가 있습니다. 여기에는 3D 공간에 노드가 포함되어 있으며 각 노드는 사용자를 만든 노드를 참조합니다. 어떤 사용자가 몇 개의 노드를 만들 었는지 쿼리하려면 다음을 수행하십시오 ( explain analyze자세한 내용은 추가).

EXPLAIN ANALYZE SELECT user_id, count(user_id) FROM treenode WHERE project_id=1 GROUP BY user_id;
                                                    QUERY PLAN                                                         
---------------------------------------------------------------------------------------------------------------------------
 HashAggregate  (cost=253668.70..253669.07 rows=37 width=8) (actual time=1747.620..1747.623 rows=38 loops=1)
   ->  Seq Scan on treenode  (cost=0.00..220278.79 rows=6677983 width=8) (actual time=0.019..886.803 rows=6677983 loops=1)
         Filter: (project_id = 1)
 Total runtime: 1747.653 ms

보시다시피, 약 1.7 초가 걸립니다. 데이터 양을 고려하면 그리 나쁘지는 않지만 이것이 개선 될 수 있는지 궁금합니다. 사용자 열에 BTree 색인을 추가하려고 시도했지만 아무런 도움이되지 않았습니다.

다른 제안이 있습니까?


완전성을 위해, 이것은 외래 키 제약 조건, 참조 및 트리거없이 모든 인덱스를 포함한 완전한 테이블 정의입니다.

    Column     |           Type           |                      Modifiers                    
---------------+--------------------------+------------------------------------------------------
 id            | bigint                   | not null default nextval('concept_id_seq'::regclass)
 user_id       | bigint                   | not null
 creation_time | timestamp with time zone | not null default now()
 edition_time  | timestamp with time zone | not null default now()
 project_id    | bigint                   | not null
 location      | double3d                 | not null
 reviewer_id   | integer                  | not null default (-1)
 review_time   | timestamp with time zone |
 editor_id     | integer                  |
 parent_id     | bigint                   |
 radius        | double precision         | not null default 0
 confidence    | integer                  | not null default 5
 skeleton_id   | bigint                   |
Indexes:
    "treenode_pkey" PRIMARY KEY, btree (id)
    "treenode_id_key" UNIQUE CONSTRAINT, btree (id)
    "skeleton_id_treenode_index" btree (skeleton_id)
    "treenode_editor_index" btree (editor_id)
    "treenode_location_x_index" btree (((location).x))
    "treenode_location_y_index" btree (((location).y))
    "treenode_location_z_index" btree (((location).z))
    "treenode_parent_id" btree (parent_id)
    "treenode_user_index" btree (user_id)

편집 : 이것은 @ypercube가 제안한 쿼리 (및 인덱스)를 사용할 때의 결과입니다 (쿼리없이 5.3 초 소요 EXPLAIN ANALYZE).

EXPLAIN ANALYZE SELECT u.id, ( SELECT COUNT(*) FROM treenode AS t WHERE t.project_id=1 AND t.user_id = u.id ) AS number_of_nodes FROM auth_user As u;
                                                                        QUERY PLAN                                                                     
----------------------------------------------------------------------------------------------------------------------------------------------------------
 Seq Scan on auth_user u  (cost=0.00..6987937.85 rows=46 width=4) (actual time=29.934..5556.147 rows=46 loops=1)
   SubPlan 1
     ->  Aggregate  (cost=151911.65..151911.66 rows=1 width=0) (actual time=120.780..120.780 rows=1 loops=46)
           ->  Bitmap Heap Scan on treenode t  (cost=4634.41..151460.44 rows=180486 width=0) (actual time=13.785..114.021 rows=145174 loops=46)
                 Recheck Cond: ((project_id = 1) AND (user_id = u.id))
                 Rows Removed by Index Recheck: 461076
                 ->  Bitmap Index Scan on treenode_user_index  (cost=0.00..4589.29 rows=180486 width=0) (actual time=13.082..13.082 rows=145174 loops=46)
                       Index Cond: ((project_id = 1) AND (user_id = u.id))
 Total runtime: 5556.190 ms
(9 rows)

Time: 5556.804 ms

편집 2 : 이것은 @ erwin-brandstetter가 제안한대로 indexon을 사용 project_id, user_id하지만 스키마 최적화는 아직 사용하지 않은 결과입니다 (쿼리는 원래 쿼리와 같은 속도로 1.5 초 동안 실행됩니다)

EXPLAIN ANALYZE SELECT user_id, count(user_id) as ct FROM treenode WHERE project_id=1 GROUP BY user_id;
                                                        QUERY PLAN                                                      
---------------------------------------------------------------------------------------------------------------------------
 HashAggregate  (cost=253670.88..253671.24 rows=37 width=8) (actual time=1807.334..1807.339 rows=38 loops=1)
   ->  Seq Scan on treenode  (cost=0.00..220280.62 rows=6678050 width=8) (actual time=0.183..893.491 rows=6678050 loops=1)
         Filter: (project_id = 1)
 Total runtime: 1807.368 ms
(4 rows)

기본 키로 테이블 Usersuser_id있습니까?
ypercubeᵀᴹ

방금 Postgres 용 타사 columnstore addon이 있음을 보았습니다. 또한 새로운 ios 앱에서 게시하고 싶었습니다
swasheck

2
버전, 테이블 정의 등 좋은 명확하고 완전한 질문에 감사드립니다.
Craig Ringer

@ypercube 예, Users 테이블이 있습니다.
tomka

얼마나 많은 다른 project_iduser_id? 테이블이 지속적으로 업데이트됩니까? 아니면 구체화 된 뷰로 작업 할 수 있습니까?
Erwin Brandstetter

답변:


25

주요 문제는 누락 된 인덱스입니다. 그러나 더 있습니다.

SELECT user_id, count(*) AS ct
FROM   treenode
WHERE  project_id = 1
GROUP  BY user_id;
  • 많은 bigint열이 있습니다. 아마 과잉입니다. 일반적으로 and와 integer같은 열에는 충분합니다 . 다음 항목에도 도움이됩니다. 테이블 정의를 최적화하는 동안 데이터 정렬패딩 에 중점을 둔이 관련 답변을 고려하십시오 . 그러나 나머지 부분도 대부분 적용됩니다.project_iduser_id

  • 방에 코끼리 : 더이없는 인덱스project_id . 하나를 만드십시오. 이 답변의 나머지 부분보다 더 중요합니다.
    그것에있는 동안 다중 열 색인을 만드십시오.

    CREATE INDEX treenode_project_id_user_id_index ON treenode (project_id, user_id);

    내 충고를 따랐다면 integer여기에서 완벽 할 것입니다.

  • user_id는 정의 NOT NULL되었으므로 count(user_id)와 동일 count(*)하지만 후자는 약간 짧고 빠릅니다. (이 특정 쿼리에서는 user_id정의 되지 않은 상태에서도 적용 됩니다 NOT NULL.)

  • id이미 기본 키이며 추가 UNIQUE제약 조건은 쓸모없는 밸러스트 입니다. 그거 떨어 뜨려:

    "treenode_pkey" PRIMARY KEY, btree (id)
    "treenode_id_key" UNIQUE CONSTRAINT, btree (id)

    옆으로 : 나는 id열 이름으로 사용하지 않을 것 입니다. 과 같은 설명을 사용하십시오 treenode_id.

추가 된 정보

Q : How many different project_id and user_id?
A : not more than five different project_id.

즉, Postgres는 쿼리를 충족시키기 위해 전체 테이블의 약 20 % 를 읽어야 합니다. 인덱스 전용 스캔을 사용할 수 없으면 테이블의 순차적 스캔이 인덱스를 포함하는 것보다 빠릅니다. 테이블 및 서버 설정을 최적화하지 않는 한 더 이상 성능이 향상되지 않습니다.

에 관해서는 인덱스 만 검사 :이 될 수 얼마나 효과적 참조하려면 실행 VACUUM ANALYZE당신은 그 (잠금 테이블을 독점적으로) 줄 수있는 경우에. 그런 다음 쿼리를 다시 시도하십시오. 이제 색인 사용 하면 적당히 빨라 집니다. 이 관련 답변을 먼저 읽으십시오.

인덱스 전용 스캔에서 Postgres 9.6Postgres Wiki함께 추가 된 매뉴얼 페이지는 물론 입니다.


1
어윈, 제안 해 주셔서 감사합니다. 당신 말이 맞아, 대한 user_idproject_id integer충분해야한다. 여기서 약 70ms count(*)count(user_id)절약하는 대신 사용하면 알 수 있습니다. 내가 추가 한 EXPLAIN ANALYZE나는 당신의 제안 추가 한 후 쿼리의 index첫 번째 게시물에. 그러나 성능을 향상 시키지는 않지만 아프지 않습니다. index전혀 사용되지 않는 것 같습니다 . 스키마 최적화를 곧 테스트하겠습니다.
tomka

1
을 비활성화 seqscan하면 인덱스가 사용 Index Only Scan using treenode_project_id_user_id_index on treenode되지만 ( ) 쿼리에 약 2.5 초가 소요됩니다 (seqscan보다 약 1 초 더 깁니다).
tomka

1
업데이트 해 주셔서 감사합니다. 이 누락 된 비트는 내 질문의 일부 였어야합니다. 나는 단지 그들의 영향을 알지 못했습니다. 제안한대로 스키마를 최적화하겠습니다. 그로부터 얻을 수있는 것을 살펴 보겠습니다. 설명해 주셔서 감사합니다. 이해가되므로 답변을 허용 된 것으로 표시하겠습니다.
tomka

7

먼저 색인을 추가 한 (project_id, user_id)다음 9.3 버전 에서이 쿼리를 시도하십시오.

SELECT u.user_id, c.number_of_nodes 
FROM users AS u
   , LATERAL
     ( SELECT COUNT(*) AS number_of_nodes 
       FROM treenode AS t
       WHERE t.project_id = 1 
         AND t.user_id = u.user_id
     ) c 
-- WHERE c.number_of_nodes > 0 ;   -- you probably want this as well
                                   -- to show only relevant users

9.2에서 다음을 시도하십시오.

SELECT u.user_id, 
       ( SELECT COUNT(*) 
         FROM treenode AS t
         WHERE t.project_id = 1 
           AND t.user_id = u.user_id
       ) AS number_of_nodes  
FROM users AS u ;

users테이블 이 있다고 가정 합니다. 그렇지 않은 경우 다음으로 교체하십시오 users.
(SELECT DISTINCT user_id FROM treenode)


대답 해 주셔서 감사합니다. 당신은 맞습니다, 사용자 테이블이 있습니다. 그러나 9.2에서 쿼리를 사용하면 인덱스 생성 여부에 관계없이 결과를 얻는 데 약 5 초가 걸립니다. 나는 다음과 같은 색인을 CREATE INDEX treenode_user_index ON treenode USING btree (project_id, user_id);만들었지 만 USING절 없이 시도했다 . 내가 뭔가를 그리워합니까?
tomka

users테이블 에는 몇 개의 행이 있고 쿼리는 몇 개의 행을 반환합니까 (그래서 몇 명의 사용자가 project_id=1있습니까?)? 색인을 추가 한 후이 쿼리에 대한 설명을 보여줄 수 있습니까?
ypercubeᵀᴹ

1
첫째, 나는 첫 번째 의견에서 틀렸다. 제안 된 색인이 없으면 결과를 검색하는 데 약 40 초 (!)가 걸립니다. 약 5 초가 소요 index됩니다. 혼란을 드려 죄송합니다. 내 users테이블 에는 46 개의 항목이 있습니다. 쿼리는 9 개의 행만 반환합니다. 놀랍게도 SELECT DISTINCT user_id FROM treenode WHERE project_id=1;38 행을 반환합니다. explain첫 번째 게시물에를 추가했습니다 . 그리고 혼란을 피하기 위해 : 내 users테이블은 실제로라고 auth_user합니다.
tomka

SELECT DISTINCT user_id FROM treenode WHERE project_id=1;쿼리가 9를 반환하는 동안 38 행을 반환 하는 방법이 궁금합니다 .
ypercubeᵀᴹ

당신은 이것을 시도 할 수 있습니까? :SET enable_seqscan = OFF; (Query); SET enable_seqscan = ON;
ypercubeᵀᴹ
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.