728x90

김정선의 좋은 글을 찾아서……
SQL Server 인덱스 구성 전략(시리즈-4. 정렬되지 않은 파티션 인덱스)

 

 

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

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

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

 

Microsoft SQL Server MVP

MCT/MCITP/MCDBA

 


Part 4: 오프라인, 직렬/병렬 파티셔닝(정렬되지 않은 파티션 인덱스 구성)

정렬되지 않은 경우는 힙과 인덱스가 서로 다른 파티션 스킴을 사용하거나, 힙이 파티션되지 않은 경우이다.

이번 글에서 원본 데이터가 파티션 된 경우와 그렇지 않은 2가지 경우의 정렬되지 않은 파티션 인덱스 구성에 대해 다룰 것이다.

 

원본이 파티션 되지 않은 경우

다음 쿼리를 보자.

 

Create Partition Function pf (int) as range right for values (1, 100, 1000)

Create Partition Scheme ps as Partition pf
ALL TO ([PRIMARY])

Create table t (c1 int, c2 int) 테이블은 디폴트로 PRIMARY 파일 그룹에 생성

Create clustered Index idx_t on t(c1) on ps(c1)  -정렬되지 않은 인덱스 구성

 

직렬 계획은 간단하다,

 Index Insert (write data to the in-build index)
   |
 Sort (order by index key)
   |
 Scan (read data from source)

 

Sort 반복연산자(iterator)는 각 파티션당 하나의 정렬 테이블을 만든다(예제에서는 4개의 파티션이 존재하므로 동시의 4개의 정렬 테이블이 만들 것이다). 디폴트로, 데이터 정렬을 위해 사용자 데이터베이스를 사용한다. 이전에 언급한대로, 정렬 데이터에서 모든 데이터를 복사하고 나면 각각의 익스텐트를 해제한다. 이 동작을 통해서 각 파티션 별로 필요한 디스크 공간을 3 x 파티션크기에서 2.2 x 파티션 크기로 줄일 수 있다. 따라서 각 파일 그룹은 2.2 x (해당 파일 그룹에 속한 전체 파티션 크기)만큼의 디스크 공간을 요구한다. SORT_IN_TEMPDB 옵션을 지정한다면, 모든 정렬 테이블은 tempdb에서 다루어질 것이고, 2.2 x (인덱스 전체 크기)만큼의 여유 공간을 tempdb에 요구할 것이다.

 

Index Insert 반복연산자는 sort 반복연산자가 정렬을 완료하고 나면 인덱스 구성을 시작한다. 파티션 수 만큼의 정렬 테이블이 필요하므로, 각 정렬 테이블 당 최소 40페이지가 필요함을 상기한다면, 최소 필요 메모리는 파티션 수 x 40페이지가 될 것이다.

 

병렬 계획이 된다면,

 

X (Exchange)
   |
 Index Insert
   |
 Sort
   |
 Scan

 

각 작업자 스레드가 병렬 처리 수와 해당 파티션 수만큼 계산되어 할당된다 (예를 들어, 4개의 파티션에 4개의 작업자 스레드이면 각 스레드 당 1개의 파티션). Sort 반복연산자를 할당된 각 파티션에 대해 하나의 정렬 테이블을 생성한다. 각 작업자는 원본 데이터를 한 번 스캔하고 그 파티션에 속한 행들을 처리한다, 처리된 행들은 소속된 파티션에 따라 해당 정렬 테이블에 입력된다.

 

모든 정렬 테이블이 완성되면, 인덱스 구성자가 정렬 테이블을 하나씩 처리하며, 각 파티션의 파일 그룹별로 b-tree를 구성하게 된다.

디스크 공간 및 메모리 필요 사항은 이전의 직렬 계획과 동일하다. 두 경우 모드, 모든 정렬 테이블이 완성되기 전까지 인덱스 구성을 시작할 수 없기 때문이다.

 

 

 

원본이 파티션된 경우

