Database
 sql >> Baza danych >  >> RDS >> Database

Zabawa z kompresją (kolumnową) na bardzo dużym stole – część 1

[ Część 1 | Część 2 | Część 3 ]

Ostatnio ktoś w pracy poprosił o więcej miejsca na szybko rosnący stół. W tym czasie miał 3,75 miliarda wierszy, prezentowanych na 143 milionach stron i zajmujących ~1,14 TB. Oczywiście zawsze możemy rzucić więcej dysków na stół, ale chciałem zobaczyć, czy możemy skalować to wydajniej niż obecny trend liniowy. Brzmi jak świetna robota do kompresji, prawda? Ale chciałem też wypróbować kilka innych rozwiązań, w tym columnstore – którego ludzie zaskakująco niechętnie próbują. Nie jestem Niko, ale chciałem się postarać, aby zobaczyć, co może tu dla nas zrobić.

Pamiętaj, że w tej chwili nie skupiam się na raportowaniu obciążenia lub wydajności innych zapytań odczytu – chcę tylko zobaczyć, jaki wpływ mogę mieć na wykorzystanie pamięci (i pamięci) tych danych.

Oto oryginalny stół. Zmieniłem nazwy tabel i kolumn, aby chronić niewinnych, ale wszystko inne jest stosunkowo dokładne.

CREATE TABLE dbo.tblOriginal
(
	OID bigint          IDENTITY(1,1) NOT NULL PRIMARY KEY, -- there are gaps!
	IN1 int             NOT NULL,
	IN2 int             NOT NULL,
	VC1 varchar(3)      NULL,
	BI1 bigint          NULL,
	IN3 int             NULL,
	VC2 varchar(128)    NOT NULL,
	VC3 varchar(128)    NOT NULL,
	VC4 varchar(128)    NULL,
	NM1 numeric(24,12)  NULL,
	NM2 numeric(24,12)  NULL,
	NM3 numeric(24,12)  NULL,
	BI2 bigint          NULL,
	IN4 int             NULL,
	BI3 bigint          NULL,
	NM4 numeric(24,12)  NULL,
	IN5 int             NULL,
	NM5 numeric(24,12)  NULL,
	DT1 date            NULL,
	VC5 varchar(128)    NULL,
	BI4 bigint          NULL,
	BI5 bigint          NULL,
	BI6 bigint          NULL,
	BT1 bit             NOT NULL,
	NV1 nvarchar(512)   NULL,
	VB1                 AS (HASHBYTES('MD5',VC2+VC3)),
	IN6 int             NULL,
	IN7 int             NULL,
	IN8 int             NULL
);

Jest tam kilka innych drobiazgów, które są szersze niż powinny i/lub kompresja wierszy może zostać oczyszczona, na przykład te numeric(24,12) i bigint kolumny, które mogą być przedwcześnie przewymiarowane, ale nie zamierzam wracać do zespołu aplikacji i sprawdzać, czy jest tam niewielka wydajność, i pominę kompresję wierszy w tym ćwiczeniu i skoncentruję się na kompresji strony i magazynu kolumn.

Jest to kopia danych na bezczynnym serwerze (8 rdzeni, 64 GB RAM), z dużą ilością miejsca na dysku (znacznie ponad 6 TB). Więc najpierw dodajmy kilka grup plików, jedną dla standardowego klastrowanego magazynu kolumn i jedną dla partycjonowanej wersji tabeli (gdzie wszystkie partycje oprócz najnowszej zostaną skompresowane za pomocą COLUMNSTORE_ARCHIVE , ponieważ wszystkie te starsze dane są teraz „tylko do odczytu i rzadko”):

ALTER DATABASE OCopy ADD FILEGROUP FG_CCI;
ALTER DATABASE OCopy ADD FILEGROUP FG_CCI_PARTITIONED;

A potem kilka plików dla tych grup plików (jeden plik na rdzeń, ładny i jednorodny rozmiar przy 256 GB):

