My Books

My Slides

rss 아이콘 이미지

Search

'Parallel Index Scan'에 해당되는 글 1건

  1. 2017.05.24 PostgreSQL 10 - Parallel Index Scan 소개 (1)

PostgreSQL 10 - Parallel Index Scan 소개

PostgreSQL 2017.05.24 22:37 Posted by 시연아카데미

이번 시간에 소개할 내용은 PostgreSQL 10 버전부터 지원하는 Parallel Index Scan 기능입니다.

 

기능을 확인하기 전까지는 ORACLE의 Parallel Index Fast Full Scan 기능과 유사할 것으로 생각했는데, 조금 더 스마트합니다. (박수!!)

 

PostgreSQL 10 버전의 Parallel Index Scan 기능은 Index Range Scan시에도 병렬 처리가 가능합니다.

 

즉, ORACLE은 Index Fast Full Scan 방식을 이용해서 전체 인덱스 블록을 스캔할 때만 병렬 처리가 가능하지만, PostgreSQL은 인덱스의 특정 범위에 대한 병렬 처리도 가능합니다.

 

알고리즘은 다음과 같습니다.

  1. 조건절 범위에 해당하는 "Start 리프 블록"과 "End 리프 블록"의 위치를 계산합니다.
  2. 처리 범위가 인덱스 병렬 처리를 할 정도로 큰지 확인합니다.
  3. 만일 크다면, 크기에 따라서 Worker 개수를 설정한 후에, "Start" ~ "End" 범위를 나눠서 처리합니다.
  4. 만일 작다면, 싱글 프로세스로 처리합니다. 
      

이처럼, 인덱스 Range 스캔 시에도 병렬 처리를 지원함으로써 튜닝 시에 더욱 다양한 방법을 적용할 수 있게 됐습니다. (튜너 입장에서는 좋은 무기를 하나 얻은 셈입니다)

 

그럼 테스트 내용을 살펴보겠습니다.

 

 

테스트 환경 구성

 

테스트 환경은 이전 포스트에서 사용한 환경을 이용합니다.

 

그리고 np1 테이블의 c1 칼럼에 인덱스를 생성합니다.

 

create index np1_c1_idx on np1(c1);

 

Parallel 처리와 관련된 파라미터 확인 및 설정 변경

 

아래의 파라미터 중에서 Worker 개수와 관련된 파라미터인 max_parallel_workers는 24로, max_parallel_workers_per_gather는 8로 변경한 후에 재기동합니다.

 

postgres=# select name, setting, unit from pg_settings where name like '%parallel%';
              name               | setting | unit
---------------------------------+---------+------
 force_parallel_mode             | off     |
 max_parallel_workers            | 8       |
 max_parallel_workers_per_gather | 2       |
 min_parallel_index_scan_size    | 64      | 8kB
 min_parallel_table_scan_size    | 1024    | 8kB

 

 

-- 재기동 후에 변경 내용 확인

 

재기동 후에 파라미터가 정상적으로 변경된 것을 확인합니다.

 

postgres=# select name, setting, unit from pg_settings where name like '%parallel%';
              name               | setting | unit
---------------------------------+---------+------
 force_parallel_mode             | off     |
 max_parallel_workers            | 24      |
 max_parallel_workers_per_gather | 8       |
 min_parallel_index_scan_size    | 64      | 8kB
 min_parallel_table_scan_size    | 1024    | 8kB

 

-- min_parallel_table_scan_size 파라미터의 의미

 

해당 파라미터는 테이블 병렬 처리 여부를 결정하는 테이블 최소 크기를 정의합니다. 기본설정값은 8MB입니다. 즉, 8MB 이상이면 병렬 처리 대상입니다. 그리고 해당 크기의 3배수 단위로 Worker가 1개씩 추가되며, 최대 7까지 할당됩니다.

 

-- min_parallel_index_scan_size 파라미터의 의미

 

해당 파라미터는 인덱스 병렬 처리 여부를 결정하는 인덱스 최소 크기를 정의합니다. 기본설정값은 512kB입니다. 즉, 512kB 이상이면 병렬 처리 대상입니다. 그리고 해당 크기의 3배수 단위로 Worker가 1개씩 추가되며, 최대 7까지 할당됩니다.

 

 

Parallel Index Scan 동작 방식 세부 내용

 

테이블 병렬 처리는 통계 정보를 이용합니다. 그런데 인덱스 병렬 처리는 조건절에 해당하는 범위를 이용해서 결정합니다. 이 부분은 소스를 잠깐 보는 게 도움이 될 것 같습니다.

 

아래 소스를 보면, 처리 대상 인덱스 페이지수를 계산한 결과를 index_pages 변수에 저장하고, 해당 값과 min_parallel_index_scan_size 값을 비교합니다.

 

