Tại sao có thể mất tới 30 giây để tạo một nhóm hàng CCI đơn giản?


20

Tôi đang thực hiện một bản demo liên quan đến CCI khi tôi nhận thấy rằng một số phần chèn của tôi mất nhiều thời gian hơn dự kiến. Bảng định nghĩa để sao chép:

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);

Đối với các bài kiểm tra tôi đang chèn tất cả 1048576 hàng từ bảng phân tầng. Điều đó đủ để điền chính xác một nhóm hàng được nén miễn là vì lý do nào đó nó không bị cắt bớt.

Nếu tôi chèn tất cả các số nguyên mod 17000 thì chỉ mất chưa đến một giây:

TRUNCATE TABLE dbo.CCI_BIGINT;

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

Thời gian thực thi máy chủ SQL: Thời gian CPU = 359 ms, thời gian trôi qua = 364 ms.

Tuy nhiên, nếu tôi chèn cùng một số nguyên mod 16000 thì đôi khi mất hơn 30 giây:

TRUNCATE TABLE dbo.CCI_BIGINT;

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

Thời gian thực thi máy chủ SQL: Thời gian CPU = 32062 ms, thời gian trôi qua = 32511 ms.

Đây là một thử nghiệm lặp lại đã được thực hiện trên nhiều máy. Dường như có một mô hình rõ ràng trong thời gian trôi qua khi giá trị mod thay đổi:

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

Nếu bạn muốn chạy thử nghiệm, hãy tự sửa đổi mã kiểm tra mà tôi đã viết ở đây .

Tôi không thể tìm thấy bất cứ điều gì thú vị trong sys.dm_os_wait_stats cho bản chèn 16000 mod:

╔════════════════════════════════════╦══════════════╗
             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 
╚════════════════════════════════════╩══════════════╝

Tại sao chèn để ID % 16000mất nhiều thời gian hơn chèn cho ID % 17000?

Câu trả lời:


12

Trong nhiều khía cạnh, đây là hành vi dự kiến. Bất kỳ tập hợp các thói quen nén sẽ có hiệu suất rộng rãi tùy thuộc vào phân phối dữ liệu đầu vào. Chúng tôi hy vọng sẽ trao đổi tốc độ tải dữ liệu cho kích thước lưu trữ và hiệu năng truy vấn thời gian chạy.

Có một giới hạn nhất định đối với mức độ chi tiết của câu trả lời bạn sẽ nhận được ở đây, vì VertiPaq là một triển khai độc quyền và các chi tiết là một bí mật được bảo vệ chặt chẽ. Mặc dù vậy, chúng tôi biết rằng VertiPaq chứa các thói quen cho:

  • Mã hóa giá trị (chia tỷ lệ và / hoặc dịch các giá trị để phù hợp với một số lượng nhỏ bit)
  • Mã hóa từ điển (tham chiếu số nguyên đến các giá trị duy nhất)
  • Chạy mã hóa chiều dài (lưu trữ các giá trị lặp lại dưới dạng cặp [value, Count])
  • Đóng gói bit (lưu trữ luồng càng ít bit càng tốt)

Thông thường, dữ liệu sẽ được mã hóa theo giá trị hoặc từ điển, sau đó RLE hoặc đóng gói bit sẽ được áp dụng (hoặc kết hợp giữa RLE và đóng gói bit được sử dụng trên các phần phụ khác nhau của dữ liệu phân đoạn). Quá trình quyết định áp dụng các kỹ thuật nào có thể liên quan đến việc tạo biểu đồ để giúp xác định mức tiết kiệm bit tối đa có thể đạt được.

Nắm bắt trường hợp chậm với Windows Performance Recorder và phân tích kết quả với Windows Performance Analyzer, chúng ta có thể thấy rằng phần lớn thời gian thực hiện được sử dụng để xem xét phân cụm dữ liệu, xây dựng biểu đồ và quyết định cách phân vùng tốt nhất tiết kiệm:

Phân tích WPA

Việc xử lý tốn kém nhất xảy ra đối với các giá trị xuất hiện ít nhất 64 lần trong phân khúc. Đây là một heuristic để xác định khi nào RLE thuần có khả năng mang lại lợi ích. Các trường hợp nhanh hơn dẫn đến lưu trữ không tinh khiết, ví dụ như một đại diện được đóng gói bit, với kích thước lưu trữ cuối cùng lớn hơn. Trong các trường hợp lai, các giá trị có 64 lần lặp lại trở lên được mã hóa RLE và phần còn lại được đóng gói bit.

Thời lượng dài nhất xảy ra khi số lượng giá trị riêng biệt tối đa với 64 lần lặp lại xuất hiện trong phân đoạn lớn nhất có thể, ví dụ: 1.048.576 hàng với 16.384 bộ giá trị với mỗi mục nhập 64. Kiểm tra mã cho thấy một giới hạn thời gian mã hóa cứng cho việc xử lý đắt tiền. Điều này có thể được định cấu hình trong các triển khai VertiPaq khác, ví dụ SSAS, nhưng không phải trong SQL Server như tôi có thể nói.

