Udostępnij za pośrednictwem


Kompresowanie danych przy użyciu tabel kolumnowych w usłudze Azure Cosmos DB for PostgreSQL

DOTYCZY: Usługa Azure Cosmos DB for PostgreSQL (obsługiwana przez rozszerzenie bazy danych Citus do bazy danych PostgreSQL)

Usługa Azure Cosmos DB for PostgreSQL obsługuje magazyn tabel kolumnowych tylko do dołączania na potrzeby obciążeń analitycznych i magazynowania danych. Gdy kolumny (a nie wiersze) są przechowywane stale na dysku, dane stają się bardziej kompresowane, a zapytania mogą szybciej żądać podzestawu kolumn.

Utwórz tabelę

Aby użyć magazynu kolumnowego, określ USING columnar podczas tworzenia tabeli:

CREATE TABLE contestant (
    handle TEXT,
    birthdate DATE,
    rating INT,
    percentile FLOAT,
    country CHAR(3),
    achievements TEXT[]
) USING columnar;

Usługa Azure Cosmos DB for PostgreSQL konwertuje wiersze na magazyn kolumnowy w "paski" podczas wstawiania. Każdy pasek zawiera jedną wartość danych transakcji lub 150000 wierszy, w zależności od tego, która wartość jest mniejsza. (Rozmiar paska i inne parametry tabeli kolumnowej można zmienić za pomocą funkcji alter_columnar_table_set ).

Na przykład następująca instrukcja umieszcza wszystkie pięć wierszy w tym samym pasku, ponieważ wszystkie wartości są wstawione w jednej transakcji:

-- insert these values into a single columnar stripe

INSERT INTO contestant VALUES
  ('a','1990-01-10',2090,97.1,'XA','{a}'),
  ('b','1990-11-01',2203,98.1,'XA','{a,b}'),
  ('c','1988-11-01',2907,99.4,'XB','{w,y}'),
  ('d','1985-05-05',2314,98.3,'XB','{}'),
  ('e','1995-05-05',2236,98.2,'XC','{a}');

Najlepiej, gdy jest to możliwe, aby duże paski, ponieważ usługa Azure Cosmos DB for PostgreSQL kompresuje dane kolumnowe oddzielnie na paski. Możemy zobaczyć fakty dotyczące naszej tabeli kolumnowej, takie jak szybkość kompresji, liczba pasków i średnie wiersze na paski przy użyciu polecenia VACUUM VERBOSE:

VACUUM VERBOSE contestant;
INFO:  statistics for "contestant":
storage id: 10000000000
total file size: 24576, total data size: 248
compression rate: 1.31x
total row count: 5, stripe count: 1, average rows per stripe: 5
chunk count: 6, containing data for dropped columns: 0, zstd compressed: 6

Dane wyjściowe pokazują, że usługa Azure Cosmos DB for PostgreSQL użyła algorytmu kompresji zstd w celu uzyskania kompresji danych 1,31 x. Szybkość kompresji porównuje a) rozmiar wstawionych danych, ponieważ został on przygotowany w pamięci względem b) rozmiar tych danych skompresowanych w ostatecznym pasku.

Ze względu na to, jak jest mierzona, szybkość kompresji może być niezgodna z różnicą rozmiaru między magazynem wierszy i kolumn dla tabeli. Jedynym sposobem, aby naprawdę znaleźć tę różnicę, jest utworzenie tabeli wierszy i kolumn, które zawierają te same dane i porównanie.

Mierzenie kompresji

Utwórzmy nowy przykład z większą ilością danych, aby porównać oszczędności kompresji.

-- first a wide table using row storage
CREATE TABLE perf_row(
  c00 int8, c01 int8, c02 int8, c03 int8, c04 int8, c05 int8, c06 int8, c07 int8, c08 int8, c09 int8,
  c10 int8, c11 int8, c12 int8, c13 int8, c14 int8, c15 int8, c16 int8, c17 int8, c18 int8, c19 int8,
  c20 int8, c21 int8, c22 int8, c23 int8, c24 int8, c25 int8, c26 int8, c27 int8, c28 int8, c29 int8,
  c30 int8, c31 int8, c32 int8, c33 int8, c34 int8, c35 int8, c36 int8, c37 int8, c38 int8, c39 int8,
  c40 int8, c41 int8, c42 int8, c43 int8, c44 int8, c45 int8, c46 int8, c47 int8, c48 int8, c49 int8,
  c50 int8, c51 int8, c52 int8, c53 int8, c54 int8, c55 int8, c56 int8, c57 int8, c58 int8, c59 int8,
  c60 int8, c61 int8, c62 int8, c63 int8, c64 int8, c65 int8, c66 int8, c67 int8, c68 int8, c69 int8,
  c70 int8, c71 int8, c72 int8, c73 int8, c74 int8, c75 int8, c76 int8, c77 int8, c78 int8, c79 int8,
  c80 int8, c81 int8, c82 int8, c83 int8, c84 int8, c85 int8, c86 int8, c87 int8, c88 int8, c89 int8,
  c90 int8, c91 int8, c92 int8, c93 int8, c94 int8, c95 int8, c96 int8, c97 int8, c98 int8, c99 int8
);

