간단한 CCI 행 그룹을 만드는 데 최대 30 초가 걸리는 이유는 무엇입니까?


20

인서트 중 일부가 예상보다 오래 걸리는 것을 발견했을 때 CCI와 관련된 데모를 진행하고있었습니다. 재현 할 테이블 정의 :

DROP TABLE IF EXISTS dbo.STG_1048576;
CREATE TABLE dbo.STG_1048576 (ID BIGINT NOT NULL);
INSERT INTO dbo.STG_1048576
SELECT TOP (1048576) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) RN
FROM master..spt_values t1
CROSS JOIN master..spt_values t2;

DROP TABLE IF EXISTS dbo.CCI_BIGINT;
CREATE TABLE dbo.CCI_BIGINT (ID BIGINT NOT NULL, INDEX CCI CLUSTERED COLUMNSTORE);

테스트를 위해 준비 테이블에서 1048576 행을 모두 삽입하고 있습니다. 어떤 이유로 압축되지 않은 한 정확히 하나의 압축 행 그룹을 채우기에 충분합니다.

모든 정수 mod 17000을 삽입하면 1 초도 걸리지 않습니다.

TRUNCATE TABLE dbo.CCI_BIGINT;

INSERT INTO dbo.CCI_BIGINT WITH (TABLOCK)
SELECT ID % 17000
FROM dbo.STG_1048576
OPTION (MAXDOP 1);

SQL Server 실행 시간 : CPU 시간 = 359ms, 경과 시간 = 364ms

그러나 동일한 정수 mod 16000을 삽입하면 때로는 30 초 이상이 걸립니다.

TRUNCATE TABLE dbo.CCI_BIGINT;

INSERT INTO dbo.CCI_BIGINT WITH (TABLOCK)
SELECT ID % 16000
FROM dbo.STG_1048576
OPTION (MAXDOP 1);

SQL Server 실행 시간 : CPU 시간 = 32062ms, 경과 시간 = 32511ms

이것은 여러 시스템에서 수행 된 반복 가능한 테스트입니다. 모드 값이 변경됨에 따라 경과 시간에 명확한 패턴이있는 것 같습니다.

MOD_NUM TIME_IN_MS
1000    2036
2000    3857
3000    5463
4000    6930
5000    8414
6000    10270
7000    12350
8000    13936
9000    17470
10000   19946
11000   21373
12000   24950
13000   28677
14000   31030
15000   34040
16000   37000
17000   563
18000   583
19000   576
20000   584

테스트를 직접 실행하려면 여기에 작성한 테스트 코드를 자유롭게 수정 하십시오 .

mod 16000 인서트의 sys.dm_os_wait_stats에서 흥미로운 것을 찾을 수 없었습니다.

╔════════════════════════════════════╦══════════════╗
             wait_type               diff_wait_ms 
╠════════════════════════════════════╬══════════════╣
 XE_DISPATCHER_WAIT                        164406 
 QDS_PERSIST_TASK_MAIN_LOOP_SLEEP          120002 
 LAZYWRITER_SLEEP                           97718 
 LOGMGR_QUEUE                               97298 
 DIRTY_PAGE_POLL                            97254 
 HADR_FILESTREAM_IOMGR_IOCOMPLETION         97111 
 SQLTRACE_INCREMENTAL_FLUSH_SLEEP           96008 
 REQUEST_FOR_DEADLOCK_SEARCH                95001 
 XE_TIMER_EVENT                             94689 
 SLEEP_TASK                                 48308 
 BROKER_TO_FLUSH                            48264 
 CHECKPOINT_QUEUE                           35589 
 SOS_SCHEDULER_YIELD                           13 
╚════════════════════════════════════╩══════════════╝

인서트가 인서트 ID % 16000보다 왜 더 오래 걸리 ID % 17000나요?

답변:


12

여러 측면에서 이것은 예상되는 동작입니다. 모든 압축 루틴 세트는 입력 데이터 분배에 따라 광범위한 성능을 갖습니다. 스토리지 크기 및 런타임 쿼리 성능을 위해 데이터 로딩 속도를 바꿀 것으로 예상됩니다.

