Ich kann nicht genau sagen, warum dieses Verhalten auftritt, aber ich glaube, ich habe ein gutes Modell des Verhaltens durch Brute-Force-Tests entwickelt. Die folgenden Schlussfolgerungen gelten nur für das Laden von Daten in eine einzelne Spalte und für Ganzzahlen, die sehr gut verteilt sind.
Zuerst habe ich versucht, die Anzahl der in die CCI eingefügten Zeilen mit zu variieren TOP
. Ich habe ID % 16000
für alle Tests verwendet. Unten sehen Sie ein Diagramm, in dem Zeilen verglichen werden, die mit der Größe des komprimierten Zeilengruppensegments eingefügt wurden:
Unten sehen Sie eine Grafik der Zeilen, die zur CPU-Zeit in ms eingefügt wurden. Beachten Sie, dass die X-Achse einen anderen Ausgangspunkt hat:
Wir können sehen, dass die Segmentgröße der Zeilengruppe linear ansteigt und eine kleine Menge an CPU bis zu ungefähr 1 Million Zeilen verbraucht. Zu diesem Zeitpunkt nimmt die Zeilengruppengröße drastisch ab und die CPU-Auslastung nimmt drastisch zu. Es scheint, dass wir für diese Komprimierung einen hohen Preis für die CPU zahlen.
Beim Einfügen von weniger als 1024000 Zeilen ergab sich eine offene Zeilengruppe in der CCI. Das Erzwingen der Komprimierung mit REORGANIZE
oder hatte REBUILD
jedoch keinen Einfluss auf die Größe. Abgesehen davon fand ich es interessant, dass ich, als ich eine Variable verwendete TOP
, mit einer offenen, aber RECOMPILE
mit einer geschlossenen Zeilengruppe endete.
Als nächstes testete ich, indem ich den Modulwert variierte, während die Anzahl der Zeilen gleich blieb. Hier ein Beispiel der Daten beim Einfügen von 102400 Zeilen:
╔═══════════╦═════════╦═══════════════╦═════════════╗
║ 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 ║
╚═══════════╩═════════╩═══════════════╩═════════════╝
Bis zu einem mod-Wert von 1600 erhöht sich die Zeilengruppensegmentgröße linear um 80 Byte für jede weiteren 10 eindeutigen Werte. Es ist ein interessanter Zufall, dass a BIGINT
traditionell 8 Bytes belegt und die Segmentgröße für jeden weiteren eindeutigen Wert um 8 Bytes zunimmt. Ab einem Mod-Wert von 1600 nimmt die Segmentgröße schnell zu, bis sie sich stabilisiert.
Es ist auch hilfreich, die Daten zu betrachten, wenn der Modulwert gleich bleibt und die Anzahl der eingefügten Zeilen geändert wird:
╔═══════════╦═════════╦═══════════════╦═════════════╗
║ 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 ║
╚═══════════╩═════════╩═══════════════╩═════════════╝
Es sieht so aus, als ob die eingefügte Anzahl der Zeilen <~ 64 * die Anzahl der eindeutigen Werte ist. Wir sehen eine relativ schlechte Komprimierung (2 Bytes pro Zeile für mod <= 65000) und eine niedrige lineare CPU-Auslastung. Wenn die Anzahl der eingefügten Zeilen> ~ 64 * die Anzahl der eindeutigen Werte ist, sehen wir eine viel bessere Komprimierung und eine höhere, immer noch lineare CPU-Auslastung. Es gibt einen Übergang zwischen den beiden Zuständen, der für mich nicht einfach zu modellieren ist, aber in der Grafik zu sehen ist. Es scheint nicht zu stimmen, dass die maximale CPU-Auslastung angezeigt wird, wenn für jeden eindeutigen Wert genau 64 Zeilen eingefügt werden. Wir können nur maximal 1048576 Zeilen in eine Zeilengruppe einfügen und sehen eine viel höhere CPU-Auslastung und -Komprimierung, sobald mehr als 64 Zeilen pro eindeutigem Wert vorhanden sind.
Unten sehen Sie ein Konturdiagramm, wie sich die CPU-Zeit ändert, wenn sich die Anzahl der eingefügten Zeilen und die Anzahl der eindeutigen Zeilen ändert. Wir können die oben beschriebenen Muster sehen:
Unten sehen Sie eine Konturdarstellung des vom Segment verwendeten Raums. Ab einem bestimmten Punkt stellen wir eine viel bessere Komprimierung fest, wie oben beschrieben:
Anscheinend arbeiten hier mindestens zwei verschiedene Komprimierungsalgorithmen. Vor diesem Hintergrund ist es sinnvoll, dass beim Einfügen von 1048576 Zeilen die maximale CPU-Auslastung angezeigt wird. Es ist auch sinnvoll, dass beim Einfügen von etwa 16000 Zeilen zu diesem Zeitpunkt die höchste CPU-Auslastung festgestellt wird. 1048576/64 = 16384.
Ich habe alle meine Rohdaten hier hochgeladen, falls jemand sie analysieren möchte.
Erwähnenswert ist, was bei Parallelplänen passiert. Ich habe dieses Verhalten nur bei gleichmäßig verteilten Werten beobachtet. Beim parallelen Einfügen kommt es häufig zu einem Zufallselement, und die Threads sind normalerweise nicht ausgeglichen.
Fügen Sie 2097152 Zeilen in die Staging-Tabelle ein:
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;
Diese Einlage ist in weniger als einer Sekunde fertig und weist eine schlechte Komprimierung auf:
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);
Wir können die Wirkung der unsymmetrischen Fäden sehen:
╔════════════╦════════════╦══════════════╦═══════════════╗
║ state_desc ║ total_rows ║ deleted_rows ║ size_in_bytes ║
╠════════════╬════════════╬══════════════╬═══════════════╣
║ OPEN ║ 13540 ║ 0 ║ 311296 ║
║ COMPRESSED ║ 1048576 ║ 0 ║ 2095872 ║
║ COMPRESSED ║ 1035036 ║ 0 ║ 2070784 ║
╚════════════╩════════════╩══════════════╩═══════════════╝
Es gibt verschiedene Tricks, die wir anwenden können, um das Ausbalancieren der Threads und die gleiche Verteilung der Zeilen zu erzwingen. Hier ist einer von ihnen:
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)
Die Wahl einer ungeraden Zahl für den Modul ist hier wichtig. SQL Server durchsucht die Staging-Tabelle seriell, berechnet die Zeilennummer und verwendet dann die Round-Robin-Verteilung, um die Zeilen auf parallelen Threads zu platzieren. Das bedeutet, dass wir am Ende perfekt ausbalancierte Fäden haben.
Der Einsatz dauert ungefähr 40 Sekunden, ähnlich dem seriellen Einsatz. Wir erhalten schön komprimierte Zeilengruppen:
╔════════════╦════════════╦══════════════╦═══════════════╗
║ state_desc ║ total_rows ║ deleted_rows ║ size_in_bytes ║
╠════════════╬════════════╬══════════════╬═══════════════╣
║ COMPRESSED ║ 1048576 ║ 0 ║ 128568 ║
║ COMPRESSED ║ 1048576 ║ 0 ║ 128568 ║
╚════════════╩════════════╩══════════════╩═══════════════╝
Wir können die gleichen Ergebnisse erzielen, indem wir Daten aus der ursprünglichen Staging-Tabelle einfügen:
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);
Hier wird die Round-Robin-Verteilung für die abgeleitete Tabelle verwendet, s
sodass ein Scan der Tabelle für jeden parallelen Thread durchgeführt wird:
Wenn Sie also gleichmäßig verteilte Ganzzahlen einfügen, können Sie eine sehr hohe Komprimierung feststellen, wenn jede einzelne Ganzzahl mehr als 64-mal vorkommt. Dies kann auf einen anderen Komprimierungsalgorithmus zurückzuführen sein. Das Erreichen dieser Komprimierung kann hohe CPU-Kosten verursachen. Kleine Änderungen in den Daten können zu dramatischen Unterschieden in der Größe des komprimierten Zeilengruppensegments führen. Ich vermute, dass es zumindest für diesen Datensatz ungewöhnlich sein wird, den Worst-Case (aus Sicht der CPU) zu sehen. Es ist noch schwieriger zu erkennen, wenn parallele Einfügungen vorgenommen werden.