ALTER DATABASE OCopy ADD FILE (name = N'CCI_1', size = 250000, 
  filename = 'K:\Data\o_cci_1.mdf') TO FILEGROUP FG_CCI;
  -- ... 6 more ...
ALTER DATABASE OCopy ADD FILE (name = N'CCI_8', size = 250000, 
  filename = 'K:\Data\o_cci_8.mdf') TO FILEGROUP FG_CCI;
 
ALTER DATABASE OCopy ADD FILE (name = N'CCI_P_1', size = 250000, 
  filename = 'K:\Data\o_p_1.mdf') TO FILEGROUP FG_CCI_PARTITIONED;
  -- ... 6 more ...
ALTER DATABASE OCopy ADD FILE (name = N'CCI_P_8', size = 250000, 
  filename = 'K:\Data\o_p_8.mdf') TO FILEGROUP FG_CCI_PARTITIONED;

Na tym konkretnym sprzęcie (YMMV!) zajęło to około 10 sekund na plik i dało następujące wyniki:

Aby wygenerować partycje, naiwnie podzieliłem dane „równomiernie” – a przynajmniej tak mi się wydawało. Właśnie wziąłem 3,75 miliarda wierszy i podzieliłem na coś, co wydawało mi się możliwe do opanowania:38 partycji ze 100 milionami wierszy w pierwszych 37 partycjach, a reszta w ostatniej. (Pamiętaj, że to tylko część 1! Istnieje nieodłączne założenie o równomiernym rozkładzie wartości w tabeli źródłowej, a także wokół tego, co jest optymalne dla populacji wierszy w tabeli docelowej.) Tworzenie schematu partycji i funkcji dla tego jest tak następuje:

CREATE PARTITION FUNCTION PF_OID([bigint])
AS RANGE LEFT FOR VALUES (100000000, 200000000, /* ... 33 more ... */ , 3600000000, 3700000000);
 
CREATE PARTITION SCHEME PS_OID AS PARTITION PF_OID ALL TO (FG_CCI_PARTITIONED);

Używam RANGE LEFT ponieważ, jak wciąż przypomina mi Cathrine Wilhelmsen, oznacza to, że wartość graniczna jest częścią przegrody po jej lewej stronie. Innymi słowy, wartości, które określam, są maksymalnymi wartościami w każdej partycji (w przypadku dat zwykle chcesz RANGE RIGHT ).

Następnie utworzyłem dwie kopie tabeli, po jednej na każdej grupie plików. Pierwszy z nich miał standardowy klastrowany indeks magazynu kolumn, jedyne różnice to OID kolumna nie jest IDENTITY a wyliczona kolumna to po prostu varbinary(8000) :

CREATE TABLE dbo.tblCCI
(
  OID bigint NOT NULL,
  -- ... other columns ...
) ON FG_CCI;
GO
 
CREATE CLUSTERED COLUMNSTORE INDEX CCI_IX 
  ON dbo.tblCCI;

Drugi został zbudowany na schemacie partycji, więc najpierw potrzebował nazwanego PK, który następnie musiał zostać zastąpiony klastrowanym indeksem magazynu kolumn (chociaż Brent Ozar pokazuje w tym krótkim poście, że istnieje pewna nieintuicyjna składnia, która umożliwi to w mniejszej liczbie kroków ):

CREATE TABLE dbo.tblCCI_Partitioned
(
  OID bigint NOT NULL,
  -- ... other columns ...,
  CONSTRAINT PK_CCI_Part PRIMARY KEY CLUSTERED (OID) ON PS_OID (OID)
);
GO
 
ALTER TABLE dbo.tblCCI_Partitioned DROP CONSTRAINT PK_CCI_Part;
GO
 
CREATE CLUSTERED COLUMNSTORE INDEX CCI_Part 
  ON dbo.tblCCI_Partitioned 
  ON PS_OID (OID);