-- next a table with identical columns using columnar storage
CREATE TABLE perf_columnar(LIKE perf_row) USING COLUMNAR;

Wypełnij obie tabele tym samym dużym zestawem danych:

INSERT INTO perf_row
  SELECT
    g % 00500, g % 01000, g % 01500, g % 02000, g % 02500, g % 03000, g % 03500, g % 04000, g % 04500, g % 05000,
    g % 05500, g % 06000, g % 06500, g % 07000, g % 07500, g % 08000, g % 08500, g % 09000, g % 09500, g % 10000,
    g % 10500, g % 11000, g % 11500, g % 12000, g % 12500, g % 13000, g % 13500, g % 14000, g % 14500, g % 15000,
    g % 15500, g % 16000, g % 16500, g % 17000, g % 17500, g % 18000, g % 18500, g % 19000, g % 19500, g % 20000,
    g % 20500, g % 21000, g % 21500, g % 22000, g % 22500, g % 23000, g % 23500, g % 24000, g % 24500, g % 25000,
    g % 25500, g % 26000, g % 26500, g % 27000, g % 27500, g % 28000, g % 28500, g % 29000, g % 29500, g % 30000,
    g % 30500, g % 31000, g % 31500, g % 32000, g % 32500, g % 33000, g % 33500, g % 34000, g % 34500, g % 35000,
    g % 35500, g % 36000, g % 36500, g % 37000, g % 37500, g % 38000, g % 38500, g % 39000, g % 39500, g % 40000,
    g % 40500, g % 41000, g % 41500, g % 42000, g % 42500, g % 43000, g % 43500, g % 44000, g % 44500, g % 45000,
    g % 45500, g % 46000, g % 46500, g % 47000, g % 47500, g % 48000, g % 48500, g % 49000, g % 49500, g % 50000
  FROM generate_series(1,50000000) g;

INSERT INTO perf_columnar
  SELECT
    g % 00500, g % 01000, g % 01500, g % 02000, g % 02500, g % 03000, g % 03500, g % 04000, g % 04500, g % 05000,
    g % 05500, g % 06000, g % 06500, g % 07000, g % 07500, g % 08000, g % 08500, g % 09000, g % 09500, g % 10000,
    g % 10500, g % 11000, g % 11500, g % 12000, g % 12500, g % 13000, g % 13500, g % 14000, g % 14500, g % 15000,
    g % 15500, g % 16000, g % 16500, g % 17000, g % 17500, g % 18000, g % 18500, g % 19000, g % 19500, g % 20000,
    g % 20500, g % 21000, g % 21500, g % 22000, g % 22500, g % 23000, g % 23500, g % 24000, g % 24500, g % 25000,
    g % 25500, g % 26000, g % 26500, g % 27000, g % 27500, g % 28000, g % 28500, g % 29000, g % 29500, g % 30000,
    g % 30500, g % 31000, g % 31500, g % 32000, g % 32500, g % 33000, g % 33500, g % 34000, g % 34500, g % 35000,
    g % 35500, g % 36000, g % 36500, g % 37000, g % 37500, g % 38000, g % 38500, g % 39000, g % 39500, g % 40000,
    g % 40500, g % 41000, g % 41500, g % 42000, g % 42500, g % 43000, g % 43500, g % 44000, g % 44500, g % 45000,
    g % 45500, g % 46000, g % 46500, g % 47000, g % 47500, g % 48000, g % 48500, g % 49000, g % 49500, g % 50000
  FROM generate_series(1,50000000) g;

VACUUM (FREEZE, ANALYZE) perf_row;
VACUUM (FREEZE, ANALYZE) perf_columnar;

