Hyun Soo, Lim

DW 시스템에서 대량 데이터 집계를 할 때 유용한 인덱스 유형이 추가되어 소개드립니다. 

이번 글에서는 BOL 있는 Columnstore Indexes 특징/제약조건에 대해서 간단히 알아보고, 대량 데이터 집계 작업에서 얼마나 성능
차이가 있는지 테스트를 진행해보도록 하겠습니다.
추가적으로 Columnstore Indexes에 대해서 궁금하신 분이 계실 듯 하여 관련 자료를 하단에 첨부해 놓았으니 참고 부탁드립니다.

[특징]
1. Columnar data format
  - 일반적으로 사용해왔던 rowstore format의 구조가 아니라 columnar data format을 구조를 가지는 인덱스입니다.
  - Columnar data format 구조란 하나의 컬럼에 대해서 묶어서 저장한다는 점입니다.
    (표현이 좀 모호한데, 아래의 그림 참고 부탁드립니다."Columnstore Indexes for Fast DW QP SQL Server 11" 문서에서 발취)

    

2. Faster query results (아래와 같은 이유로 빠르게 집계 및 연산이 되어짐)
  - 필요한 컬럼만 읽는다. (디스크 I/O와 메모리 사용량을 최소화할 수 있음)
  - 컬럼데이터에 대해서 압축이 된다. (인덱스 자체에 압축이 적용되어지기 때문에 페이지 압축을 사용불가함)
  - 개선된 쿼리 처리 알고리즘을 사용하여 CPU 사용률이 낮다.

3. Clustered index key
  - CL 인덱스가 있는 테이블의 경우 NC Columnstore Index 내에는 클러스터키 컬럼을 모두 가지고 있는다.

4. Partitioning
  - 파티션 테이블에서도 사용 가능

5. Record size
  - 인덱스 레코드 크기는 900 bytes를 초과할 수 없다.

[제약조건]
1. Columnstore index의 경우 CL로 만들 수 없다.
2. Unique index로 만들 수 없다.
3. View나 indexed view위에 만들 수 없다.
4. Primary key나 foreign key로써 사용할 수 없다.
5. Alert Index 구문으로 변경이 불가하다. Drop 후 re-create 로 처리해야 한다.
6. 인덱스 정렬을 위해서 asc, desc를 사용할 수 없다. (압축 알고리즘으로 인하여 정렬은 불가하다.)
7. 아래의 기능과 같이 사용 불가하다.
  - Page and row compression
  - Replication
  - Change tracking
  - Change data capture
  - Filestream

[테스트]
1. 소개
  - sys.sysobjects 테이블을 가지고 약 3500만건으로 뻥튀기한다.
  - 해당 테이블에 일반 인덱스와 컬럼스토어 인덱스를 만든다.
  - 각각의 인덱스를 사용하여 집계 쿼리를 수행하여 얼마나 시간이 소요되어지는지 확인한다.

2. 진행
2-1. 테스트 데이터 만들기
-- 테스트 데이터베이스 생성
create database testdb

use
testdb
go

-- 임시 테이블 생성
-- drop table ColumnstoreTable
select 
        identity(int, 1, 1) as seqNo
,       a.*
into ColumnstoreTable
from sys.sysobjects a
        cross join sys.sysobjects b
        cross join sys.sysobjects c
        cross join (select top 500 * from sys.sysobjects) d

Result> 소요시간 : 3 41
(35153041 행이 영향을 받음)

2-2. 인덱스 생성
--
create columnstore index idx_ColumnstoreTable_name on ColumnstoreTable (name) ;
go

--
create index idx_ColumnstoreTable_name_normal on ColumnstoreTable (name) ;
go

2-3. name 컬럼 기준으로 몇건의 데이터가 있는지 집계 쿼리 수행
--

select name, count(*)
from ColumnstoreTable with (readuncommitted)
group by name

-- 실행계획


-- 결과



--
select name, count(*)
from ColumnstoreTable with (readuncommitted, index(idx_ColumnstoreTable_name_normal))
group by name 

-- 실행계획


-- 결과


3. 느낀점
  - 집계 쿼리의 경우 일반인덱스와 비교자체가 불가할 정도로 빠릅니다. 위에 테스트 결과를 보더라도 거의 100배이상으로 빠른 것을 볼 수 있습니다.
    일반 인덱스에 비하여 빠른 이유는 압축으로 인한 효과가 크지 않을까 생각해봅니다. 어차피 넌클러스터 인덱스도 해당 컬럼 데이터만 가지고 있기 때문에
    해당 컬럼 데이터만 사용하기 때문이라는 이유로는 설명이 안됩니다. 중복데이터가 많은 경우 columnstore index의 효과가 더 높지 않을까합니다.
  - CTP3 기준으로 "columnstore index가 있는 테이블의 데이터 변경이 불가하다"는 제약사항이 있어 운영환경에서 사용시 많은 제약이 따릅니다.
  - 사이베이스에서 볼 수 있는 기능이였는데, 드디어 sql server에서도 볼 수 있게 되었네요.
  - 개인적으로 오라클에서의 비트맵인덱스와 비슷하다고 생각을 하는데, 비트맵인덱스의 경우 온라인 상태에서 데이터 변경 및 추가가 가능하니까,
    곧 columnstore index도 가능한 날이 오지 않을까 생각해봅니다. 
    (구조가 틀리기는 하지만 만들어지게 된 목적이나 컨셉은 비슷하다고 느껴집니다. 공감하기 힘들수도....)

[참고문서_columnstore Indexes]