원본 테이블이 이미 파티션된 경우엔, 파티션 인덱스를 구성할 때 그 방법을 바꿀 수 있다.

예를 들어, 원본 테이블과 동일한 파티션 함수와 스킴을 사용하되, 새로운 인덱스는 다른 칼럼으로 파티션될 수 있다.

CREATE TABLE t (c1 int, c2 int) ON ps (c2)

……

CREATE CLUSTERED INDEX idx_t ON t(c1) ON ps(c1)

 

직렬 계획은 다음과 같다,

    Index Insert
       |
     Sort
       |
      NL (Nested Loop)
     /    \
 CTS   Scan


Constant Table Scan 연산자에 의해 파티션 ID를 하나씩 넘겨주면, NL 연산자에 의해서 해당 파티션 데이터를 스캔하고 결과를 Sort 반복연산자에 넘겨준다. 여기서부터 원본이 정렬되지 않은 시나리오와 동일하다. 메모리와 디스크 요구 사항 또한 같다.

 

병렬 계획의 경우는,

 

X (Distribute Streams)
       |
     Index Insert
       |
     Sort
       |
       X (Repartition Streams)
       |
      NL
     /   \
   X    Scan
  /
CTS

 

CTS위에 있는 연산자가 Gather Streams 연산자이다, 그 말은 생성자 하나의 다중 소비자를 가진다는 뜻이다 (역주: Gather Stream연산자에 다중 입력 처리를 통해 병렬 처리를 수행하고 이를 단일 출력으로 제공하는 연산자이다). Gather Streams Repartition Streams 연산자 사이에, 원본 파티션 수에 병렬 처리 수를 나눈 만큼의 작업자가 할당된다. 원본은 한 번만 스캔한다.

Repartition Streams 연산자는 쿼리 계획을 두 개의 병렬 처리 영역으로 분리한다. 최상위에 있는 Distribute Streams 연산자와 Repartition Streams 연산자 사이에서는 앞서 Repartition Streams 아래에서 만들어진 작업자 집합과는 또 다른 집합을 가진다. 각 작업자가 대상 파티션에 할당되는데 그 수는 대상 인덱스 파티션 수를 병렬 처리 수로 나눈 만큼 할당된다. 나머지 정렬 및 인덱스 구성 작업은 이전의 원본이 파티션되지 않은 경우의 병렬 처리와 동일하며, 메모리 및 디스크 공간 또한 동일하다.

 

 

여기까지입니다.

계속 살펴봐 주신 분들에게 감사드립니다.

더불어 원본에서 다음 문서가 또 올라오면 이어서 계속 소개해 드리도록 하겠습니다.


그럼, 일단 이 시리즈 문서는 접구요,

다음에 또 다른 재미있는 자료를 번역해서 공유하도록 하겠습니다.


행복한 하루 되세요~~~

728x90
 

김정선의 좋은 글을 찾아서……
Sorted Seeks 문제

 

 

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

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

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

 

Microsoft SQL Server MVP

MCT/MCITP/MCDBA

 

 

저자: Conor, Query Optimizer Team

원문: http://blogs.msdn.com/queryoptteam/archive/2006/04/12/575241.aspx


 

김정선의 의견

이번 글은 아주 흥미로운 내용입니다! 특히 쿼리 튜닝에 있어서요.

(RID 혹은 Key) Bookmark Lookup이나 Loop Join 등에서 발생하는 Random I/O에 대해 이해할 수 없는 부분들이 많이 있으시죠? 그 중에 하나가 아마도 논리적 읽기와 물리적 읽기에 대한 차별화되지 않은 처리일 것입니다. 우연히 보게 된 이 글에서 Query Optimizer팀이 어떤 고민을 하고 있는지, 어떻게 변화될 것인지? 그래서 어떻게 사용하는 것이 좋을지에 대한 추천할만한 이야기를 하고 있습니다. 솔직히 저는 약간 다른 의견입니다만, 어찌됐던 아주 좋은 글이라 번역을 해서 공유를 합니다.

 