W przypadku tych danych współczynnik kompresji jest lepszy niż 8X w tabeli kolumnowej.

SELECT pg_total_relation_size('perf_row')::numeric/
       pg_total_relation_size('perf_columnar') AS compression_ratio;
 compression_ratio
--------------------
 8.0196135873627944
(1 row)

Przykład

Magazyn kolumnowy działa dobrze w przypadku partycjonowania tabel. Aby zapoznać się z przykładem, zobacz dokumentację społeczności aparatu Citus, archiwizowanie za pomocą magazynu kolumnowego.

Gotchas

  • Magazyn kolumnowy kompresuje na paski. Paski są tworzone na transakcję, więc wstawianie jednego wiersza na transakcję spowoduje umieszczenie pojedynczych wierszy we własnych paskach. Kompresja i wydajność pojedynczych pasków wierszy będzie gorzej niż tabela wierszy. Zawsze wstaw zbiorczo do tabeli kolumnowej.
  • Jeśli zadzierasz i felietonizujesz kilka małych pasków, utkniesz. Jedyną poprawką jest utworzenie nowej tabeli kolumnowej i skopiowanie danych z oryginału w jednej transakcji:
    BEGIN;
    CREATE TABLE foo_compacted (LIKE foo) USING columnar;
    INSERT INTO foo_compacted SELECT * FROM foo;
    DROP TABLE foo;
    ALTER TABLE foo_compacted RENAME TO foo;
    COMMIT;
    
  • Zasadniczo nieskompresowalne dane mogą być problemem, chociaż magazyn kolumnowy jest nadal przydatny podczas wybierania określonych kolumn. Nie trzeba ładować innych kolumn do pamięci.
  • W tabeli podzielonej na partycje z kombinacją partycji wierszy i kolumn aktualizacje muszą być dokładnie ukierunkowane. Przefiltruj je, aby trafić tylko do partycji wierszy.
    • Jeśli operacja jest ukierunkowana na określoną partycję wiersza (na przykład UPDATE p2 SET i = i + 1), zakończy się powodzeniem. Jeśli zostanie ona skierowana do określonej partycji kolumnowej (na przykład UPDATE p1 SET i = i + 1), zakończy się niepowodzeniem.
    • Jeśli operacja jest przeznaczona dla tabeli partycjonowanej i ma klauzulę WHERE, która wyklucza wszystkie partycje kolumnowe (na przykład UPDATE parent SET i = i + 1 WHERE timestamp = '2020-03-15'), powiedzie się.
    • Jeśli operacja jest przeznaczona dla tabeli partycjonowanej, ale nie filtruje kolumn klucza partycji, zakończy się niepowodzeniem. Nawet jeśli istnieją klauzule WHERE, które pasują do wierszy tylko w partycjach kolumnowych, nie jest wystarczająco dużo — klucz partycji również musi być filtrowany.

Ograniczenia

Ta funkcja nadal ma znaczące ograniczenia:

  • Kompresja jest na dysku, a nie w pamięci
  • Tylko dołączanie (brak obsługi aktualizacji/USUWANIA)
  • Brak odzyskiwania miejsca (na przykład wycofane transakcje mogą nadal zużywać miejsce na dysku)
  • Brak obsługi indeksu, skanowania indeksów ani skanowania indeksów map bitowych
  • Brak tidscans
  • Brak przykładowych skanów
  • Brak obsługi TOAST (duże wartości obsługiwane w tekście)
  • Brak obsługi instrukcji ON CONFLICT (z wyjątkiem akcji NIE RÓB NIC bez określonego celu).
  • Brak obsługi blokad krotki (SELECT ... W OBSZARZE UDOSTĘPNIJ WYBIERZ POZYCJĘ ... DLA AKTUALIZACJI)
  • Brak obsługi poziomu izolacji możliwej do serializacji
  • Obsługa tylko serwera PostgreSQL w wersji 12 lub nowszej
  • Brak obsługi kluczy obcych, unikatowych ograniczeń ani ograniczeń wykluczeń
  • Brak obsługi dekodowania logicznego
  • Brak obsługi skanowania równoległego wewnątrz węzła
  • Brak obsługi after ... DLA KAŻDEGO WYZWALACZA WIERSZA
  • Brak tabel kolumnowych UNLOGGED
  • Brak tabel kolumn tymczasowych

Następne kroki