Một số cái nhìn sâu sắc về sự sắp xếp lưu trữ cuối cùng có thể có được bằng cách sử dụng lệnh không có giấy tờDBCC CSINDEX . Điều này cho thấy các mục tiêu và mảng của RLE, bất kỳ dấu trang nào vào dữ liệu RLE và tóm tắt ngắn gọn về dữ liệu gói bit (nếu có).

Để biết thêm thông tin, xem:


9

Tôi không thể nói chính xác lý do tại sao hành vi này xảy ra nhưng tôi tin rằng tôi đã phát triển một mô hình tốt về hành vi thông qua thử nghiệm vũ lực. Các kết luận sau chỉ áp dụng khi tải dữ liệu vào một cột duy nhất và với các số nguyên được phân phối rất tốt.

Đầu tiên tôi đã thử thay đổi số lượng hàng được chèn vào CCI bằng cách sử dụng TOP. Tôi đã sử dụng ID % 16000cho tất cả các bài kiểm tra. Dưới đây là biểu đồ so sánh các hàng được chèn vào kích thước phân đoạn nhóm được nén:

đồ thị của đỉnh so với kích thước

Dưới đây là biểu đồ các hàng được chèn vào thời gian CPU tính bằng ms. Lưu ý rằng trục X có điểm bắt đầu khác nhau:

đầu so với cpu

Chúng ta có thể thấy rằng kích thước phân khúc nhóm hàng tăng trưởng với tốc độ tuyến tính và sử dụng một lượng nhỏ CPU cho đến khoảng 1 M hàng. Tại thời điểm đó, kích thước nhóm hàng giảm đáng kể và việc sử dụng CPU tăng đáng kể. Có vẻ như chúng ta phải trả giá đắt cho CPU cho lần nén đó.

Khi chèn ít hơn 1024000 hàng, tôi đã kết thúc với một nhóm hàng mở trong CCI. Tuy nhiên, buộc nén bằng cách sử dụng REORGANIZEhoặc REBUILDkhông có ảnh hưởng đến kích thước. Ở một bên, tôi thấy thú vị rằng khi tôi sử dụng một biến cho TOPtôi đã kết thúc với một nhóm hàng mở nhưng cuối cùng RECOMPILEtôi đã có một nhóm hàng đóng.

Tiếp theo tôi đã kiểm tra bằng cách thay đổi giá trị mô đun trong khi vẫn giữ số lượng hàng giống nhau. Dưới đây là mẫu dữ liệu khi chèn 102400 hàng:

╔═══════════╦═════════╦═══════════════╦═════════════╗
 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 
╚═══════════╩═════════╩═══════════════╩═════════════╝

Cho đến khi giá trị mod là 1600, kích thước phân đoạn nhóm hàng tăng tuyến tính thêm 80 byte cho mỗi 10 giá trị duy nhất bổ sung. Một sự trùng hợp thú vị là BIGINTtheo truyền thống chiếm 8 byte và kích thước phân đoạn tăng thêm 8 byte cho mỗi giá trị duy nhất bổ sung. Quá giá trị mod là 1600, kích thước phân khúc tăng nhanh cho đến khi ổn định.

Cũng rất hữu ích khi xem dữ liệu khi giữ nguyên giá trị mô đun và thay đổi số lượng hàng được chèn:

╔═══════════╦═════════╦═══════════════╦═════════════╗
 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 
╚═══════════╩═════════╩═══════════════╩═════════════╝

Có vẻ như khi số lượng hàng được chèn <~ 64 * số lượng giá trị duy nhất chúng ta thấy nén tương đối kém (2 byte mỗi hàng cho mod <= 65000) và mức sử dụng CPU tuyến tính thấp. Khi số lượng hàng được chèn> ~ 64 * số lượng giá trị duy nhất chúng ta thấy nén tốt hơn nhiều và sử dụng CPU tuyến tính cao hơn. Có một sự chuyển đổi giữa hai trạng thái không dễ dàng để tôi lập mô hình nhưng nó có thể được nhìn thấy trong biểu đồ. Có vẻ như không đúng khi chúng ta thấy mức sử dụng CPU tối đa khi chèn chính xác 64 hàng cho mỗi giá trị duy nhất. Thay vào đó, chúng tôi chỉ có thể chèn tối đa 1048576 hàng vào một nhóm hàng và chúng tôi thấy mức độ sử dụng và nén CPU cao hơn nhiều khi có hơn 64 hàng cho mỗi giá trị duy nhất.