좋은 글들이라 쉽지 않은 시간 내서 번역하고 공유하는데요, 읽으시는 분들이 별로 호응도 업고 의견도 없고, 댓 글로 없어서 의욕을 점점 상실하고 있습니다.  

(사실 대충 번역만 하고 글도 다듬지 않고 올립니다, 그러니 얼마나 불편하시겠어요! 저도 압니다, !)

그래도, 계속 열심히 할 수 있게 용기 좀 주세요 ^^

 

내용이 길지 않으니 편하게, 잠깐 읽어보시고 의견주세요.

그럼, ..하세요~~~

 

 

본문 시작

Optimizer 모델은 실행 계획 선택 시 여러 가지를 가정한다. 때론 그 가정이 특정 쿼리와 데이터 집합에 대해 잘못된 선택의 문제 원인이 되기도 하는데, 그 중 하나가 바로 Optimizer 팀에서 “The Sorted Seek Problem”라고 부르는 문제이다. 이는 Clustered Index Heap 그리고 Bookmark Lookup 쿼리 계획 선택에 영향을 미치는 내용이다.

 

Heap에 대한 Bookmark Lookup 연산이나 Index Index Seek 연산에 대한 비용 산정 시 이러한 작업을 일련의 랜덤 I/O로 취급한다. 랜덤 I/O는 디스크 암의 위치 이동과 특정 섹터 검색을 위한 회전 동작을 요구하므로 순차 I/O에 비해 훨씬 더 고 비용의 작업이다. 해당 페이지를 찾으면 다음 재사용을 위해 버퍼 풀(메모리)에 로드하고 캐싱 처리를 한다. 또한 그 계획의 비용을 산정하는 코드는 평균적으로 봤을 때 이후 연산에서 캐시 상의 페이지가 얼마자 자주 액세스되는지 이해하고 있으며, 더불어 그러한 메모리 기반 액세스가 이전의 디스크 액세스보다 훨씬 더 저렴한 비용으로 산정하고 있다.

 

쿼리에서 이러한 액세스 방법을 이용해 데이터를 조회하는 경우, 비용 산정 코드는 해당 데이터가 테이블 혹은 인덱스 페이지에 균등하게 분포되었다고 가정한다. 이는 각 행이 디스크로부터 로드될 것이라는 가정을 유발해서 결국 행 별 검색 작업이 매우 고 비용이라고 판단하게 만든다. (경우에 따라, 필요한 모든 페이지가 이미 버퍼 풀에 존재하므로 고 비용의 랜덤 I/O가 불 필요할 수 있음에도 불구하고)

 

실제로, 대개의 경우 데이터는 랜덤하게 분포되어 있지 않다. 사실 대부분의 많은 사용자 데이터가 물리적으로 군집된 정렬 상태로 입력된다. 예를 들어 특정 주문에 대한 주문 상세를 입력한다면 이는 주문이 생성된 시점에 순차적으로 이루어질 것이다. 즉 특정 페이지 혹은 소수의 페이지에 데이터를 군집된 형태로 저장되는 것이다. 그 결과, 비용 산정 모델이 예측하는 것 보다 훨씬 더 빠른 시간 안에 Bookmark Lookup 이나 Index Lookup을 수행할 수 있다. 결국 Query Optimizer는 실제보다 더 느린 계획을 선택할지도 모르는 것이다. 잘못된 예측으로 인해서.

 

데이터가 균등하게 분포된 시나리오도 또한 존재하므로, 위 가정을 변경하는 것 또한 옳지 않다. 향후에 이러한 문제들을 해결할 수 있는 기술이 제공될 것이다. 현재로선 이런 시나리오에 적합한 해결 방안은 인덱스를 사용하도록 쿼리 힌트 등으로 강제하는 것이다.

역주: ! 그럴 수는 없다. 다른 방법들을 먼저 고려해야 한다.

 

고객의 실제 시나리오를 보자. 테이블은 80개의 칼럼과 5.5백만 행을 가진다. 쿼리에서 검색 행수는 대략 21,000 행이다.

 