[참고자료]
  - http://msdn.microsoft.com/en-us/library/gg509105(v=SQL.110).aspx
  - http://www.b-eye-network.com/blogs/mcknight/archives/2010/11/microsoft_unvei.php
  - http://cs-www.cs.yale.edu/homes/dna/talks/Column_Store_Tutorial_VLDB09.pdf
  - http://www.slideshare.net/abadid/columnstores-vs-rowstores-how-different-are-they-really
  - http://www.benjaminnevarez.com/tag/column-store-indexes/
  - http://sql-articles.com/articles/general/columnstore-indexsql-server-2011/
  - http://www.infoq.com/news/2011/03/Columnstore-index

SQL Server 2008까지는 바로 전에 복사한 내용만 붙여넣기가 되었는데, SQL Server 2011부터는 클립보드에 있는 내용 중
선택이 가능하도록 되었습니다. 아래의 명령어를 누르면 이전에 복사했던 내용이 나오는 것을 볼 수 있습니다.

화면으로 캡쳐할 수 있는 내용이 아니라서 팝업창 내용과 단축키만 내용 공유드립니다.

[메뉴 화면]


[단축키]
Ctrl + Shift + V

출처.
   - http://sqlblog.com/blogs/rob_farley/archive/2011/08/30/sometimes-it-s-the-little-things.aspx

SQL Server 2012에서 신규로 추가된 비교 함수를 소개드리고, 간단히 성능 테스트해 본 결과 공유드립니다.

1. IIF 함수
1-1. 소개
  - CASE 구문과 동일한 기능을 하며, true/false로 구분되어지는 구문을 CASE 구문에 비하여 단순한게 표현이 가능합니다.
  - 오라클의 DECODE함수와 동일하다고 보시면 됩니다.

1-2. 문법
  IIF ( boolean_expression, true_value, false_value )

1-3. 예제

  SELECT IIF ( 45 > 30, 1, 0 ) AS Result;

  -- 결과창

  

2. CHOOSE 함수
1-1. 소개
  - Index 값 위치의 변수값을 돌려주는 구문 (제한된 범위 내에서 선택이 필요한 경우 유용함)

1-2. 문법
  CHOOSE ( index, val_1, val_2 [, val_n ] )

1-3. 예제
  SELECT CHOOSE (1, 'Manager', 'Director', 'Developer', 'Tester' ) AS Result;
 
SELECT CHOOSE (2, 'Manager', 'Director', 'Developer', 'Tester' ) AS Result;
  SELECT CHOOSE (3, 'Manager', 'Director', 'Developer', 'Tester' ) AS Result;

  -- 결과창

  

3. IIF 함수 성능 테스트
3-1. 테스트 목적
  - CASE 구문을 사용하는 것이 IIF 함수를 사용하는 것보다 빠르지 않을까라는 의심을 하게 됨.
  - 느리다고 생각한 이유는 내장함수더라도 함수니까 CASE 구문에 비해서 가독성이 높아지더라도 성능저하가 있을 것으로 추정해 봄.

3-3. 테스트 데이터 생성
-- tempdb로 이동

use tempdb

 
-- 테스트에 사용할 500만건 데이터를 생성하기
-- drop table iiftest

select top 5000000

        identity(int, 1, 1) as seqNo

,       a.name

,       a.xtype

,       a.crDate

,       a.refDate

into iiftest

from sys.sysobjects a

        cross join sys.sysobjects b

        cross join sys.sysobjects c

        cross join (select top 10 * from sys.sysobjects) d

-- 결과창




3-3. 테스트 쿼리 수행 (CASE 구문 2개와 IIF 함수 구문 1개 수행하여 수행시간 비교)
-- 쿼리 수행 시간 비교
set statistics time on

-- IIF 함수

select

        sum(

               iif(name='sysfiles1', iif((seqNo%2)=1, iif((seqNO%5)=1, 1, 0), 0), 0))

from iiftest with (readuncommitted)

-- CASE 구문_01

select sum(

               case

                       when name = 'sysfiles1' and (seqNo%2)=1 and (seqNO%5)=1 then 1

                       else 0

               end)

from iiftest with (readuncommitted)

-- CASE 구문_02

select sum(

               case

                       when name = 'sysfiles1' then

                                      case

                                             when (seqNo%2)=then (case when (seqNO%5)=1 then 1 else 0 end)

                                             else 0

                                      end

                       else 0

               end)

from iiftest with (readuncommitted)


-- 결과값


-- 소요시간 (거의 결과 시간이 동일함을 볼 수 있슴)


-- 실행계획 (동일한 실행계획이 나오는 것을 볼 수 있음)


3-4. 의견
  - IIF 함수가 CASE 구문에 비해서 성능이 좋지 않을 것 같다는 생각은 보기좋게 빗나갔습니다.
  - 실행계획이나 테스트 내용에서 볼 수 있듯이 CASE 구문과 동일하게 동작합니다.
  - Depth 제한등도 CASE 구문과 동일한 것으로 보아, 가독성만 높인 것으로 보입니다.
  - 개인적으로 2 depth 이상으로 들어가는 경우 IIF 구문보다는 CASE 구문을 권장하고 싶습니다. DECODE 구문을 사용해 본 경험상,
    남용하게 되면 가독력이 CASE 구문에 비해서 떨어지고, 오류를 범할 확률도 높아진다고 생각하기 때문입니다 .(개인적으로!!!)

4. 참고자료
  - IIF 함수 도움말
  - CHOOSE 함수 도움말
  - IIF 함수 블로그 글 01
  - IIF 함수 블로그 글 02