VertiPaq은 독점적 인 구현이고 세부 사항은 밀접하게 보호되는 비밀이기 때문에 여기에 답을 얻는 방법에 대한 명확한 제한이 있습니다. 그럼에도 불구하고, VertiPaq에는 다음에 대한 루틴이 포함되어 있습니다.

  • 값 인코딩 (작은 비트 수에 맞게 값을 스케일링 및 / 또는 변환)
  • 사전 인코딩 (고유 값에 대한 정수 참조)
  • 실행 길이 인코딩 (반복 된 값의 실행을 [값, 개수] 쌍으로 저장)
  • 비트 패킹 (가능한 한 적은 비트로 스트림 저장)

일반적으로 데이터는 값 또는 사전 인코딩 된 다음 RLE 또는 비트 패킹이 적용됩니다 (또는 세그먼트 데이터의 다른 하위 섹션에서 사용되는 RLE 및 비트 패킹의 하이브리드). 적용 할 기술을 결정하는 프로세스에는 최대 비트 절약을 달성 할 수있는 방법을 결정하는 데 도움이되는 히스토그램 생성이 포함될 수 있습니다.

Windows Performance Recorder로 느린 사례를 캡처하고 Windows Performance Analyzer로 결과를 분석하면 데이터 클러스터링, 히스토그램 작성 및 최상의 파티션 분할 방법을 결정하는 데 실행 시간의 대부분이 소요됨을 알 수 있습니다. 저금:

WPA 분석

세그먼트에서 최소 64 번 나타나는 값에 대해 가장 비싼 처리가 발생합니다. 이것은 순수한 RLE가 언제 유익 할 것인지를 결정하는 휴리스틱 입니다. 빠른 경우가 발생할 불순 큰 최종 스토리지 크기, 비트 패킹 표현 예 스토리지. 하이브리드의 경우 반복 횟수가 64 개 이상인 값은 RLE로 인코딩되고 나머지는 비트 패킹됩니다.

가장 긴 지속 시간은 64 개의 반복이있는 고유 값의 최대 수가 최대 세그먼트에 표시 될 때 발생합니다 (예 : 각각 64 개의 항목이있는 16,384 개의 값 세트가있는 1,048,576 행). 코드를 검사하면 고가의 처리를 위해 하드 코딩 된 시간 제한이 드러납니다. 이것은 SSAS와 같은 다른 VertiPaq 구현에서 구성 할 수 있지만 SQL Server에서는 불가능합니다.

문서화되지 않은 DBCC CSINDEX명령을 사용하여 최종 스토리지 배열에 대한 통찰력을 얻을 수 있습니다 . 여기에는 RLE 헤더 및 배열 항목, RLE 데이터에 대한 책갈피 및 비트 팩 데이터 (있는 경우)에 대한 간략한 요약이 표시됩니다.

자세한 내용은 다음을 참조하십시오.


9

이 동작이 왜 발생하는지 정확하게 말할 수는 없지만 무차별 강제 테스트를 통해 올바른 동작 모델을 개발했다고 생각합니다. 다음 결론은 단일 열에 데이터를로드 할 때와 잘 분산 된 정수로만 적용됩니다.

먼저을 사용하여 CCI에 삽입되는 행 수를 변경하려고했습니다 TOP. ID % 16000모든 테스트에 사용 했습니다. 아래는 압축 된 행 그룹 세그먼트 크기에 삽입 된 행을 비교 한 그래프입니다.

상단 대 크기의 그래프

아래는 ms 단위로 CPU 시간에 삽입 된 행의 그래프입니다. X 축의 시작점이 다릅니다.

상단 vs CPU

행 그룹 세그먼트 크기가 선형 속도로 증가하고 약 1M 행까지 소량의 CPU를 사용함을 알 수 있습니다. 이 시점에서 행 그룹 크기가 크게 줄어들고 CPU 사용량이 크게 증가합니다. 해당 압축에 대해 CPU에서 많은 비용을 지불하는 것으로 보입니다.