-- 1) Optimizer가 산출한 실행 계획: 병렬 Table Scan

SELECT * FROM CustTable  WHERE col='someconstvalue'

 

  |--Parallelism(Gather Streams)

       |--Table Scan(OBJECT:(CustTable), WHERE:(col=’someconstvalue’))

 

Table ‘CustTable’. Scan count 3, logical reads 416967, physical reads 22, read-ahead reads 413582, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

Completed in 3 minutes 9 seconds

 

 

-- 2) index 힌트로 강제한 경우

SELECT * FROM CustTable with (index=idxoncol) WHERE col='someconstvalue'

  |--Nested Loops(Inner Join, OUTER REFERENCES:([Bmk1000], [Expr1005]) WITH UNORDERED PREFETCH)

       |--Index Seek(OBJECT:(idxoncol), SEEK:(col=’someconstvalue’) ORDERED FORWARD)

       |--RID Lookup(OBJECT:(CustTable), SEEK:([Bmk1000]=[Bmk1000]) LOOKUP ORDERED FORWARD)

 

Table 'CustTable'. Scan count 1, logical reads 21167, physical reads 387, read-ahead reads 1354, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

Completed in 11 seconds

 

 

첫 번째 쿼리가 원본 쿼리이며 그 실행 계획은 해당 테이블을 병렬 스캔 하는 것으로 결정되었다. 조건은 비 검색인수(Non-SARG) 조건자로 WHERE절에서 처리된다. 처리 시간은 대략 3. 두 번째 실행 계획은 Bookmark Lookup을 수행하도록 인덱스 힌트로 강제한 결과이다. 대략 11초 걸렸다.

만일 한 번의 디스크 랜덤 액세스에 대략 10ms가 걸린다고 가정하면, 1초에 100번 이상은 수행할 수 없다. 단일 디스크를 대상으로 이러한 랜덤 I/O 21,000 번 일어난다면 아마도 3분 이상이 걸릴 것이다. 그런데 실제로 21,000 랜덤 I/O보다는 작게 발생할 것이다. 이는 위 결과에서 statistics I/O 출력 값을 보면 387 + 1354가 실제 물리적인 페이지라는 것을 알 수 있게 해준다.

 

위의 고객 데이터는 실제 물리적으로 군집을 이룬 상태였다. 따라서, 한 번 Bookmark Lookup을 통해 메모리(버퍼 풀)로 읽은 들인 페이지 내의 행들은 랜덤 I/O가 필요 없으며, 이를 통해 쿼리 성능에 큰 도움을 준다.

 

쿼리가 명확히 이런 사례에 해당한다면, 쿼리 힌트 사용을 권장한다.

 역주: 글쎄 안 된데두~ ^^

728x90

김정선의 좋은 글을 찾아서……
SQL Server 2005 SP2에서 변경된 Plan Caching 동작

 

 

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

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

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

 

Microsoft SQL Server MVP

MCT/MCITP/MCDBA   

 

 

 

저자: sangeethashekar, SQL Programmability & API Development Team 블로그

원문: http://blogs.msdn.com/sqlprogrammability/archive/2007/01/22/3-0-changes-in-caching-behavior-between-sql-server-2000-sql-server-2005-rtm-and-sql-server-2005-sp2.aspx

 

 

김정선의 의견

오랜 만의 좋은 글을 찾아서입니다 ^^

역시 전부터 계속 소개하고자 생각했던 글인데, 이제서야 올립니다.

 

SQL Server 2005 출시 이후, 대용량 메모리 사용이 늘어나면서 Plan Cache 용 메모리가 폭주하는 문제를 가지고 있었습니다. 그 원인이 Plan Cache를 할당하는 수식 상의 이슈가 있었는데요, SP2와 함께 변경되었다고 합니다.

 

대용량 메모리, 특히 64bit 장비를 운영하는 관리자 혹은 그러한 장비로의 업그레이드를 고려하고 있는 사용자라면 중요하게 알고 계셔야 할 내용일 듯 합니다.

 