Następnie, aby umieścić kompresję archiwum na wszystkich partycjach oprócz ostatniej, uruchomiłem następujące polecenie:

ALTER TABLE dbo.tblCCI_Part  
REBUILD PARTITION = ALL WITH 
(  
    DATA_COMPRESSION = COLUMNSTORE         ON PARTITIONS (38),  
    DATA_COMPRESSION = COLUMNSTORE_ARCHIVE ON PARTITIONS (1 TO 37)  
);

Teraz byłem gotów wypełnić te tabele danymi, zmierzyć czas i wynikowy rozmiar oraz porównać. Zmodyfikowałem pomocny skrypt wsadowy Andy'ego Mallona i wstawiłem wiersze do obu tabel po kolei, z rozmiarem partii 10 milionów wierszy. W prawdziwym skrypcie jest o wiele więcej (w tym aktualizacja tabeli kolejki z postępem), ale w zasadzie:

DECLARE @BatchSize int = 10000000, @MaxID bigint, @LastID bigint = 0;
 
SELECT @MaxID = MAX(OID) FROM dbo.tblOriginal;
 
WHILE @LastID < @MaxID
BEGIN
  INSERT dbo.tblCCI
  (
    -- all columns except the computed column 
  )
  SELECT -- all columns except the computed column
    FROM dbo.tblOriginal AS o
      WHERE o.CostID >= @LastID
        AND o.CostID < @LastID + @BatchSize;
 
  SET @LastID += @BatchSize;
END

Po zapełnieniu obu tabel magazynu kolumn z oryginalnego (nieskompresowanego) źródła, ponownie odbudowałem te partycje, aby wyczyścić bałagan w grupach wierszy i słownikach. Na koniec zastosowałem kompresję stron w tabeli źródłowej. Oto czasy i wyniki kompresji każdego typu:

Jestem pod wrażeniem i rozczarowaniem. Pod wrażeniem, ponieważ te dane są bardzo dobrze skompresowane – zmniejszenie pojemności pamięci masowej do 5% pierwotnego 1 TB jest niesamowite. Rozczarowany, ponieważ:

  1. Zrobiłem te pliki danych sposób za duży.
  2. Nie rozumiem, co się stało z 14-godzinną wstępną kompresją magazynu kolumn:
    • Nie zaobserwowałem żadnej pamięci ani nacisku na dzienniki.
    • Nie wystąpiły zdarzenia wzrostu plików.
    • Niestety nie przyszło mi do głowy, aby śledzić oczekiwania. Nie, nie zamierzam ponownie tego próbować. :-)
  3. Kompresja strony przewyższa zwykłą kompresję magazynu kolumn — być może z powodu danych.
  4. Odbudowa partycji archiwum magazynu kolumn zużywała dużo czasu procesora przy prawie zerowym zysku.

W nadchodzących postach i po przejrzeniu moich notatek z niesamowitej prezentacji w sklepie kolumnowym autorstwa Joe Obbisha na PASS Summit (do którego link bezpośrednio, gdyby tylko PASS wiedział, jak korzystać z interfejsu użytkownika), opowiem trochę o zmianach, które wprowadź konfigurację serwera i mój skrypt populacji, aby sprawdzić, czy mogę uzyskać lepszą wydajność z populacji magazynu kolumn.

[ Część 1 | Część 2 | Część 3 ]


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. 9 najlepszych praktyk dotyczących pisania zapytań SQL

  2. ScaleGrid DBaaS na krótkiej liście Cloud Excellence Awards 2018

  3. Opieranie modeli baz danych na rzeczywistości:wyzwanie blogera

  4. Podobieństwa i różnice między funkcjami RANK, DENSE_RANK i ROW_NUMBER

  5. Podstawy wyrażeń tabelowych, Część 9 – Widoki w porównaniu z tabelami pochodnymi i CTE