728x90

김정선의 좋은 글을 찾아서……
SQL Server 2005 파티션 제외(Partition Elimination) 기능

 

저자: Lubor Kollar, SQLCAT 팀 블로그

원본: http://blogs.msdn.com/sqlcat/archive/2006/02/17/Partition-Elimination-in-SQL-Server-2005.aspx


 

사용자 삽입 이미지
김정선(jskim@feelanet.com)

필라넷 DB사업부 수석컨설턴트

SQLServer 아카데미/트라이콤 교육센터 강사

 

Microsoft SQL Server MVP

MCT/MCITP/MCDBA

 

 

 

역자 서문

김정선의 좋은 글을 찾아서 번째 글이네요 ^^

Vista 사이드 바에 추가할 블로그 피드를 정리하다가, 오랜만에 sqlcat(아시죠? ^^) 블로그까지 가게 되었습니다. 좋은 글이 없나 뒤적거리다가 마침 파티션 관련된 글이 있어서 선정하게 되었습니다. 2006 2월에 쓰여진 글이네요.

 

Partition Elimination(역주: 불필요한 파티션을 검색하지 않는) 파티션 쿼리에서 성능 상에 중요한 이슈가 밖에 없습니다. 이미 이전의 파티션 뷰에서부터 그래왔으니까요. 관련된 내용이라 더욱 관심이 갔습니다. 그렇지 않아도 요즘 파티션 관련해서 복습을 하고 있거든요.

 

파티션 보셨나요? 얘기 해주세요, 읽지만 마시고 ^^

 

번역에 대해서 말씀 드리면,

번역은 역시나 역자 마음대로 합니다 ^^. 원문이 있으니 충분히 참고하실 있으실 거구요.

제가 여전히 강의나 세미나를 많이 하는 터라, 가급적 용어는 온라인 설명서(“분할이라고 번역) 맞추려고 합니다만, 파티션은 워낙 이전부터 통상적으로 써오던 표현이라 그냥 파티션이라고 했습니다.

원문 그대로 번역하지 않고 필요에 따라 생략하거나 추가 언급한 부분도 있습니다. 쉽게 읽으실 있도록

 

아무튼 좋은 글이라 생각되니, 여러분들에게도 도움이 되셨으면 합니다.

 

덧글 달아주실거죠? ^^

 

 

본문

파티션 제외 기능은 파티션 테이블이나 파티션 쿼리를 실행할 SQL Server에서 매우 중요한 부분이다. 일반적으로 SQL Server 조건자에 의해서 제외되는 파티션 영역을 스캔하지 않는 방식으로 이러한 기능을 수행해 낸다. 그런데 최근에 SQL Server 2005 파티션 테이블에 대해서 이러한 파티션 제외 기능이 제대로 동작하는 않는 시나리오를 발견했다. 여기서 문제가 되는 해당 조건과  해결 방안을 소개하고자 한다. 추가로 파티션 쿼리가 이러한 제외 기능을 수행하는지 판단하는 방법도 배우게 것이며, 정적 동적 파티션 제외가 무엇인지도 배우게 것이다. (역주: 사실 부분 때문에 번역 글로 선택했습니다. ^^)

 

파티션 제외가 제대로 수행되는지 확인하는 가장 좋은 방법은 SET STATISTICS PROFILE ON 명령을 사용하는 (혹은 그래픽 실행 계획)이다, 해당 쿼리를 실행하고 명령의 출력 결과를 확인하면 된다.

(역주: 최신 업데이트된 온라인 설명서에는 “Interpreting Execution Plans of Partitioned Objects라는 주제의 새로운 문서가 포함되어 있습니다. 내용은 짧지만, 읽어보세요 ^^ )

우선 예제 테이블부터 만들자:

 

create partition function PF1 (int) as range for values (100,200,300,400);

create partition scheme PS1 as partition PF1 all to ([PRIMARY]);

go

create table t1 (a int, b int) on PS1 (a);

go

declare @i int;

set @i=1;

set nocount on;

while (@i<22)

begin;

insert into t1 values (20*@i, @i);

set @i=@i+1;

end;

go

 

다음 쿼리는 t1 테이블의 5 파티션에 걸친 데이터의 분포를 알려준다:

 

select $partition.PF1(a) [Partition Number], a, b from t1

 

Partition Number       a         b

1                                      20         1

1                                      40         2

1                                      60         3

1                                      80         4

1                                      100       5

2                                      120       6

2                                      140       7

2                                      160       8

2                                      180       9

2                                      200       10

3                                      220       11

3                                      240       12

3                                      260       13

3                                      280       14

3                                      300       15

4                                      320       16

4                                      340       17

4                                      360       18

4                                      380       19

4                                      400       20

5                                      420       21

 