참고로, 원문에 나와 있는 내용은 “Inside Microsoft SQL Server 2005 Query Tuning and Optimization”에 더 잘 나와 있습니다. 보다 폭 넒은 내용은 이 책을 참조해 보세요.

 

역시 내용이 길지 않으니 편하게 보시면 되겠습니다.

그리고 어려운 용어들이 몇 가지 있고 문맥도 힘들어서 번역에 오류가 있을 듯 합니다. 잘 아시는 분 계시면 정정해 주시면 매우 감사하겠습니다 ^^

 

그럼, ..하세요~~~

 

잠깐 질문. 혹시 이런 내용으로 세미나하면, 많이들 오실까요? ^^ (유로로...)


 

[본문 시작]

SQL Server 2000, 2005 RTM, 2005 SP2간의 Caching 동작 변경 사항

 

SQL Server 2000 2005 RTM간의 비용 산정 알고리즘의 변화

 

SQL Server 2000 2005 3가지 요소로 비용이 결정되는데, 디스크 IO 요청 수, 쿼리 컴파일 동안 Context Switch , 메모리 페이지 수(단일 또는 다중 페이지 할당자 모두). SQL Server 2005에서는 쿼리의 원본 비용이 2의 제곱에 해당하며, 디스크 IO, Context Switch, 메모리 페이지 비용에 대한 합을 그 지수로 한다.

 

수학적으로

Cost = 2 min (IO > 0 ? (IO - 1) / 2 + 1 : 0, 19) + min (CS > 1 ? (CS - 1) / 2 + 1 : 0, 8) + min (MP / 16, 4)

 

여기서

IO – IO 요청 수(바이트 수에 무관),

CS – Context Switch ,

MP – 메모리 페이지 수(단일 혹은 다중 페이지 할당자)

 

Context Switch 지수는 다음과 같다: a) 1미만의 context switch을 가진 쿼리는 지수가 0이다. 두 번이면 지수는1이다. 2를 초과하는 2의 배수는 지수가 1씩 증가하며 최대 8을 가진다. 디스크 I/O 지수는 디스크 IO가 0이면 똑같이 0이다. 역시 2의 배수로 지수가 1씩 증가하며 최대 19를 가진다.  메모리 페이지 지수는 16의 배수로 나누어지며 최대 4를 가진다. 예를 들어 Disk IO 0, context switch 4, 메모리 페이지 2이면, 해당 쿼리의 원본(초기) 비용은 2 (0 + 2 + 0)  = 4가 된다.

 

SQL Server 2005 비용은 Plan cache 메모리 페이지에 비례한 반면, SQ Server 2000은 메모리 페이지에 반 비례하게 계산된다. 따라서 대량 메모리 페이지를 사용하는 쿼리의 경우 SQL Server 2000에서는 캐시되지 않지만, SQL Server 2005에서는 캐시된다. 업그레이드의 경우 이러한 쿼리가 많은 작업 부하를 가진 시스템이라면 Plan cache 크기가 상당히 증가할 것이다.
(
역주. 무슨 말인지 이해 되시나요? 조금 애매하죠? 저두요…… ^^;)

 

