_대문 | 방명록 | 최근글 | 홈피소개 | 주인놈 | RSS
내용포함 대/소문자구분안함
_
Database System
Data Warehouse
Data Analysis
Master Data Management
Data Governance
Data Quality

Hive
SQL Server
Oracle Server
DBMSs
Performance Tuning

Open Source
Operating System
Enterprise Architecture
Software Engineering
Programming
Process
Working Smart
Trade-Off

Link
News
Philosophy
Blog
Booklog
Tools
Misc
UserPreferences
_
_
Edit SideBar

Contents

1 쿼리플랜비교1:Join
2 쿼리플랜비교2:Star Join
3 쿼리플랜비교3:Partitioning
4 병렬처리 강제화
5 파티션 압축
6 Distribute, Repartition, Gather Stream
7 결론
8 참고자료


이 문서의 내용은 SQL Server Magazine 2010년 7월호에 개재된 내용 및 필자가 풀어 쓴 것이다. 각각의 2005(sp3), 2008(sp1) 버전의 SQL Server가 설치된 테스트 환경이 필요하다. (이 문서는 2008R2에서 테스트함.) 다음의 코드를 2005, 2008 각 버전의 인스턴스에서 실행시킨다.

1 쿼리플랜비교1:Join #

테스트 준비
-- Sample datatabase
SET NOCOUNT ON;
IF DB_ID('testparallel') IS NULL CREATE DATABASE testparallel;
GO

USE testparallel;
GO
-- Helper function GetNums
-- returns a sequence of integers of a requested size
IF OBJECT_ID('dbo.GetNums', 'IF') IS NOT NULL
DROP FUNCTION dbo.GetNums;
GO

CREATE FUNCTION dbo.GetNums(@n AS BIGINT) RETURNS TABLE
AS
RETURN
WITH
L0 AS(SELECT 1 AS c UNION ALL SELECT 1),
L1 AS(SELECT 1 AS c FROM L0 AS A CROSS JOIN L0 AS B),
L2 AS(SELECT 1 AS c FROM L1 AS A CROSS JOIN L1 AS B),
L3 AS(SELECT 1 AS c FROM L2 AS A CROSS JOIN L2 AS B),
L4 AS(SELECT 1 AS c FROM L3 AS A CROSS JOIN L3 AS B),
L5 AS(SELECT 1 AS c FROM L4 AS A CROSS JOIN L4 AS B),
Nums AS(SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS n FROM L5)
SELECT TOP (@n) n FROM Nums ORDER BY n;
GO

USE testparallel;
IF OBJECT_ID('dbo.T1') IS NOT NULL DROP TABLE dbo.T1;
IF OBJECT_ID('dbo.T2') IS NOT NULL DROP TABLE dbo.T2;
GO

CREATE TABLE dbo.T1
(
col1 INT NOT NULL,
col2 INT NOT NULL,
filler BINARY(100) NOT NULL DEFAULT(0x)
);

CREATE UNIQUE CLUSTERED INDEX idx_cl_col1 ON dbo.T1(col1);

INSERT INTO dbo.T1 WITH(TABLOCK) (col1, col2)
SELECT n AS col1, n AS col2
FROM dbo.GetNums(1000000);

SELECT * INTO dbo.T2 FROM dbo.T1;
CREATE UNIQUE CLUSTERED INDEX idx_cl_col1 ON dbo.T2(col1);
GO

다음의 SQL을 2005, 2008 각 인스턴스에서 실행시켜보자.
SELECT *
FROM dbo.T1
	JOIN T2 
		ON T1.col1 = T2.col1
WHERE T1.col2 <= 100;

2005 버전
p01.jpg

2008 버전
p02.jpg

2005버전의 경우는 Thread 4를 제외하고는 작업을 하지 않을 테지만, 2008의 경우는 첫 번째 작업을 끝내고 난 결과를 다시 여러 Thread에 분배함으로써 다음 작업을 수월하게 해준다.

2 쿼리플랜비교2:Star Join #

테스트 준비
USE testparallel;
IF OBJECT_ID('dbo.Fact', 'U') IS NOT NULL DROP TABLE dbo.Fact;
IF OBJECT_ID('dbo.Dim1', 'U') IS NOT NULL DROP TABLE dbo.Dim1;
IF OBJECT_ID('dbo.Dim2', 'U') IS NOT NULL DROP TABLE dbo.Dim2;
IF OBJECT_ID('dbo.Dim3', 'U') IS NOT NULL DROP TABLE dbo.Dim3;
GO

CREATE TABLE dbo.Dim1
(
key1 INT NOT NULL CONSTRAINT PK_Dim1 PRIMARY KEY,
attr1 INT NOT NULL,
filler BINARY(100) NOT NULL DEFAULT (0x)
);

CREATE TABLE dbo.Dim2
(
key2 INT NOT NULL CONSTRAINT PK_Dim2 PRIMARY KEY,
attr1 INT NOT NULL,
filler BINARY(100) NOT NULL DEFAULT (0x)
);