우선, SQL Server 2005에서 파티션 제외가 정상적으로 수행되는 5개의 예제를 보여줄 것이다. 그리고 정적 동적 파티션 제외의 차이를 설명한다. 출력 결과에 대해서도 이후에 확인한다.

 

set statistics profile on;

declare @i1 int;

declare @i2 int;

set @i1=50;

set @i2=250 ;

select * from t1 where a<50 or  a>450; -- (Q1) 2 파티션만 스캔

select * from t1 where a in (50,450); -- (Q2) 2 파티션만 스캔

 

select * from t1 where a<@i2 and  a>100; -- (Q3) 2 파티션만 스캔

 

select * from t1 where a=100;-- (Q4) 1 파티션만 스캔 정적 파티션 제외

select * from t1 where a=@i2; -- (Q5) 1 파티션만 스캔 동적 파티션 제외

set statistics profile off;

 

쿼리의 결과 집합 다음에 showplan 출력(역주: 흔히 말하는 쿼리 (실행) 계획) 보여준다. 여기서 “Rows” “Executes” 항목을 살펴보자.

쿼리 Q1, select * from t1 where a<50 or  a>450 대한 결과를 보면:

(역주: 쿼리 계획 상의 주석은 번역하지 않았습니다)

 

Rows     Executes             StmtText

2           1           select * from t1 where a<50 or  a>450; -- (Q1) only two partitions

