Non posso dire esattamente perché si stia verificando questo comportamento, ma credo di aver sviluppato un buon modello di comportamento tramite test della forza bruta. Le seguenti conclusioni si applicano solo quando si caricano dati in una singola colonna e con numeri interi molto ben distribuiti.
Innanzitutto ho provato a variare il numero di righe inserite nel CCI usando TOP
. Ho usato ID % 16000
per tutti i test. Di seguito è riportato un grafico che confronta le righe inserite con la dimensione del segmento del gruppo di righe compresso:
Di seguito è riportato un grafico delle righe inserite nel tempo della CPU in ms. Si noti che l'asse X ha un punto di partenza diverso:
Possiamo vedere che la dimensione del segmento del gruppo di righe aumenta a una velocità lineare e utilizza una piccola quantità di CPU fino a circa 1 milione di righe. A quel punto la dimensione del gruppo di righe diminuisce drasticamente e l'utilizzo della CPU aumenta notevolmente. Sembrerebbe che paghiamo un prezzo pesante in CPU per quella compressione.
Quando ho inserito meno di 1024000 righe ho finito con un rowgroup aperto nel CCI. Tuttavia, forzare la compressione utilizzando REORGANIZE
o REBUILD
non ha influito sulla dimensione. Per inciso, ho trovato interessante che quando ho usato una variabile per TOP
ho finito con un rowgroup aperto ma con RECOMPILE
ho finito con un rowgroup chiuso.
Successivamente ho testato variando il valore del modulo mantenendo lo stesso numero di righe. Ecco un esempio dei dati quando si inseriscono 102400 righe:
╔═══════════╦═════════╦═══════════════╦═════════════╗
║ 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 ║
╚═══════════╩═════════╩═══════════════╩═════════════╝
Fino a un valore mod di 1600 la dimensione del segmento rowgroup aumenta linearmente di 80 byte per ogni 10 valori univoci aggiuntivi. È una coincidenza interessante che BIGINT
tradizionalmente occupa 8 byte e la dimensione del segmento aumenta di 8 byte per ogni valore univoco aggiuntivo. Passato un valore mod di 1600, la dimensione del segmento aumenta rapidamente fino a quando non si stabilizza.
È anche utile esaminare i dati quando si lascia lo stesso valore del modulo e si modifica il numero di righe inserite:
╔═══════════╦═════════╦═══════════════╦═════════════╗
║ 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 ║
╚═══════════╩═════════╩═══════════════╩═════════════╝
Sembra che quando il numero inserito di righe <~ 64 * è il numero di valori univoci che vediamo una compressione relativamente scarsa (2 byte per riga per mod <= 65000) e un utilizzo lineare e basso della CPU. Quando il numero inserito di righe> ~ 64 * indica il numero di valori univoci, vediamo una compressione molto migliore e un utilizzo della CPU ancora più lineare e lineare. C'è una transizione tra i due stati che non è facile per me modellare ma può essere visto nel grafico. Non sembra vero che vediamo il massimo utilizzo della CPU quando si inseriscono esattamente 64 righe per ogni valore univoco. Piuttosto, possiamo solo inserire un massimo di 1048576 righe in un gruppo di righe e vediamo un utilizzo e una compressione della CPU molto più elevati quando ci sono più di 64 righe per valore univoco.
Di seguito è riportato un diagramma di contorno di come il tempo della cpu cambia al variare del numero di righe inserite e del numero di righe univoche. Possiamo vedere gli schemi sopra descritti:
Di seguito è riportato un diagramma di contorno dello spazio utilizzato dal segmento. Dopo un certo punto iniziamo a vedere una compressione molto migliore, come descritto sopra:
Sembra che ci siano almeno due diversi algoritmi di compressione al lavoro qui. Considerato quanto sopra, ha senso che vedremo il massimo utilizzo della CPU quando si inseriscono 1048576 righe. Ha anche senso che si veda il maggior utilizzo della CPU a quel punto quando si inseriscono circa 16000 righe. 1048576/64 = 16384.
Ho caricato tutti i miei dati grezzi qui nel caso qualcuno volesse analizzarli.
Vale la pena ricordare cosa succede con i piani paralleli. Ho osservato questo comportamento solo con valori distribuiti uniformemente. Quando si fa un inserto parallelo c'è spesso un elemento di casualità e i thread sono generalmente sbilanciati.
Inserisci 2097152 righe nella tabella di gestione temporanea:
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;
Questo inserto termina in meno di un secondo e ha una compressione scarsa:
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);
Possiamo vedere l'effetto dei thread sbilanciati:
╔════════════╦════════════╦══════════════╦═══════════════╗
║ state_desc ║ total_rows ║ deleted_rows ║ size_in_bytes ║
╠════════════╬════════════╬══════════════╬═══════════════╣
║ OPEN ║ 13540 ║ 0 ║ 311296 ║
║ COMPRESSED ║ 1048576 ║ 0 ║ 2095872 ║
║ COMPRESSED ║ 1035036 ║ 0 ║ 2070784 ║
╚════════════╩════════════╩══════════════╩═══════════════╝
Ci sono vari trucchi che possiamo fare per forzare l'equilibrio dei thread e avere la stessa distribuzione di righe. Eccone uno:
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)
La scelta di un numero dispari per il modulo è importante qui. SQL Server esegue la scansione della tabella di gestione temporanea in serie, calcola il numero di riga, quindi utilizza la distribuzione round robin per posizionare le righe su thread paralleli. Ciò significa che finiremo con fili perfettamente bilanciati.
L'inserto richiede circa 40 secondi, il che è simile all'inserto seriale. Otteniamo rowgroup ben compressi:
╔════════════╦════════════╦══════════════╦═══════════════╗
║ state_desc ║ total_rows ║ deleted_rows ║ size_in_bytes ║
╠════════════╬════════════╬══════════════╬═══════════════╣
║ COMPRESSED ║ 1048576 ║ 0 ║ 128568 ║
║ COMPRESSED ║ 1048576 ║ 0 ║ 128568 ║
╚════════════╩════════════╩══════════════╩═══════════════╝
È possibile ottenere gli stessi risultati inserendo i dati dalla tabella di gestione temporanea originale:
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);
Qui viene utilizzata la distribuzione round robin per la tabella derivata, s
quindi viene eseguita una scansione della tabella su ciascun thread parallelo:
In conclusione, quando si inseriscono numeri interi distribuiti uniformemente, è possibile vedere una compressione molto elevata quando ciascun numero intero appare più di 64 volte. Ciò può essere dovuto all'uso di un diverso algoritmo di compressione. Ci può essere un costo elevato nella CPU per ottenere questa compressione. Piccole modifiche ai dati possono portare a notevoli differenze nelle dimensioni del segmento di rowgroup compresso. Ho il sospetto che vedere il caso peggiore (dal punto di vista della CPU) sia raro in natura, almeno per questo set di dati. È ancora più difficile vedere quando si eseguono inserti paralleli.