1024000 미만의 행을 삽입하면 CCI에서 열린 행 그룹이 생겼습니다. 그러나 사용하여 압축을 강제 REORGANIZE또는 REBUILD크기에 영향을주지 않았다. 옆으로, 변수를 사용할 때 TOP열린 행 그룹으로 끝나지 만 RECOMPILE닫힌 행 그룹으로 끝나는 것이 흥미 롭습니다.

다음으로 행 수를 동일하게 유지하면서 계수 값을 변경하여 테스트했습니다. 다음은 102400 개의 행을 삽입 할 때의 데이터 샘플입니다.

╔═══════════╦═════════╦═══════════════╦═════════════╗
 TOP_VALUE  MOD_NUM  SIZE_IN_BYTES  CPU_TIME_MS 
╠═══════════╬═════════╬═══════════════╬═════════════╣
    102400     1580          13504          352 
    102400     1590          13584          316 
    102400     1600          13664          317 
    102400     1601          19624          270 
    102400     1602          25568          283 
    102400     1603          31520          286 
    102400     1604          37464          288 
    102400     1605          43408          273 
    102400     1606          49360          269 
    102400     1607          55304          265 
    102400     1608          61256          262 
    102400     1609          67200          255 
    102400     1610          73144          265 
    102400     1620         132616          132 
    102400     1621         138568          100 
    102400     1622         144512           91 
    102400     1623         150464           75 
    102400     1624         156408           60 
    102400     1625         162352           47 
    102400     1626         164712           41 
╚═══════════╩═════════╩═══════════════╩═════════════╝

1600의 mod 값이 될 때까지 행 그룹 세그먼트 크기는 추가 10 개의 고유 한 값마다 80 바이트 씩 선형으로 증가합니다. BIGINT전통적으로 8 바이트를 차지하고 각 추가 고유 값에 대해 세그먼트 크기가 8 바이트 씩 증가 한다는 것은 우연의 일치입니다 . 1600의 mod 값을 지나면 세그먼트 크기는 안정화 될 때까지 빠르게 증가합니다.

모듈러스 값을 동일하게두고 삽입 된 행 수를 변경할 때 데이터를 보는 것도 도움이됩니다.

╔═══════════╦═════════╦═══════════════╦═════════════╗
 TOP_VALUE  MOD_NUM  SIZE_IN_BYTES  CPU_TIME_MS 
╠═══════════╬═════════╬═══════════════╬═════════════╣
    300000     5000         600656          131 
    305000     5000         610664          124 
    310000     5000         620672          127 
    315000     5000         630680          132 
    320000     5000          40688         2344 
    325000     5000          40696         2577 
    330000     5000          40704         2589 
    335000     5000          40712         2673 
    340000     5000          40728         2715 
    345000     5000          40736         2744 
    350000     5000          40744         2157 
╚═══════════╩═════════╩═══════════════╩═════════════╝

삽입 된 행 수 <~ 64 * 고유 값의 수는 상대적으로 압축률이 낮고 (mod <= 65000의 경우 행 당 2 바이트) 선형 CPU 사용량이 적습니다. 삽입 된 행 수가> 64 * 고유 값의 수인 경우 훨씬 더 나은 압축과 선형 CPU 사용을 볼 수 있습니다. 모델링하기 쉽지 않은 두 상태 사이에 전환이 있지만 그래프에서 볼 수 있습니다. 각각의 고유 한 값에 대해 정확히 64 개의 행을 삽입 할 때 최대 CPU 사용량을 볼 수는 없습니다. 오히려 최대 1048576 개의 행만 행 그룹에 삽입 할 수 있으며 고유 한 값당 64 개가 넘는 행이 있으면 CPU 사용률과 압축률이 훨씬 높아집니다.

아래는 삽입 된 행 수와 고유 한 행 수가 변경 될 때 CPU 시간이 어떻게 변하는 지에 대한 등고선도입니다. 위에서 설명한 패턴을 볼 수 있습니다.

