Я не можу точно сказати, чому така поведінка трапляється, але я вважаю, що я розробив гарну модель поведінки за допомогою грубої перевірки. Наступні висновки застосовуються лише при завантаженні даних в один стовпчик і з цілими числами, які дуже добре розподілені.
Спочатку я спробував змінити кількість рядків, вставлених у ІТП, використовуючи TOP
. Я використовував ID % 16000
для всіх тестів. Нижче наведено графік, який порівнює рядки, вставлені до розміру сегмента стислої групи рядків:
Нижче наведено графік рядків, вставлених до часу процесора в мс. Зауважте, що вісь X має іншу вихідну точку:
Ми можемо бачити, що розмір сегмента групи рядків зростає лінійною швидкістю і використовує невелику кількість ЦП до приблизно 1 М рядків. У цей момент розмір групи рядів різко зменшується, а використання процесора різко збільшується. Здається, ми платимо високу ціну в процесорі за це стиснення.
Коли я вставляв менше 1024000 рядків, я опинився з відкритою групою рядків у ТПП. Однак примушування стиснення з використанням 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 розмір сегмента групи рядків лінійно збільшується на 80 байт за кожне додаткове 10 унікальних значень. Цікавий збіг, що BIGINT
традиційно займає 8 байт, а розмір сегмента збільшується на 8 байт за кожне додаткове унікальне значення. Минуле значення мода 1600 розмір сегмента швидко збільшується до його стабілізації.
Також корисно подивитися на дані, залишаючи значення модуля однаковим і змінюючи кількість вставлених рядків:
╔═══════════╦═════════╦═══════════════╦═════════════╗
║ 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 * кількість унікальних значень ми бачимо відносно погану компресію (2 байти в рядку для мод <= 65000) та низьке лінійне використання процесора. Коли вставлена кількість рядків> ~ 64 * кількість унікальних значень, ми бачимо набагато кращу компресію та більше, все ж лінійне використання процесора. Існує перехід між двома станами, який мені нелегко моделювати, але це можна побачити на графіку. Не видається, що ми бачимо максимальне використання процесора, вставляючи рівно 64 рядки для кожного унікального значення. Швидше за все, ми можемо вставити максимум 1048576 рядків у групу рядків, і ми бачимо набагато більше використання CPU та стиснення, коли є більше 64 рядків за унікальне значення.
Нижче наводиться контурний графік того, як змінюється час процесора в міру зміни кількості вставлених рядків та кількості унікальних рядків. Ми можемо побачити описані вище шаблони:
Нижче представлена контурна ділянка простору, що використовується сегментом. Після певного моменту ми починаємо набагато краще стискати, як описано вище:
Здається, що тут принаймні два різні алгоритми стиснення. З огляду на вищесказане, має сенс, що ми побачили максимальне використання процесора при вставці 1048576 рядків. Це також має сенс, що ми бачимо найбільше використання процесора в цей момент, вставляючи близько 16000 рядків. 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;
Ця вставка закінчується менше ніж за секунду і має слабке стиснення:
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 сканує таблицю інсценізації послідовно, обчислює номер рядка, а потім використовує розподіл круглих каналів для розміщення рядків на паралельні нитки. Це означає, що ми закінчимо ідеально збалансованими нитками.
Вставка займає близько 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
тому по одному скануванню таблиці робиться кожен паралельний потік:
На закінчення, при вставці рівномірно розподілених цілих чисел можна побачити дуже високу компресію, коли кожне унікальне ціле число з’являється більше 64 разів. Це може бути пов'язано з іншим алгоритмом стиснення, який використовується. Для досягнення цього стиснення може бути висока вартість процесора. Невеликі зміни в даних можуть призвести до кардинальних відмінностей у розмірі сегмента стислих груп рядків. Я підозрюю, що бачити найгірший випадок (з точки зору процесора) буде нечасто в дикій природі, принаймні для цього набору даних. Ще важче це помітити, роблячи паралельні вставки.