SQL Server 2005 SP2에서 향상된 Plan Cache 동작

 

  1. Plan cache 항목(entry)의 생성 및 제거 시 충돌 감소
    SQL Server 2005 RTM SP1에서 동적 쿼리의 컴파일 계획에 대한 Descriptor를 위해 하나의 할당자가 있었다. Descriptor는 컴파일 계획의 일부로서 컴파일 계획을 plan cache에 추가할 때 생성되며 반대로 제거될 때 같이 삭제된다. 이 때 하나의 Descriptor에 대한 충돌로 인해서 응용 프로그램 처리량의 40%가 감소되거나 메모리 부족과 같은 심각한 문제를 유발하기도 했다. SP2에서 이러한 충돌을 줄이기 위해 각 CPU 단위로 분할해서 할당하며, 기존 문제를 해결하는데 도움을 준다.

  1. 모든 NUMA 모드에 걸쳐 공정하게 cache 항목 제거 수행
    NUMA(non-uniform memory access)로 구성된 서버에서 다중 NUMA 노드가 존재하고 각 노드 별로 캐시를 가진다. SQL Server cache 항목 제거(퇴거)에 의한 메모리 압박에 반응한다. SQL Server 2005 RTM SP1에서 작은 캐시를 가진 노드로 인해 여러 번의 캐시 제거 동작이 반복될 수 있었다. 예를 들어, 작은 메타데이터 캐시 항목은 주기적으로 재 생성되고 이로 인해 스키마 변경 잠금(Sch-m)에 충돌을 유발할 수 있다. 이러한 충돌로 전체 처리 성능이 떨어질 수 있다. SP2에서, 전체 NUMA 모드에 걸쳐 골고루 캐시 항목 제거 정책을 처리함으로써, 이러한 문제를 줄일 수 있다.
    (
    역주: Local 메모리 압박의 대한 NUMA 구성의 기존 문제점을 언급하고 있는 것 같습니다
    à 책에 잘 설명되어 있습니다.)

 

  1. SQL Server 2000에서와 유사한 Plan cache 크기 배정
    SQL Server 2005 RTM SP1에서 Plan Cache의 최대 크기가 2000 대비 상당히 커졌다. SQL Server 20052 SP에서 이를 조정했다. 다음 표를 참조.

 

SQL Server 버전

Cachestore에 대한 내부 메모리 압박 시점 표시

SQL Server 2005 RTM & SP1

75% of server memory from 0-8GB + 50% of server memory from 8Gb-64GB + 25%  of server memory > 64GB

SQL Server 2005 SP2   

 

75% of server memory from 0-4GB + 10% of server memory from 4Gb-64GB + 5% of server memory > 64GB

SQL Server 2000

SQL Server 2000 4GB upper cap on the plan cache

 

:

32GB를 가진 SQL Server의 경우, SQL Server 2005 RTM SP1에서는 75% X 8 + 50% X (32 - 8) = 18GB가 된다.

반면에 SQL Server 2005 SP2 75% X 4 + 10% X (32-4) = 5.8GB로 줄어든다.

 

참고: Plan cache에 대한 또 다른 내부 메모리 압박은 32비트에서 4만개 이상의 항목, 그리고 64비트에서 16만개 이상일 경우이다.
(
역주: 이 부분도 책을 참조하는 것이 좋을 듯 합니다……소개가 조금 다른 듯 합니다)

           

  1. 일부 0 비용 계획을 캐시하지 않도록 구성하여, 캐시 항목을 감소시킨다
    SQL Server 2005 RTM, SP1, SP2
    에서 컴파일 계획의 비용은 컴파일 계획의 크기와 컴파일하는  IO CPU에 비례한다. 컴파일 계획은 특정 임계 값 이하는 비용 0으로 감안한다. SQL Server 2005 RTMSP1에서 하나의 SET 문 혹은 하나의 트랜잭션 문(begin/commit/save/rollback transaction )을 포함한 동적 배치에 대한 비용 0의 컴파일 계획이 캐시되었다. SP2에서 이러한 계획은 더 이상 캐시되지 않는다.

예외로는,
a)
배치 내 모든 문이 set (옵션) 문인 경우, SP2는 비용 0라도 캐시

b) 배치 내 모든 문이 IF 문과(혹은) 트랜잭션 문의 경우, SP2는 캐시

 

역주)위 내용 a, b 또한 이해가 잘 안되는……누가 설명 좀 해 주세요 ^^;

 

SP2의 이러한 변화로 비용 0이며 거의 재사용이 어려운 동적 배치에 대한 캐시 소비를 줄일 수 있다.

 

결론, SP2 꼭 설치하라는 얘기네요 ^^

+ Recent posts