컨투어 CPU

아래는 세그먼트가 사용하는 공간의 등고선도입니다. 특정 시점이 지나면 위에서 설명한 것처럼 훨씬 더 나은 압축이 시작됩니다.

윤곽 크기

여기에는 적어도 두 개의 다른 압축 알고리즘이 작동하는 것 같습니다. 위의 내용을 감안할 때 1048576 행을 삽입 할 때 최대 CPU 사용량을 볼 수 있습니다. 약 16000 개의 행을 삽입 할 때 해당 시점에서 가장 많은 CPU 사용량을 볼 수 있습니다. 1048576/64 = 16384.

누군가가 데이터 를 분석하려는 경우를 위해 모든 원시 데이터를 여기 에 업로드 했습니다.

병렬 계획으로 어떤 일이 발생하는지 언급 할 가치가 있습니다. 나는 고르게 분포 된 값 으로이 동작을 관찰했습니다. 병렬 인서트를 할 때 종종 임의의 요소가 있으며 스레드는 일반적으로 불균형합니다.

준비 테이블에 2097152 개의 행을 넣습니다.

DROP TABLE IF EXISTS STG_2097152;
CREATE TABLE dbo.STG_2097152 (ID BIGINT NOT NULL);
INSERT INTO dbo.STG_2097152 WITH (TABLOCK)
SELECT TOP (2097152) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) RN
FROM master..spt_values t1
CROSS JOIN master..spt_values t2;

이 인서트는 1 초 이내에 마무리되며 압축률이 낮습니다.

DROP TABLE IF EXISTS dbo.CCI_BIGINT;
CREATE TABLE dbo.CCI_BIGINT (ID BIGINT NOT NULL, INDEX CCI CLUSTERED COLUMNSTORE);

INSERT INTO dbo.CCI_BIGINT WITH (TABLOCK)
SELECT ID % 16000
FROM dbo.STG_2097152 
OPTION (MAXDOP 2);

불균형 스레드의 효과를 볼 수 있습니다.

╔════════════╦════════════╦══════════════╦═══════════════╗
 state_desc  total_rows  deleted_rows  size_in_bytes 
╠════════════╬════════════╬══════════════╬═══════════════╣
 OPEN             13540             0         311296 
 COMPRESSED     1048576             0        2095872 
 COMPRESSED     1035036             0        2070784 
╚════════════╩════════════╩══════════════╩═══════════════╝

스레드의 균형을 맞추고 동일한 행 분포를 갖기 위해 수행 할 수있는 다양한 트릭이 있습니다. 다음 중 하나입니다.

DROP TABLE IF EXISTS dbo.CCI_BIGINT;
CREATE TABLE dbo.CCI_BIGINT (ID BIGINT NOT NULL, INDEX CCI CLUSTERED COLUMNSTORE);

INSERT INTO dbo.CCI_BIGINT WITH (TABLOCK)
SELECT FLOOR(0.5 * ROW_NUMBER() OVER (ORDER BY (SELECT NULL)))  % 15999
FROM dbo.STG_2097152
OPTION (MAXDOP 2)

여기서 계수에 홀수를 선택하는 것이 중요합니다. SQL Server는 준비 테이블을 직렬로 검색하고 행 번호를 계산 한 다음 라운드 로빈 배포를 사용하여 행을 병렬 스레드에 넣습니다. 그것은 우리가 완벽하게 균형 잡힌 스레드로 끝날 것임을 의미합니다.

균형 1

인서트는 약 40 초가 걸리며 직렬 인서트와 유사합니다. 우리는 잘 압축 된 행 그룹을 얻습니다.

╔════════════╦════════════╦══════════════╦═══════════════╗
 state_desc  total_rows  deleted_rows  size_in_bytes 
╠════════════╬════════════╬══════════════╬═══════════════╣
 COMPRESSED     1048576             0         128568 
 COMPRESSED     1048576             0         128568 