2           1             |--Nested Loops(Inner Join, OUTER REFERENCES:([PtnIds1004]) PAR

2           1                  |--Constant Scan(VALUES:(((1)),((5))))

2           2                  |--Table Scan(OBJECT:([test].[dbo].[t1]), WHERE:([test].[d

 

파티션 테이블의 스캔(검색) 파티션에 Nested Loop 반복하고 있다. 그에 앞서 Constant Scan 연산자에서 파티션 1 5만을 스캔할 것임을 있다. “Executes” 2 개별 파티션에 대해 2 스캔했음을 확인시켜준다.

 

쿼리 Q2 IN “a in (50, 450)” “a = 50 OR a = 450”으로 변환되고, 1 5 파티션만 스캔하는 것을 보여준다.

 

Rows     Executes             StmtText

0           1           select * from t1 where a in (50,450); -- (Q2) only two partitions are sc

0           1             |--Nested Loops(Inner Join, OUTER REFERENCES:([PtnIds1004]) PAR

2           1                  |--Constant Scan(VALUES:(((1)),((5))))

0           2                  |--Table Scan(OBJECT:([test].[dbo].[t1]), WHERE:([test].[dbo].[t

 

번째 쿼리 Q3,

select * from t1 where a<@i2 and  a>100

결과는

 

Rows     Executes             StmtText

7           1           select * from t1 where a<@i2 and  a>100; -- (Q3) only two partitions

7           1             |--Nested Loops(Inner Join, OUTER REFERENCES:([PtnIds1004]) PAR

2           1                  |--Filter(WHERE:([PtnIds1004]<=RangePartitionNew([@i2],(0)

4           1                  |    |--Constant Scan(VALUES:(((2)),((3)),((4)),((5))))

7           2                  |--Table Scan(OBJECT:([test].[dbo].[t1]), WHERE:([test].[d

 

Constant Scan 연산자를 보면, 우선 정적으로 파티션 1 제외된 것을 있다. 이는 “a > 100”이라는 조건절 때문에 가능한 것이다. 그리고 위에 Filter 연산자의 내용을 보면 “a < @i2” 조건자를 사용해서 암시적으로 보다 많은 파티션을 제외하고 있다. 후자가 바로 동적 제외이다. 이는 제외할 파티션을 결정하는데 사용될 로컬변수 @i2 값을 컴파일 타임이 아닌 런타임에 있기 때문이다. 실제로 Table Scan 연산자의 “Executes” 항목을 보면 2번만 스캔하고 있음을 있다.

 

쿼리 Q4

select * from t1 where a=100

앞서 4개의 쿼리와 달리 쿼리는 Constant Scan 연산자가 없다. 이는 SQL Server 컴파일 타임에 하나의 파티션만 필요하다는 것을 있기 때문이다. 아래 쿼리 계획을 보면

 

Rows     Executes             StmtText

1           1           SELECT * FROM [t1] WHERE [a]=@1

1           1             |--Table Scan(OBJECT:([test].[dbo].[t1]), WHERE:([test].[dbo]. [t1].[a]=(100)) PARTITION ID:((1)))

 

이를 쿼리 Q5 비교해 보면

select * from t1 where a=@i2.

물론 최종 실행은 하나의 파티션만 스캔하고 있지만 컴파일 타임에는 로컬변수 값을 없다. 따라서 WHERE절이 매개변수로 처리되고 있다.

 

Rows     Executes             StmtText

0           1           select * from t1 where a=@i2; -- (Q5) only one partition is scanned 0          1             |--Table Scan(OBJECT:([test].[dbo].[t1]), WHERE:([test].[dbo].[t1].[a]=[@i2]) PARTITION ID:(RangePartitionNew([@i2],(0),(100),(200),(300),(400))))

 

예제는 SQL Server 파티션 제외 기능이 제대로 수행되고 있음을 보여준다.

 

 

이제 다음의 경우를 살펴보자

 

set statistics profile on;

declare @i1 int;

declare @i2 int;

set @i1=50;

set @i2=250;

select * from t1 where a<50 or  a>@i2; -- (Q6)

select * from t1 where a<@i1 or  a>@i2; -- (Q7)

select * from t1 where a in (@i1,@i2); -- (Q8)

 

set statistics profile off;

 

경계 (100, 200, 300, 400) 쿼리의 로컬변수 @i1=50, @i2=250 따져 보면, 쿼리 Q6 Q7 번째 파티션(200미만) 제외할 있다고 판단된다. 그런데, Q6 Q7 쿼리 계획을 보면,

 

-- Q6

Rows     Executes             StmtText

11         1           select * from t1 where a<50 or  a>@i2 -- (Q6) all partitions are

11         1             |--Nested Loops(Inner Join, OUTER REFERENCES:([PtnIds1004]) PAR

5           1                  |--Constant Scan(VALUES:(((1)),((2)),((3)),((4)),((5))))

11         5                  |--Table Scan(OBJECT:([test].[dbo].[t1]), WHERE:([test].[d

 

-- Q7

Rows     Executes             StmtText

11         1           select * from t1 where a<@i1 or  a>@i2 -- (Q7) all partitions are

11         1             |--Nested Loops(Inner Join, OUTER REFERENCES:([PtnIds1004]) PAR

5           1                  |--Constant Scan(VALUES:(((1)),((2)),((3)),((4)),((5))))

11         5                  |--Table Scan(OBJECT:([test].[dbo].[t1]), WHERE:([test].[d

 

쿼리 Q8 경우는 파티션 1 3만을 스캔하면 된다. 그런데 5 모두 스캔하고 있다.

 

Rows     Executes             StmtText

0           1           select * from t1 where a in (@i1,@i2) -- (Q8)

0           1             |--Nested Loops(Inner Join, OUTER REFERENCES:([PtnIds1004]) PAR

5           1                  |--Constant Scan(VALUES:(((1)),((2)),((3)),((4)),((5))))

0           5                  |--Table Scan(OBJECT:([test].[dbo].[t1]), WHERE:([test].[d

 

 

SQL Server 2005에서 가지의 경우 모든 파티션을 스캔하고 있다. “SQL Server에서의 파티션 제외 기능은 제외식을 가진 조건자 사이에 OR 있고 매개변수화가 되는 경우 제대로 동작하지 않는다.”

IN 조건자 또한 OR 조건자로 변환될 것이고 하나가 매개 변수 값을 가지므로 조건에 만족한다.

(역주: 글의 답글을 보면, 유사 케이스도 있습니다)

 

해결 방법은 여러 가지가 존재한다. 가장 편한 방법은 OR 대신에 UNION ALL 사용하는 것이다. Q6 경우라면

 

select * from t1 where a<50

UNION ALL

select * from t1 where a>@i2

 

쿼리 계획은

 

Rows     Executes             StmtText

11         1           select * from t1 where a<50   UNION ALL  select * from t1 where a

11         1             |--Concatenation

2           1                  |--Table Scan(OBJECT:([test].[dbo].[t1]), WHERE:([test].[d

9           1                  |--Nested Loops(Inner Join, OUTER REFERENCES:([PtnIds1011]

3           1                       |--Filter(WHERE:([PtnIds1011]>=RangePartitionNew([@i2

5           1                       |    |--Constant Scan(VALUES:(((1)),((2)),((3)),((4))

9           3                       |--Table Scan(OBJECT:([test].[dbo].[t1]), WHERE:([tes

 

쿼리 계획을 보면 조건자 “a < 50” 대해서는 정적 제외를 하고 “a > @i2” 대해서는 동적 파티션 제외가 수행됨을 있다. 번째 것은 단일 파티션만 처리하고 번째(a >  250)에서 이후 3개의 파티션을 스캔할 것이다.

 

번째 쿼리 Q7 다음과 같이 작성할 있다.

 

select * from t1 where a<@i1

UNION ALL

select * from t1 where a>@i2

 

변경된 쿼리 계획은(역주: 예상하겠지만 ^^)                 

 

Rows     Executes             StmtText

11         1           select * from t1 where a<@i1   UNION ALL  select * from t1 where

11         1             |--Concatenation

2           1                  |--Nested Loops(Inner Join, OUTER REFERENCES:([PtnIds1010]

1           1                  |    |--Filter(WHERE:([PtnIds1010]<=RangePartitionNew([@i1

5           1                  |    |    |--Constant Scan(VALUES:(((1)),((2)),((3)),((4))

2           1                  |    |--Table Scan(OBJECT:([test].[dbo].[t1]), WHERE:([tes

9           1                  |--Nested Loops(Inner Join, OUTER REFERENCES:([PtnIds1011]

3           1                       |--Filter(WHERE:([PtnIds1011]>=RangePartitionNew([@i2

5           1                       |    |--Constant Scan(VALUES:(((1)),((2)),((3)),((4))

9           3                       |--Table Scan(OBJECT:([test].[dbo].[t1]), WHERE:([tes

 

번째 쿼리 Q7 또한 다음과 같다.

 

select * from t1 where a =@i1    

UNION ALL

select * from t1 where a =@i2

 

역시 실행 계획도

 

Rows     Executes             StmtText

0           1           select * from t1 where a =@i1   -- (Q7)   UNION ALL  select * fro

0           1             |--Concatenation

0           1                  |--Table Scan(OBJECT:([test].[dbo].[t1]), WHERE:([test].[d

0           1                  |--Table Scan(OBJECT:([test].[dbo].[t1]), WHERE:([test].[d

 

(역주: OR 문제를 해결하는데 많은 사용되는 것이 UNION ALL이죠! 이유는? 실행 계획을 명시적으로 분리할 있으니까! 그런데 방법 밖에 없나?, 그럼 안되는데…^^ )

 

또는 동적 쿼리 형태로 문자열로 쿼리를 결합해서 결합된 쿼리를 수행하도록 처리할 수도 있다.

 

(역주: 만일 쿼리를 UNION 바꿀 수도 없고, 동적 쿼리로 바꾸기에 너무 () 쿼리라면 어떻게 해야 하나?...여러분 다른 방법을 찾아주세요~~~ help me~~~)

 

요약

SQL Server에서 파티션 제외 기능을 확인하는 방법을 살펴봤다. 대부분의 경우 SQL Server 이를 훌륭히 처리해 주겠지만, 위에서 소개된 사례와 같이 제대로 수행되지 못하는 경우도 발생한다. 이런 경우에 반드시 쿼리 계획을 확인하고, 해당 쿼리가 정적 혹은 동적 파티션 제외가 제대로 수행되는지 확인해야 한다. SQL Server 개발 팀은 향후 서비스 팩이나 릴리스에서 위에서 언급한 문제들을 해결할 계획을 세우고 있다.

(역주: 현재 SP2, 9.00.3042 버전에서 테스트한 결과 아직 반영 ^^, SQL Server 2008은 어떨까요? ^^)

 

728x90

김정선의 좋은 글을 찾아서……
SQL Server 인덱스 구성 전략(시리즈-1. 소개)

 

 

사용자 삽입 이미지
김정선(jskim@feelanet.com)

필라넷 DB사업부 수석컨설턴트

SQLServer 아카데미/트라이콤 교육센터 강사

 

Microsoft SQL Server MVP

MCT/MCITP/MCDBA



저자: SQL Server Query Processing Team 블로그

원본: 원본 아래와 같이 9개의 포스트입니다만, 번역 주제별 나누어 4개의 포스트 올리겠습니다.

 

( 번째-소개)

http://blogs.msdn.com/sqlqueryprocessing/archive/2006/11/08/index-build-strategy-in-sql-server-introduction-i.aspx

http://blogs.msdn.com/sqlqueryprocessing/archive/2006/11/09/index-build-strategy-in-sql-server-introduction-ii.aspx


(두 번째-일반 인덱스)

http://blogs.msdn.com/sqlqueryprocessing/archive/2006/11/20/index-build-strategy-in-sql-server-part-1-offline-serial-no-partitioning.aspx

http://blogs.msdn.com/sqlqueryprocessing/archive/2006/12/11/index-build-strategy-in-sql-server-part-2-offline-parallel-no-partitioning.aspx

http://blogs.msdn.com/sqlqueryprocessing/archive/2006/12/13/index-build-strategy-in-sql-server-part-2-offline-parallel-no-partitioning-non-stats-plan-no-histogram.aspx


(세 번째-정렬된 파티션 인덱스)

http://blogs.msdn.com/sqlqueryprocessing/archive/2007/01/16/index-build-strategy-in-sql-server-part-3-offline-serial-parallel-partitioning.aspx

http://blogs.msdn.com/sqlqueryprocessing/archive/2007/01/19/index-build-strategy-in-sql-server-part-3-offline-serial-parallel-partitioning-aligned-partitioned-parallel-index-build.aspx


(네 번째-정렬되지 않은 파티션 인덱스)

http://blogs.msdn.com/sqlqueryprocessing/archive/2007/05/08/index-build-strategy-in-sql-server-part-4-1-offline-serial-parallel-partitioning-non-aligned-partitioned-parallel-index-build.aspx

http://blogs.msdn.com/sqlqueryprocessing/archive/2007/05/13/index-build-strategy-in-sql-server-part-4-2-offline-serial-parallel-partitioning-non-aligned-partitioned-index-build.aspx

 



 

역자 서문

이번엔, Query Processing Team 블로그에 올라온 포스트입니다. 역시 좀 지난 글이긴 하지만 내용이 너무 좋습니다. 에전에 한 번 제목만 보고 스~윽 지나쳤던 글인데, 다시 보니 다른 곳에서는 보기 힘든 유용한 내용들로 소개되고 있었습니다.

 

사실 저로서도 조금 힘들만큼 어려운 내용들을 설명하고 있습니다. 모호한 내용도 있는 것 같습니다. 직접적으로 확인하기 어려운 내용이기도 하구요. SQL Server 인덱스에 대해서 자세히 모른다면 이 내용을 읽고 이해하기가 쉽지는 않을 것입니다

 

그런데 왜 이 포스트 시리즈를 선택했냐구요?

SQL Server가 인덱스를 어떻게 만드는지? 병렬 처리를 어떻게 하는지? 얼만큼의 메모리와 디스크를 요구하는지? 파티션 테이블과 인덱스는 또 어떻게 처리되는지? 정렬된 파티션 인덱스와 그렇치 않은 경우에는 또 어떻게 달라지는지? 그래서 어떻게 작업하는 것이 상황 별로 가장 좋을지에 대한 중요한 힌트를 얻을 수는 내용들이 포함되어 있습니다.

 

그 결론만 참고하셔도 실무에서 아주 유용하게 활용될 수 있는 내용입니다.

 

앞에서 분류된 대로, 9개의 포스트를 주제별로 묶어서 4개의 글로 올리겠습니다.

 

시리즈-1. 소개

시리즈-2. 일반 인덱스

시리즈-3. 정렬된(Aligned) 파티션 인덱스

시리즈-4. 정렬되지(Non-aligned) 않은 파티션 인덱스

 

그럼, 우선 소개 부분부터 올립니다.

 

 

소개(I)

SQL Server의 인덱스 구성 전략은 사용자의 요구에 따라 다양하다. 그 전략에 따라 서로 다른 메모리와 디스크 공간을 요구한다. 이러한 전략들에 대해서 살펴볼 것이다.

 

우선 SQL Server 2005의 어떤 종류의 인덱스 구성 방식이 있는 보자.

 

 

온라인 인덱스 구성 vs. 오프라인 인덱스 구성

SQL Server 2005에서는 온라인으로 인덱스의 만들기, 재구성, 삭제 작업이 가능하다. ONLINE 옵션은 이러한 인덱스 작업을 수행하는 동안에도 사용자가 테이블이나 클러스터형(Clustered) 인덱스와 관련된 비클러스터형(Nonclustered) 인덱스에 접근하는 것이 허용한다. 오프라인으로 클러스터형 인덱스를 구성하거나 재구성하는 등의 DDL 작업을 하면 이는 해당 데이터와 관련 인덱스에 배타적 잠금을 보유하게 되고 이로 인해 다른 사용자가 데이터나 인덱스에 접근하지 못하도록 방해하게 된다.

 

Example:

Create index idx_t ON t(c1, c2)

WITH (ONLINE = ON)

 

 

직렬 인덱스 구성 vs. 병렬 인덱스 구성

다중 프로세서를 가진 컴퓨터에서 인덱스 구문 또한 다른 쿼리를 실행할 때처럼, 스캔, 정렬, 그리고 구성 작업을 수행하는데 병렬 처리가 가능하다. 병렬 처리 수는 최대 병렬 처리 수(sp_configure로 설정한), MAXDOP 인덱스 옵션, 현재 작업부하의 크기, 파티션되지 않은 경우, 첫 번째 키 칼럼의 데이터 분포등에 의해서 결정될 수 있다.

 

Example:

Create index idx_t ON t(c1, c2)

WITH (MAXDOP = 2)

-- 인덱스 구성에 2개의 프로세서 사용

 

 

사용자 데이터베이스 사용 vs. tempdb 사용

인덱스 구성/재구성 작업 시 일반적으로 중간 단계에서 생성되는 정렬 결과를 저장하기 위해서 해당 사용자 데이터베이스를 사용하거나 tempdb 데이터베이스를 사용할 수 있다. 후자의 경우 SORT_IN_TEMPDB 인덱스 옵션으로 지정할 수 있다. 디폴트로 OFF로 설정되면, 정렬 결과는 해당 파일 그룹이나 파티션 스킴(scheme)에 저장된다.

(역주: 뒤에서 논의하겠지만, 직렬/병렬, 파티션/일반 테이블 등등의 따라서 이 옵션의 사용으로 인한 영향력은 달라지게 된다. 따라서 관심 있는 살펴볼 필요가 있는 옵션이다)

 

Example:

Create clustered index idx_t ON t(c1)

WITH (SORT_IN_TEMPDB = ON)

 

 

 

소개(II)

 

파티션(Partitioned) 인덱스 구성 vs. 일반 인덱스(Nonpartitioned) 구성

파티션 테이블과 인덱스는 데이터를 하나 이상의 파일 그룹으로 분리시켜 저장한다. 데이터가 수평 분할이 되므로, 행 그룹 단위로 개별 파티션(분할)에 놓이게 된다. 해당 테이블과 인덱스 데이터에 대해 쿼리나 수정이 일어날 때는 하나의 테이블로서 다루어진다. 단일 인덱스와 테이블에 모든 파티션은 동일 데이터베이스에 존재해야 한다.

 

정렬된 파티션 인덱스 구성하기:

파티션 인덱스가 종속된 해당 테이블과는 별도로 구현될 수 있지만, 일반적으로는 파티션 테이블을 설계하고 나서 그 테이블에 인덱스를 생성하는 것이 적합하다. 이 경우 SQL Server는 인덱스를 구성할 때 자동적으로 해당 테이블과 동일한 파티션 스킴과 파티션 열을 사용해서 파티션 인덱스로 만들어준다. 그 결과 테이블과 동일한 방식의 파티션 되는 것이다. 이것이 테이블과 인덱스가 정렬(Aligned, 혹은 맞춤)되는 것이다.

 

인덱스가 반드시 해당 테이블과 동일한 이름의 파티션 함수를 사용할 필요는 없다. 그러나, 다음 항목들은 동일해야 한다

-       파티션 함수의 인수는 동일 데이터 형식

-       동일 파티션 수

-       동일한 파티션 경계 값

 

또한 파티션 테이블 혹은 클러스터형 인덱스 상에서 비클러스터형 인덱스를 구성하면 파티션 함수를 지정하지 않아도 정렬된 파티션으로 구성된다.

 

Example:

Create Partition Function pf (int)

as range right for values (NULL,  1,  100)

                   

Create Partition Scheme ps

as Partition pf

TO ([PRIMARY], [FileGroup1], [FileGroup1], [FileGroup1])

                   

Create table t (c1 int, c2 int)

on ps(c1)

                   

Create Index idx_t on t(c1)

 

 

일반 인덱스(파티션되지 않은) 구성:

인덱스 생성 시 다른 파티션 스킴이나 개별 파일 그룹을 지정하면 테이블에 정렬되지 않은 인덱스로 만들어진다. 일반 테이블에 파티션 클러스터형 인덱스를 만들어서 파티션 테이블로 바꿀 수도 있다. 물론 이 경우 정렬 인덱스를 아니다.

 

Example:

Create Partition Function pf (int)

as range right for values (NULL,  1,  100)

                   

Create Partition Scheme ps

as Partition pf

TO ([PRIMARY], [FileGroup1], [FileGroup1], [FileGroup1])

                   

Create table t (c1 int, c2 int)

                   

Create clustered Index idx_t on t(c1)

on ps(c1)

 

참고. 기존 파티션 클러스터형 인덱스를 삭제하는 경우, 새로운 Heap 테이블은 그대로 파티션 상태로 남게되는 것이며 동일 파티션 스킴이나 파일 그룹에 위치하는 것이다. 이 경우 원한다면 MOVE TO 옵션을 사용해서 그 위치를 조정할 수 있다.

 

Example:

Drop Index idx_t on t

WITH(MOVE TO new_ps(c1))

 

----------------------------------------------------

여기까지입니다.

소개 부분이라 특별한 이슈는 보이지 않으시죠? 준비 운동 정도로 생각하셔도 될 듯.


다음 주제는,

시리즈-2. 일반 인덱스

입니다. 기다려 주세요~~~

728x90

김정선의 좋은 글을 찾아서……
SQL Server 인덱스 구성 전략(시리즈-2. 일반인덱스)

 

 

사용자 삽입 이미지
김정선(jskim@feelanet.com)

필라넷 DB사업부 수석컨설턴트

SQLServer 아카데미/트라이콤 교육센터 강사

 

Microsoft SQL Server MVP

MCT/MCITP/MCDBA

 



Part 1: 오프라인, 직렬, 일반 인덱스(파티션 되지 않은)

 

Builder (write data to the in-build index)

                           |

                     Sort (order by index key)

                           |

                     Scan (read data from source)

 

 

b-tree 인덱스를 구성하기 위해서 우리는 먼저 원본 데이터를 정렬해야 한다. 작업 순서는 원본 데이터를 스캔하고, 정렬한 뒤(가능한 메모리에서*) 그리고 b-tree를 구성하는 것이다.

 

b-tree를 바로 만들지 않고 먼저 정렬을 하는가? 이론적으로는 정렬할 필요가 없다, 일반적인 DML를 사용해서 바로 인덱스 구성 작업에 데이터를 추가해도 된다, 그러나 이 경우 추가하는 데이터가 랜덤하다면 결국 b-tree 상의 적합한 리프 노드를 먼저 검색한 뒤에 입력하게 된다. b-tree 검색이 빠르긴 하지만, 최적은 아니다. 따라서 인덱스 구성 작업은 인덱스에 필요한 정렬을 사용해서 데이터를 먼저 정렬하고, 인덱스 구성 작업으로 넘겨주는 것이다, 이는 그저 b-tree상에 추가 작업만 요구된다.

 

정렬에서 인덱스 구성자 사이에 데이터를 넘기는 동안 각 익스텐트 별로 모든 행이 복사되는 즉시 해당 익스텐트를 해제한다. 이를 통해 인덱스 구성 시 이론적으로 필요한 작업 공간인 3 x 인덱스 크기(원본 + 정렬 테이블 + b-tree) 2.2 x Index Size(대략)로 줄여준다.

 

*메모리 상에서 정렬된다고 보장하지는 않는다. 메모리 정렬 여부는 가용 메모리와 실제 행 수에 따라 의존적이다. ‘메모리 상에서 정렬은 원론적으로 디스크 상에 정렬 테이블을 할당할 필요가 없으므로 빠르다. 그러나 반드시 필요한 것은 아니다. 물론 메모리 상 정렬 보다는 더 느리지만 디스크에서 데이터를 처리할 수 있다.

 

 

 

각 정렬 테이블(매우 작은 데이터일지라도)은 실행하는데 필요한 최소 40페이지(3,200KB)를 필요로 한다 (뒤에서 다루겠지만 병렬 처리의 경우 동시의 하나 이상의 정렬 테이블을 소비한다). 정렬 메모리를 계산할 때, 메모리 정렬에 필요한 충분한 메모리를 할당하려고 한다. 인덱스 생성 작업 시 최소 40페이지를 제공할 수 없을 정도로 메모리가 부족한 경우 작업은 실패할 수 있다.

 

인덱스 구성 마지막 단계는 항상 통계 정보 구성 작업이다. 적절한 통계 정보는 쿼리 최적화 프로그램(Optimizer)이 더 좋은 쿼리 계획을 산출하는데 도움을 준다, 사용자가 직접 ‘create’ ‘update’ 통계 명령을 이용해서 SQL Server가 특정 개체에 대한 통계 정보를 생성하거나 갱신하도록 강제할 수도 있다. 그러나 새로운 인덱스를 생성할 때, 모든 행을 처리하게 되므로 결국 이 때가 전체 데이터(100%)의 통계 정보를 구성할 수 있는 절호의 기회가 되는 것이다.

 

정리:

직렬 계획과 오프라인으로 일반 인덱스를 구성하기 위해서는 대략 2.2 x 인덱스 크기에 해당하는 디스크 공간과 쿼리 실행자가 절차를 시작하기 위해 필요한 최소 40페이지의 메모리를 요구한다.

 

 

Part 2: 오프라인, 병렬, 일반 인덱스(파티션 되지 않은)

병렬 인덱스 구성 방식은 필요한 통계 정보를 가진 히스토그램이 있는지 없는지에 따라서 달라진다. , 병렬 인덱스 계획은 크게 두 가지 범주를 가진다:

-       히스토그램이 가용

-       히스토그램이 가용하지 않음

 

히스토그램이 가용한 경우(병렬 정렬 및 구성 작업)

 

              X (Exchange)

   |          \            \

         Builder… Build…  Build… (write data to the in-build index)

                           |           |            |

                      Sort…      Sort…  Sort … (order by index key)

                           |          /            /

                       Scan (read data from source)

 

통계 정보가 가용한 경우 병렬 인덱스 구성이 가능하다(파티션 범위 정보를 사용할 수 있고 데이터의 분포를 결정하는데 사용할 수 있기 때문).

 

이 경우 어떻게 데이터를 스캔할까? 첫 번째 키 칼럼에 대한 통계 정보가 필요하다, 따라서 해당 통계 정보가 없으면 샘플링 기반의 통계 정보를 생성하고 이를 통해 병렬 여부와 병렬 처리 방법을 결정하는데 사용한다. 만일 인덱싱 뷰(통계 계획이 없는)와 같이 샘플링 통계 정보를 구성할 수 없는 경우엔, 다른 인덱스 구성 계획이 생성된다. 통계 정보와 히스토그램을 사용해서 데이터 분포를 결정할 수 있다, 이를 통해 병렬 계획에서 적절한 작업 부하 분산을 결정할 수 있으며, 또한 시스템 자원에 대한 고도의 활용률을 위해 병렬 처리 수(DOP, Degree Of Parallelism)를 결정하는데 도움을 주게 된다. 히스토그램으로부터 데이터 분포 상의 각 버킷(bucket)에 대한 행 수를 예상함으로써, N(N = DOP)개의 범위로 작업 부하를 분리하며, 각 작업자(worker)당 하나씩의 범위를 처리하게 된다.

 

데이터를 스캔하기 위해 범위 파티션 스캔을 사용하며, 각 작업자는 해당 범위에 데이터를 이용해서 자신만의 정렬 테이블를 구성하고 그 데이터에 기반한 b-tree를 만들게 된다. 각 작업자는 결국 분리된 개별 정렬 테이블과 b-tree를 가지는 것이다. 이후 인덱스 구성 작업의 마지막 단계에서 이들을 조정하는 담당 스레드(thread)에 의해서 모두 짜집기를 하게 되며, 마지막으로 완성된 b-tree에 대해 전체 데이터 통계정보 구성을 완료하게 된다.

 

히스토그램을 사용한 병렬 인덱스 구성은 최적의 성능을 제공한다. 반면에 이로 이한 문제는 앞서 다루었던 대로 더 많은 메모리를 소비한다는 것이며 만일 충분한 가용 메모리가 없는 경우 작업이 실패할 수도 있다(각 작업자당 개별 정렬 테이블을 만들게 되므로). 필요 시 MAXOP) 옵션을 사용해서 이를 적절히 조정할 수 있다.

 

For example:

Create index idx_t on t(c1, c2)

WITH (MAXDOP = 2)

 

 

 

히스토그램이 가용하지 않은 경우

 

Build (serial) (write data to the in-build index)

                          |

                X (Merge exchange)

                           /          |           \

                      Sort…      Sort…  Sort …(order by index key)

                           |           |            |

                       Scan…    Scan… Scan…(read data from source)   

 

히스토그램을 사용할 수 없다면(예를 들어 뷰에 인덱스를 만드는 경우) 이전 글에서 다루었던 방법을 사용할 수 없다, 따라서 데이터 분포와는 무관하게 일반적인 병렬 스캔을 사용한다.

 

동작 방식

원본 데이터는 병렬로 스캔한다. 그러나 b-tree 구성은 직렬 작업이다. 병렬 처리를 수행하는 각 작업자를 이전에 병렬 스캔 방법과 같이 동일한 방법으로 힙으로부터 특정 페이지를 스캔한다. 스캔 후는 각 작업자 별로 정렬 테이블을 가지고 데이터를 구성하며, 나중에 병합(Merge)을 통해서 데이터를 결합시키게 된다(이전과 같은 개별 b-tree 구조와 짜집기 방식은 사용하지 못한다). 최종 정렬 데이터가 만들어지면 이를 통해 직렬로 인덱스 구성 작업을 수행하게 된다. 왜 이 계획이 상대적으로 느린 걸까? 이는 직렬로 수행되는 인덱스 구성작업과 ‘Merge exchange’에 의해서 발생하는 추가 오버헤드 때문이다.

 

메모리 고려 사항

병렬 인덱스 구성은 동시에 여러 정렬 테이블을 구성하므로 기본 메모리 요구가 더 크며 계산식 또한 약간 달라진다. 메모리 계산식은 1) 필요 메모리, 2) 추가 메모리를 가진다. 필요 메모리를 각 정렬 당 40페이지를 요구했다. 그런데 예를 들어 DOP = 2라고 한다면, 2개의 정렬 테이블에 대해 총 80페이지가 필요 메모리가 되는 것이다. 그러나 추가 메모리는 DOP 설정과 무관하게 동일하다. 이는 전체 행 수가 DOP 설정과 무관하게 동일한 값이기 때문이다. 예를 들어 직렬 계획으로 추가 메모리 500페이지가 필요하다면 병렬 계획도 동일한 요구를 가지는 것이다. 각 작업자는 500/DOP 페이지 만큼의 추가 메모리에 + 40 페이지 필요 메모리를 가질 것이다.


다음 주제는,

시리즈-3. 정렬된(Aligned) 파티션(Partitioned) 인덱스

입니다. 또 기다려 주세요~~~

+ Recent posts