Nie mogę dokładnie powiedzieć, dlaczego tak się dzieje, ale wydaje mi się, że opracowałem dobry model tego zachowania za pomocą testów siłowych. Poniższe wnioski dotyczą tylko ładowania danych do pojedynczej kolumny i liczb całkowitych, które są bardzo dobrze rozmieszczone.
Najpierw próbowałem zmienić liczbę wierszy wstawionych do CCI za pomocą TOP
. Użyłem ID % 16000
do wszystkich testów. Poniżej znajduje się wykres porównujący wiersze wstawione do rozmiaru segmentu skompresowanej grupy wierszy:
Poniżej znajduje się wykres wierszy wstawionych do czasu procesora w ms. Zauważ, że oś X ma inny punkt początkowy:
Widzimy, że rozmiar segmentu grupy rzędów rośnie w tempie liniowym i zużywa niewielką ilość procesora aż do około 1 mln wierszy. W tym momencie rozmiar grupy wierszy dramatycznie spada, a użycie procesora dramatycznie wzrasta. Wydaje się, że za tę kompresję płacimy wysoką cenę procesora.
Wstawiając mniej niż 1024000 wierszy skończyłem z otwartą grupą wierszy w CCI. Wymuszanie kompresji przy użyciu REORGANIZE
lub REBUILD
nie miało wpływu na rozmiar. TOP
Nawiasem mówiąc, ciekawe było to, że kiedy użyłem zmiennej dla , skończyłem z otwartą grupą wierszy, ale z RECOMPILE
skończyłem z zamkniętą grupą wierszy.
Następnie przetestowałem zmieniając wartość modułu, zachowując tę samą liczbę wierszy. Oto próbka danych podczas wstawiania 102400 wierszy:
╔═══════════╦═════════╦═══════════════╦═════════════╗
║ 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 ║
╚═══════════╩═════════╩═══════════════╩═════════════╝
Aż do wartości mod 1600, rozmiar segmentu grupy wierszy zwiększa się liniowo o 80 bajtów na każde dodatkowe 10 unikalnych wartości. To ciekawy zbieg okoliczności, że BIGINT
tradycyjnie zajmuje 8 bajtów, a rozmiar segmentu wzrasta o 8 bajtów dla każdej dodatkowej unikalnej wartości. Po przekroczeniu wartości mod wynoszącej 1600 rozmiar segmentu szybko rośnie, aż się ustabilizuje.
Pomocne jest również spojrzenie na dane, gdy pozostawia się wartość modułu równą i zmienia liczbę wstawionych wierszy:
╔═══════════╦═════════╦═══════════════╦═════════════╗
║ 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 ║
╚═══════════╩═════════╩═══════════════╩═════════════╝
Wygląda na to, że gdy wstawiona liczba wierszy <~ 64 * liczba unikalnych wartości widzimy stosunkowo słabą kompresję (2 bajty na wiersz dla mod <= 65000) i niskie, liniowe zużycie procesora. Gdy wstawiona liczba wierszy> ~ 64 * liczba unikalnych wartości, widzimy znacznie lepszą kompresję i wyższe, wciąż liniowe użycie procesora. Pomiędzy tymi dwoma stanami istnieje przejście, które nie jest dla mnie łatwe do modelowania, ale można to zobaczyć na wykresie. Nie wydaje się prawdą, że widzimy maksymalne użycie procesora podczas wstawiania dokładnie 64 wierszy dla każdej unikalnej wartości. Zamiast tego możemy wstawić maksymalnie 1048576 wierszy do grupy wierszy i widzimy znacznie większe użycie procesora i kompresję, gdy jest więcej niż 64 wierszy na unikalną wartość.
Poniżej znajduje się wykres konturowy zmian czasu procesora w miarę zmiany liczby wstawianych wierszy i liczby unikalnych wierszy. Widzimy wzorce opisane powyżej:
Poniżej znajduje się konturowa przestrzeń użyta przez segment. Po pewnym momencie zaczynamy widzieć znacznie lepszą kompresję, jak opisano powyżej:
Wygląda na to, że działają tu co najmniej dwa różne algorytmy kompresji. Biorąc pod uwagę powyższe, sensowne jest, abyśmy widzieli maksymalne użycie procesora podczas wstawiania 1048576 wierszy. Sensowne jest również to, że widzimy największe zużycie procesora w tym momencie, gdy wstawiamy około 16000 wierszy. 1048576/64 = 16384.
Upload wszystkie moje dane surowe tutaj w przypadku gdy ktoś chce je analizować.
Warto wspomnieć o tym, co dzieje się z równoległymi planami. Obserwowałem to zachowanie tylko przy równomiernie rozłożonych wartościach. Podczas wstawiania równoległego często występuje element losowości, a wątki są zwykle niezrównoważone.
Umieść 2097152 wierszy w tabeli pomostowej:
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;
Ta wkładka kończy się w mniej niż sekundę i ma słabą kompresję:
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);
Widzimy efekt niezrównoważonych wątków:
╔════════════╦════════════╦══════════════╦═══════════════╗
║ state_desc ║ total_rows ║ deleted_rows ║ size_in_bytes ║
╠════════════╬════════════╬══════════════╬═══════════════╣
║ OPEN ║ 13540 ║ 0 ║ 311296 ║
║ COMPRESSED ║ 1048576 ║ 0 ║ 2095872 ║
║ COMPRESSED ║ 1035036 ║ 0 ║ 2070784 ║
╚════════════╩════════════╩══════════════╩═══════════════╝
Istnieją różne sztuczki, które możemy zrobić, aby wymusić zrównoważenie wątków i taki sam rozkład rzędów. Oto jeden z nich:
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)
Ważny jest tutaj wybór liczby nieparzystej dla modułu. Program SQL Server skanuje szeregowo tabelę pomostową, oblicza numer wiersza, a następnie używa okrągłej dystrybucji robin do umieszczenia wierszy w równoległych wątkach. Oznacza to, że otrzymamy idealnie zrównoważone wątki.
Wkładka zajmuje około 40 sekund, co jest podobne do wkładki szeregowej. Otrzymujemy ładnie skompresowane grupy wierszy:
╔════════════╦════════════╦══════════════╦═══════════════╗
║ state_desc ║ total_rows ║ deleted_rows ║ size_in_bytes ║
╠════════════╬════════════╬══════════════╬═══════════════╣
║ COMPRESSED ║ 1048576 ║ 0 ║ 128568 ║
║ COMPRESSED ║ 1048576 ║ 0 ║ 128568 ║
╚════════════╩════════════╩══════════════╩═══════════════╝
Możemy uzyskać te same wyniki, wstawiając dane z oryginalnej tabeli pomostowej:
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);
W tym przypadku dla tabeli pochodnej wykorzystywany jest okrągły rozkład robin, s
więc jeden skan tabeli jest wykonywany dla każdego równoległego wątku:
Podsumowując, podczas wstawiania równomiernie rozmieszczonych liczb całkowitych można zauważyć bardzo wysoką kompresję, gdy każda unikalna liczba całkowita pojawia się więcej niż 64 razy. Przyczyną może być inny algorytm kompresji. Osiągnięcie tej kompresji może wymagać wysokich kosztów procesora. Małe zmiany w danych mogą prowadzić do dramatycznych różnic w rozmiarze skompresowanego segmentu grupy wierszy. Podejrzewam, że widzenie najgorszego przypadku (z perspektywy procesora) będzie rzadkie na wolności, przynajmniej w przypadku tego zestawu danych. Jeszcze trudniej to dostrzec podczas wykonywania równoległych wstawek.