No puedo decir exactamente por qué ocurre este comportamiento, pero creo que he desarrollado un buen modelo del comportamiento a través de pruebas de fuerza bruta. Las siguientes conclusiones solo se aplican cuando se cargan datos en una sola columna y con enteros que están muy bien distribuidos.
Primero intenté variar el número de filas insertadas en el CCI usando TOP
. Solía ID % 16000
para todas las pruebas. A continuación se muestra un gráfico que compara las filas insertadas en el tamaño del segmento del grupo de filas comprimido:
A continuación se muestra un gráfico de filas insertadas al tiempo de CPU en ms. Tenga en cuenta que el eje X tiene un punto de partida diferente:
Podemos ver que el tamaño del segmento del grupo de filas crece a una velocidad lineal y utiliza una pequeña cantidad de CPU hasta alrededor de 1 M de filas. En ese punto, el tamaño del grupo de filas disminuye drásticamente y el uso de la CPU aumenta drásticamente. Parece que pagamos un alto precio en la CPU por esa compresión.
Al insertar menos de 1024000 filas, terminé con un grupo de filas abierto en el CCI. Sin embargo, forzar la compresión usandoREORGANIZE
o REBUILD
no tuvo un efecto en el tamaño. Como TOP
comentario aparte, me pareció interesante que cuando utilicé una variable para terminé con un grupo de filas abierto pero RECOMPILE
con un grupo de filas cerrado.
Luego probé variando el valor del módulo mientras mantenía el mismo número de filas. Aquí hay una muestra de los datos al insertar 102400 filas:
╔═══════════╦═════════╦═══════════════╦═════════════╗
║ 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 ║
╚═══════════╩═════════╩═══════════════╩═════════════╝
Hasta un valor de mod de 1600, el tamaño del segmento del grupo de filas aumenta linealmente en 80 bytes por cada 10 valores únicos adicionales. Es una coincidencia interesante que BIGINT
tradicionalmente ocupe 8 bytes y que el tamaño del segmento aumente en 8 bytes por cada valor único adicional. Más allá de un valor de mod de 1600, el tamaño del segmento aumenta rápidamente hasta que se estabiliza.
También es útil mirar los datos al dejar el valor del módulo igual y cambiar el número de filas insertadas:
╔═══════════╦═════════╦═══════════════╦═════════════╗
║ 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 ║
╚═══════════╩═════════╩═══════════════╩═════════════╝
Parece que cuando el número insertado de filas <~ 64 * el número de valores únicos vemos una compresión relativamente pobre (2 bytes por fila para mod <= 65000) y un bajo uso lineal de la CPU. Cuando el número insertado de filas> ~ 64 * el número de valores únicos, vemos una compresión mucho mejor y un uso de CPU aún más lineal. Hay una transición entre los dos estados que no es fácil para mí modelar, pero se puede ver en el gráfico. No parece ser cierto que veamos el uso máximo de CPU al insertar exactamente 64 filas para cada valor único. En cambio, solo podemos insertar un máximo de 1048576 filas en un grupo de filas y vemos un uso y compresión de CPU mucho mayor una vez que hay más de 64 filas por valor único.
A continuación se muestra un diagrama de contorno de cómo cambia el tiempo de la CPU a medida que cambia el número de filas insertadas y el número de filas únicas. Podemos ver los patrones descritos anteriormente:
A continuación se muestra un diagrama de contorno del espacio utilizado por el segmento. Después de cierto punto, comenzamos a ver una compresión mucho mejor, como se describió anteriormente:
Parece que hay al menos dos algoritmos de compresión diferentes en funcionamiento aquí. Dado lo anterior, tiene sentido que veamos el uso máximo de CPU al insertar 1048576 filas. También tiene sentido que veamos el mayor uso de CPU en ese punto al insertar alrededor de 16000 filas. 1048576/64 = 16384.
Subí todos mis datos en bruto aquí en caso de que alguien quiera analizarlos.
Vale la pena mencionar lo que sucede con los planes paralelos. Solo observé este comportamiento con valores distribuidos uniformemente. Al hacer una inserción paralela, a menudo hay un elemento de aleatoriedad y los hilos generalmente no están equilibrados.
Ponga 2097152 filas en la tabla de etapas:
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;
Este inserto termina en menos de un segundo y tiene una compresión deficiente:
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);
Podemos ver el efecto de los hilos no balanceados:
╔════════════╦════════════╦══════════════╦═══════════════╗
║ state_desc ║ total_rows ║ deleted_rows ║ size_in_bytes ║
╠════════════╬════════════╬══════════════╬═══════════════╣
║ OPEN ║ 13540 ║ 0 ║ 311296 ║
║ COMPRESSED ║ 1048576 ║ 0 ║ 2095872 ║
║ COMPRESSED ║ 1035036 ║ 0 ║ 2070784 ║
╚════════════╩════════════╩══════════════╩═══════════════╝
Hay varios trucos que podemos hacer para forzar el equilibrio de los hilos y tener la misma distribución de filas. Aqui esta uno de ellos:
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)
Elegir un número impar para el módulo es importante aquí. SQL Server escanea la tabla de etapas en serie, calcula el número de fila y luego usa la distribución por turnos para colocar las filas en subprocesos paralelos. Eso significa que terminaremos con hilos perfectamente equilibrados.
El inserto toma alrededor de 40 segundos, que es similar al inserto en serie. Obtenemos grupos de filas bien comprimidos:
╔════════════╦════════════╦══════════════╦═══════════════╗
║ state_desc ║ total_rows ║ deleted_rows ║ size_in_bytes ║
╠════════════╬════════════╬══════════════╬═══════════════╣
║ COMPRESSED ║ 1048576 ║ 0 ║ 128568 ║
║ COMPRESSED ║ 1048576 ║ 0 ║ 128568 ║
╚════════════╩════════════╩══════════════╩═══════════════╝
Podemos obtener los mismos resultados insertando datos de la tabla de etapas original:
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);
Aquí se usa la distribución round robin para la tabla derivada, s
por lo que se realiza un escaneo de la tabla en cada subproceso paralelo:
En conclusión, al insertar enteros distribuidos uniformemente puede ver una compresión muy alta cuando cada entero único aparece más de 64 veces. Esto puede deberse a que se utiliza un algoritmo de compresión diferente. Puede haber un alto costo en la CPU para lograr esta compresión. Pequeños cambios en los datos pueden conducir a diferencias dramáticas en el tamaño del segmento de grupo de filas comprimido. Sospecho que ver el peor de los casos (desde la perspectiva de la CPU) será poco común en la naturaleza, al menos para este conjunto de datos. Es aún más difícil de ver cuando se realizan inserciones paralelas.