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 % 16000
cho 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:
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:
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 REORGANIZE
hoặc REBUILD
khô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 TOP
tôi đã kết thúc với một nhóm hàng mở nhưng cuối cùng RECOMPILE
tô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à BIGINT
theo 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:
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:
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.
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:
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.