그리고 min_parallel_index_scan_size * 3을 수행하면서 Worker 개수를 1씩 증가한다는 것을 알 수 있습니다.

 

그리고 소스의 아랫 부분을 보면, Worker의 개수는 "테이블 병렬 처리를 위해 계산된 수치와 인덱스 병렬 처리를 위해 계산된 수치 중에서 더 작은 수치로 설정"한다는 것을 알 수 있습니다.

 

src/backend/optimizer/path/allpaths.c

if (index_pages >= 0)
{
        int                     index_parallel_workers = 1;
        int                     index_parallel_threshold;

        /* same calculation as for heap_pages above */
		index_parallel_threshold = Max(min_parallel_index_scan_size, 1);        
		while (index_pages >= (BlockNumber) (index_parallel_threshold * 3)) 
        {
                index_parallel_workers++;
                index_parallel_threshold *= 3;
                if (index_parallel_threshold > INT_MAX / 3)
                        break;          /* avoid overflow */
        }		

        if (parallel_workers > 0) 
                parallel_workers = Min(parallel_workers, index_parallel_workers);
        else
                parallel_workers = index_parallel_workers;

 

Parallel Worker 개수 산정 방식

 

테이블과 인덱스 스캔 시에 사용되는 Parallel Worker 개수는 다음과 같습니다.

 

표-1. Parallel Worker 개수

 

테스트 #1: Parallel Index Scan으로 수행 (범위는 백만)

 

아래의 쿼리를 수행하면, Worker 프로세스는 3개가 론칭됩니다. 왜 3개인지 살펴볼까요?

 

테이블 블록 수는 23,249이므로, 표-1에 따라서 Worker는 3개입니다.

 

그리고 인덱스 처리 범위는 5,465이므로, 표-1에 따라서 Worker는 5개입니다. 3개와 5개 중에서 최솟값을 선택하므로 3개의 Worker가 론칭되는 것입니다.

 

 
explain (analyze, buffers) 
select count(*) from np1 where c1 between 1000000 and 2000000 and dummy='dummy';

[DEBUG_INDEX_PQ] heap_pages=[23249.000000] min_parallel_table_scan_size=[1024]  index_pages=[2703.000000] min_parallel_index_scan_size=[64]

                                                               QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------------
 Finalize Aggregate  (cost=32710.82..32710.83 rows=1 width=8) (actual time=263.027..263.027 rows=1 loops=1)
   Buffers: shared hit=12035
   ->  Gather  (cost=32710.50..32710.81 rows=3 width=8) (actual time=263.014..263.023 rows=4 loops=1)
         Workers Planned: 3
         Workers Launched: 3
         Buffers: shared hit=12035
         ->  Partial Aggregate  (cost=31710.50..31710.51 rows=1 width=8) (actual time=250.533..250.533 rows=1 loops=4)
               Buffers: shared hit=11612
               ->  Parallel Index Scan using np1_c1_idx on np1  (cost=0.43..30915.98 rows=317806 width=0) (actual time=0.032..186.220 rows=250000 loops=4)
                     Index Cond: ((c1 >= 1000000) AND (c1 <= 2000000))
                     Filter: (dummy = 'dummy'::bpchar)
                     Buffers: shared hit=11612
 Planning time: 0.123 ms
 Execution time: 264.310 ms

 

테스트 #2: Serial Index Scan으로 수행 (범위는 2백만)

 

Parallel Index Scan의 성능을 확인해볼까요? max_parallel_workers_per_gather 파라미터를 0으로 설정한 후에 Serial Index Scan으로 수행하면 Parallel Index Scan보다 50% 이상 느립니다.

 

물론, 처리 범위와 환경에 따라서 성능 차이는 다르겠지만, Parallel Index Scan은 매우 효과적인 튜닝 방안인 것으로 보입니다.

 

set max_parallel_workers_per_gather=0;

explain (analyze, buffers)
select count(*) from np1 where c1 between 1000000 and 2000000 and dummy='dummy';
                                                               QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=41721.38..41721.39 rows=1 width=8) (actual time=431.104..431.104 rows=1 loops=1)
   Buffers: shared hit=9105
   ->  Index Scan using np1_c1_idx on np1  (cost=0.43..39258.39 rows=985198 width=0) (actual time=0.044..299.974 rows=1000001 loops=1)
         Index Cond: ((c1 >= 1000000) AND (c1 <= 2000000))
         Filter: (dummy = 'dummy'::bpchar)
         Buffers: shared hit=9105
 Planning time: 0.119 ms
 Execution time: 431.169 ms

 

참고로, Parallel Index Scan은 Index Only Scan 방식 및 Bitmap Index Scan 방식에서도 동작합니다.

 

 

저작자 표시 비영리 변경 금지
신고
크리에이티브 커먼즈 라이선스
Creative Commons License


 

티스토리 툴바