CREATE TABLE dbo.Dim3
(
key3 INT NOT NULL CONSTRAINT PK_Dim3 PRIMARY KEY,
attr1 INT NOT NULL,
filler BINARY(100) NOT NULL DEFAULT (0x)
);

CREATE TABLE dbo.Fact
(
key1 INT NOT NULL CONSTRAINT FK_Fact_Dim1 FOREIGN KEY
REFERENCES dbo.Dim1,key2 INT NOT NULL CONSTRAINT FK_Fact_Dim2 FOREIGN KEY
REFERENCES dbo.Dim2,
key3 INT NOT NULL CONSTRAINT FK_Fact_Dim3 FOREIGN KEY REFERENCES dbo.Dim3,
measure1 INT NOT NULL,
measure2 INT NOT NULL,
measure3 INT NOT NULL,
filler BINARY(100) NOT NULL DEFAULT (0x),
CONSTRAINT PK_Fact PRIMARY KEY(key1, key2, key3)
);

INSERT INTO dbo.Dim1(key1, attr1)
SELECT n, ABS(CHECKSUM(NEWID())) % 20 + 1
FROM dbo.GetNums(100);

INSERT INTO dbo.Dim2(key2, attr1)
SELECT n, ABS(CHECKSUM(NEWID())) % 10 + 1
FROM dbo.GetNums(50);

INSERT INTO dbo.Dim3(key3, attr1)
SELECT n, ABS(CHECKSUM(NEWID())) % 40 + 1
FROM dbo.GetNums(200);

INSERT INTO dbo.Fact WITH (TABLOCK)
(key1, key2, key3, measure1, measure2, measure3)
SELECT N1.n, N2.n, N3.n,
ABS(CHECKSUM(NEWID())) % 1000000 + 1,
ABS(CHECKSUM(NEWID())) % 1000000 + 1,
ABS(CHECKSUM(NEWID())) % 1000000 + 1
FROM dbo.GetNums(100) AS N1
CROSS JOIN dbo.GetNums(50) AS N2
CROSS JOIN dbo.GetNums(200) AS N3;
GO

다음의 SQL을 2005, 2008 각 인스턴스에서 실행시켜보자. (Star Join 참고)
SELECT *
FROM dbo.Fact AS F
	JOIN dbo.Dim2 AS D2
		ON F.key2 = D2.key2
	JOIN dbo.Dim3 AS D3
		ON F.key3 = D3.key3
WHERE D2.attr1 <= 3
AND D3.attr1 <= 2;

2005 버전
p03.jpg

2008 버전
p04.jpg

조인 횟수를 확 줄여주는 것이 포인트다.

3 쿼리플랜비교3:Partitioning #

기본
partition_2005_2008.png

테스트 준비

USE testparallel;

IF OBJECT_ID('dbo.PartitionedTable', 'U') IS NOT NULL
DROP TABLE dbo.PartitionedTable;

IF EXISTS(SELECT * FROM sys.partition_schemes WHERE name = 'PS1')
DROP PARTITION SCHEME PS1;

IF EXISTS(SELECT * FROM sys.partition_functions WHERE name = 'PF1')
DROP PARTITION FUNCTION PF1;
GO

CREATE PARTITION FUNCTION PF1 (INT)
AS RANGE LEFT FOR VALUES (250000, 500000, 750000);

CREATE PARTITION SCHEME PS1
AS PARTITION PF1 ALL TO ([PRIMARY]);
CREATE TABLE dbo.PartitionedTable

(
col1 INT NOT NULL,
col2 INT NOT NULL,
filler BINARY(100) DEFAULT(0x)
) ON PS1(col1);

CREATE UNIQUE CLUSTERED INDEX idx_col1 ON dbo.PartitionedTable(col1) ON PS1(col1);

INSERT INTO dbo.PartitionedTable WITH (TABLOCK) (col1, col2)
SELECT n, ABS(CHECKSUM(NEWID())) % 1000000 + 1
FROM dbo.GetNums(1000000);

다음의 SQL문을 2005, 2008의 각각의 인스턴스에서 실행시켜본다.
SELECT *
FROM dbo.PartitionedTable
WHERE col1 <= 500000
ORDER BY col2;

2005 버전
p05.jpg

2008 버전
p06.jpg


실제실행계획을 xml로 떠서 보면 몇개의 thread가 사용되었으며, 각각의 thread가 몇개의 row를 처리했는지 볼 수 있다. Ctrl+L을 눌러서 나오는 예상실행계획에서는 볼 수 없다. 아래의 그림처럼 <RunTimeInformation> ... </RunTimeInformation> 태그부분을 보면 된다. 표시된 부분을 보면 총 5개의 thread가 사용되었으며, 각각의 thread는 대략 4만5천 row씩을 처리한 것을 볼 수 있다.
xml_plan.jpg

4 병렬처리 강제화 #