╚════════════╩════════════╩══════════════╩═══════════════╝

원래 준비 테이블에서 데이터를 삽입하여 동일한 결과를 얻을 수 있습니다.

DROP TABLE IF EXISTS dbo.CCI_BIGINT;
CREATE TABLE dbo.CCI_BIGINT (ID BIGINT NOT NULL, INDEX CCI CLUSTERED COLUMNSTORE);

INSERT INTO dbo.CCI_BIGINT WITH (TABLOCK)
SELECT t.ID % 16000 ID
FROM  (
    SELECT TOP (2) ID 
    FROM (SELECT 1 ID UNION ALL SELECT 2 ) r
) s
CROSS JOIN dbo.STG_1048576 t
OPTION (MAXDOP 2, NO_PERFORMANCE_SPOOL);

여기서 라운드 로빈 분배는 파생 테이블에 사용 s되므로 각 병렬 스레드 에서 테이블 을 한 번 스캔합니다.

균형 잡힌 2

결론적으로, 균일하게 분포 된 정수를 삽입 할 때 각각의 고유 한 정수가 64 번 이상 나타날 때 매우 높은 압축률을 볼 수 있습니다. 다른 압축 알고리즘이 사용 되었기 때문일 수 있습니다. 이 압축을 달성하려면 CPU 비용이 많이들 수 있습니다. 데이터가 조금만 변경되면 압축 된 행 그룹 세그먼트의 크기가 크게 달라질 수 있습니다. 적어도이 데이터 세트에서 최악의 경우 (CPU 관점에서)를 보는 것이 드문 일이라고 생각합니다. 병렬 인서트를 수행 할 때는보기가 더 어렵습니다.


8

나는 이것이 단일 열 테이블에 대한 압축의 내부 최적화 및 사전이 차지하는 64KB의 마법 번호와 관련이 있다고 생각합니다.

예 : MOD 16600으로 실행하는 경우 행 그룹 크기의 최종 결과는 1.683MB 이고 MOD 17000 을 실행 하면 2.001 MB 크기의 행 그룹이 제공됩니다 .

이제 생성 된 사전을 살펴보십시오 ( CISL 라이브러리 를 사용할 수 있으려면 cstore_GetDictionaries 함수가 필요하거나 sys.column_store_dictionaries DMV로 이동하여 쿼리하십시오).

(MOD 16600) 61KB

여기에 이미지 설명을 입력하십시오

(MOD 17000) 65KB

여기에 이미지 설명을 입력하십시오

재미있는 점은 테이블에 다른 열을 추가하고 REALID라고 부르는 것입니다.

DROP TABLE IF EXISTS dbo.CCI_BIGINT;
CREATE TABLE dbo.CCI_BIGINT (ID BIGINT NOT NULL, REALID BIGINT NOT NULL, INDEX CCI CLUSTERED COLUMNSTORE);

MOD 16600에 대한 데이터를 다시로드하십시오.

TRUNCATE TABLE dbo.CCI_BIGINT;

INSERT INTO dbo.CCI_BIGINT WITH (TABLOCK)
SELECT ID % 16600, ID
FROM dbo.STG_1048576
OPTION (MAXDOP 1);

이번에는 최적화 프로그램이 과도하게 지나치게 압축하지 않기로 결정하기 때문에 실행 속도가 빠릅니다.

select column_id, segment_id, cast(sum(seg.on_disk_size) / 1024. / 1024 as Decimal(8,3) ) as SizeInMB
    from sys.column_store_segments seg
        inner join sys.partitions part
            on seg.hobt_id = part.hobt_id 
    where object_id = object_id('dbo.CCI_BIGINT')
    group by column_id, segment_id;

행 그룹 크기간에 약간의 차이가 있더라도 무시할 수 있습니다 (2.000 (MOD 16600) 대 2.001 (MOD 17000)).

이 시나리오의 경우 MOD 16000의 사전이 1 열 (0.63 대 0.61)의 첫 번째 시나리오보다 큽니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.