Dưới đây là một biểu đồ đường viền về cách thời gian cpu thay đổi khi số lượng hàng được chèn và số lượng hàng duy nhất thay đổi. Chúng ta có thể thấy các mô hình được mô tả ở trên:

cpu đường viền

Dưới đây là một đường viền của không gian được sử dụng bởi phân khúc. Sau một thời điểm nhất định, chúng ta bắt đầu thấy nén tốt hơn nhiều, như được mô tả ở trên:

kích thước đường viền

Có vẻ như có ít nhất hai thuật toán nén khác nhau đang hoạt động ở đây. Với những điều trên, có nghĩa là chúng ta sẽ thấy mức sử dụng CPU tối đa khi chèn 1048576 hàng. Cũng có nghĩa là chúng ta thấy việc sử dụng CPU nhiều nhất tại thời điểm đó khi chèn khoảng 16000 hàng. 1048576/64 = 16384.

Tôi đã tải lên tất cả dữ liệu thô của mình ở đây trong trường hợp ai đó muốn phân tích nó.

Điều đáng nói là những gì xảy ra với các kế hoạch song song. Tôi chỉ quan sát hành vi này với các giá trị phân bố đồng đều. Khi thực hiện chèn song song thường có yếu tố ngẫu nhiên và các luồng thường không cân bằng.

Đặt 2097152 hàng trong bảng phân tầng:

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;

Chèn này kết thúc trong chưa đầy một giây và có độ nén kém:

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);

Chúng ta có thể thấy tác dụng của các luồng không cân bằng:

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

Có nhiều thủ thuật khác nhau mà chúng ta có thể làm để buộc các luồng được cân bằng và có cùng phân phối các hàng. Đây là một trong số chúng:

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)

Chọn một số lẻ cho mô đun là quan trọng ở đây. SQL Server quét bảng phân đoạn theo thứ tự, tính toán số hàng, sau đó sử dụng phân phối robin tròn để đặt các hàng trên các luồng song song. Điều đó có nghĩa là chúng ta sẽ kết thúc với các chủ đề cân bằng hoàn hảo.

số dư 1

Việc chèn mất khoảng 40 giây tương tự như việc chèn nối tiếp. Chúng tôi nhận được các nhóm hàng được nén độc đáo:

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

Chúng tôi có thể nhận được kết quả tương tự bằng cách chèn dữ liệu từ bảng phân tầng ban đầu:

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);

Ở đây phân phối robin tròn được sử dụng cho bảng dẫn xuất sđể một lần quét bảng được thực hiện trên mỗi luồng song song:

cân bằng 2

Tóm lại, khi chèn các số nguyên phân bố đồng đều, bạn có thể thấy độ nén rất cao khi mỗi số nguyên duy nhất xuất hiện hơn 64 lần. Điều này có thể là do một thuật toán nén khác nhau đang được sử dụng. Có thể có một chi phí cao trong CPU để đạt được nén này. Những thay đổi nhỏ trong dữ liệu có thể dẫn đến sự khác biệt lớn về kích thước của phân khúc nhóm được nén. Tôi nghi ngờ rằng việc nhìn thấy trường hợp xấu nhất (từ góc độ CPU) sẽ không phổ biến, ít nhất là đối với tập dữ liệu này. Nó thậm chí còn khó nhìn hơn khi thực hiện chèn song song.


8

Tôi tin rằng, điều này có liên quan đến việc tối ưu hóa nội bộ của nén cho các bảng cột đơn và số ma thuật của 64 KB chiếm từ điển.

Ví dụ: nếu bạn chạy với MOD 16600 , kết quả cuối cùng của kích thước Nhóm Hàng sẽ là 1.683 MB , trong khi chạy MOD 17000 sẽ cung cấp cho bạn Nhóm Hàng với kích thước 2.001 MB .

Bây giờ, hãy xem các từ điển được tạo (bạn có thể sử dụng thư viện CISL của tôi cho điều đó, bạn sẽ cần hàm cstore_GetDictionaries hoặc truy cập sys.column_store_dictionaries DMV):

(MOD 16600) 61 KB

nhập mô tả hình ảnh ở đây

(MOD 17000) 65 KB

nhập mô tả hình ảnh ở đây

Điều thú vị là, nếu bạn sẽ thêm một cột khác vào bảng của mình và hãy gọi nó là THỰC SỰ:

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

Tải lại dữ liệu cho 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);

Lần này việc thực thi sẽ nhanh, vì trình tối ưu hóa sẽ quyết định không làm việc quá sức và nén nó quá xa:

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;

Mặc dù sẽ có một sự khác biệt nhỏ giữa các kích thước Nhóm Hàng, nhưng nó sẽ không đáng kể (2.000 (MOD 16600) so với 2.001 (MOD 17000))

Đối với kịch bản này, từ điển cho MOD 16000 sẽ lớn hơn so với kịch bản đầu tiên có 1 cột (0,63 so với 0,61).

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.