이 방법은 문서화되지 않은 dbcc 명령을 통해 구현할 수 있다. cpu와 io에 대한 가중치를 옵티마이저에게 알려줄 수 있다. 최종적인 쿼리 비용이 설정된 값(기본값 = 5)보다 크면 병렬처리하므로 cpu에 대한 가중치로 병렬처리를 유도할 수 있다. 먼저 설정값을 본다.
DBCC TRACEON (3604); -- Show DBCC output
DBCC SHOWWEIGHTS; -- Show the settings

/* 결과
DBCC Opt Weights CPU: 1.000000  IO: 1.000000 SPID 81
*/

병렬처리 하지 않는 쿼리의 실행 계획을 보자.
select count(*) from master.dbo.spt_values
p01.png

위 그림을 보면 알 수 있겠지만 병렬처리 하지 않는다. cpu에 가중치를 주어 쿼리 비용을 5가 넘어가도록 조정해 보자.
DBCC FREEPROCCACHE
DBCC SETCPUWEIGHT(10000); 
go

select count(*) from master.dbo.spt_values
option (recompile, querytraceon 8649) -- 옵션은 꼭 필요하다.
go

DBCC SETCPUWEIGHT(1); -- 원래대로 해놓자.
DBCC SETIOWEIGHT(1); --io에도 가중치를 줄 수 있다.
go
p02.png

필자의 경우는 cpu에 10000 정도를 주었더니 쿼리 비용이 5를 넘어가 병렬처리를 하는 것을 볼 수 있었다. 그렇다면 비용이 5가 넘어가게 io도 가중치를 주면 병렬처리를 하지 않을까하는 생각을 해봤다. sql server 2008 r2에서 DBCC SETIOWEIGHT(10000); 과 같이 io에 가중치를 주어봤으나 병렬처리 실행 계획이 만들어지지 않았다. 이때 cpu 가중치는 1이었다. 하지만 DBCC SETCPUWEIGHT(10); 과 같이 가중치를 주니 병렬처리 실행계획을 세우더라. io와 cpu 가중치를 적절히 주어 실행계획을 유도시키면 강제로 병렬처리 할 수 있음을 알 수 있다.

DBCC FREEPROCCACHE
DBCC SETCPUWEIGHT(10); 
DBCC SETIOWEIGHT(10000); 
go

select count(*) from master.dbo.spt_values
option (recompile, querytraceon 8649)
go

DBCC SETCPUWEIGHT(1); -- Default CPU weight
DBCC SETIOWEIGHT(1);
go

뭐...TRACEON 했던거 TRACEOFF 하자.
DBCC TRACEOFF (3604);

DBCC 명령을 계속 사용할 수 없으니, play_guide(계획지침)를 이용하면 되것다. 또는 다음과 같이 병렬 실행 계획을 xml로 떠서 실행하면 되것다.
select count(*) from master.dbo.spt_values
option (use plan N'<?xml version="1.0" encoding="utf-16"?>
...
...
...
--소스가 길어서 아래의 source_01.txt로 첨부하였다.
source_01.txt (새창에서 열림)

p03.png

특이한 점은 예상하위트리 비용이 5를 넘지 않았다는 것이다. 어찌되었건 sql server는 힌트로 준 실행계획을 그대로 사용한 것이다. 뭐.. 가끔씩 쓸만할라나?

5 파티션 압축 #

set nocount on
set statistics io off
declare
	@bdt char(8)
,	@edt char(8)
,   @p int
,   @sql varchar(4000)

set @bdt = '20110928'

while (@bdt <= '20300301')
begin
	set @edt = convert(char(8), dateadd(dd, 1, @bdt), 112)
    set @p = $partition.파티션함수(@bdt)

    set @sql = '
    alter table 테이블명
    rebuild partition = ' + convert(varchar, @p) + '
    with (data_compression = page)'
    exec (@sql)

    print @sql
	--print @bdt + ', ' + @edt
	set @bdt = @edt
end

6 Distribute, Repartition, Gather Stream #

sql server 옵티마이저는 serial vs parallel 비용을 비교해서 둘 중에 작은 것을 선택한다고 한다.
parallel_mathod_2.png

7 결론 #

2005보다 병렬처리가 좋아졌다. 알고리즘(처리방식)이 좋아진거지 실제 환경에서 좋아졌다고는 볼 수 없다. 그림을 보면 알겠지만 논리적 읽기 수가 2008 버전이 더 많은 부분이 있다. 이제까지 I/O Base의 성능튜닝 기준이 바뀌었다고 볼 수도 있는 것이다. 병렬처리는 하드웨어 자원을 최대한 많이 쓰고 최대한 효율적으로 처리하기 위한 처리방법이므로 I/O에 너무 집착하지는 말아야 할 것이다. CPU, RAM, Disk 어느 한 곳이라도 병목현상이 발생하면 시스템은 전체적으로 느려진다는 진리는 여전히 유효하다.


댓글 남기기..
이름: : 오른쪽의 새로고침을 클릭해 주세요. 새로고침
EditText : Print : FindPage : DeletePage : LikePages : Powered by MoniWiki : Last modified 2012-02-28 14:37:16

친구를 얻는 비결은 친구와 하나가 되는 것이다. (앨버트 허버드)