Komprimera data med kolumntabeller i Azure Cosmos DB for PostgreSQL

GÄLLER FÖR: Azure Cosmos DB for PostgreSQL (drivs av Citus-databastillägget till PostgreSQL)

Azure Cosmos DB for PostgreSQL stöder tilläggsbaserad kolumnbaserad tabelllagring för analys- och datalagerarbetsbelastningar. När kolumner (i stället för rader) lagras sammanhängande på disken blir data mer komprimerade och frågor kan begära en delmängd kolumner snabbare.

Skapa en tabell

Om du vill använda columnar storage anger du USING columnar när du skapar en tabell:

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

Azure Cosmos DB for PostgreSQL konverterar rader till kolumnlagring i "stripes" under infogning. Varje rand innehåller en transaktions värde av data, eller 150000 rader, beroende på vilket som är mindre. (Randstorleken och andra parametrar i en kolumntabell kan ändras med funktionen alter_columnar_table_set .)

Följande instruktion placerar till exempel alla fem rader i samma rand, eftersom alla värden infogas i en enda transaktion:

-- 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}');

Det är bäst att göra stora ränder när det är möjligt, eftersom Azure Cosmos DB for PostgreSQL komprimerar kolumndata separat per rand. Vi kan se fakta om vår kolumntabell som komprimeringshastighet, antal ränder och genomsnittliga rader per rand med hjälp VACUUM VERBOSEav :

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

Utdata visar att Azure Cosmos DB for PostgreSQL använde zstd-komprimeringsalgoritmen för att hämta 1,31 x datakomprimering. Komprimeringshastigheten jämför a) storleken på infogade data som de mellanlagrades i minnet mot b) storleken på dessa data som komprimerats i dess slutliga rand.

På grund av hur den mäts kanske komprimeringshastigheten matchar storleksskillnaden mellan rad- och kolumnlagring för en tabell. Det enda sättet att verkligen hitta den skillnaden är att skapa en rad- och kolumntabell som innehåller samma data och jämföra.

Mäta komprimering

Nu ska vi skapa ett nytt exempel med mer data för att jämföra komprimeringsbesparingarna.

-- 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;

Fyll i båda tabellerna med samma stora datauppsättning:

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;

För dessa data kan du se ett komprimeringsförhållande på bättre än 8X i kolumntabellen.

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

Exempel

Columnar Storage fungerar bra med tabellpartitionering. Ett exempel finns i Community-dokumentationen för Citus Engine, arkivering med columnar storage.

Gotchas

  • Columnar Storage komprimerar per rand. Ränder skapas per transaktion, så om du infogar en rad per transaktion placeras enskilda rader i sina egna ränder. Komprimering och prestanda för enstaka radränder blir sämre än en radtabell. Infoga alltid massinfogning i en kolumntabell.
  • Om du trasslar till det och kolumnariserar en massa små ränder, är du fast. Den enda korrigeringen är att skapa en ny kolumntabell och kopiera data från originalet i en transaktion:
    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;
    
  • I grunden kan icke-komprimerbara data vara ett problem, även om kolumnlagring fortfarande är användbart när du väljer specifika kolumner. Den behöver inte läsa in de andra kolumnerna i minnet.
  • I en partitionerad tabell med en blandning av rad- och kolumnpartitioner måste uppdateringar vara noggrant riktade. Filtrera dem så att de bara träffar radpartitionerna.
    • Om åtgärden är riktad mot en specifik radpartition (till exempel UPDATE p2 SET i = i + 1), kommer den att lyckas. Om den riktas mot en angiven kolumnpartition (till exempel UPDATE p1 SET i = i + 1), misslyckas den.
    • Om åtgärden är riktad mot den partitionerade tabellen och har en WHERE-sats som exkluderar alla kolumnpartitioner (till exempel UPDATE parent SET i = i + 1 WHERE timestamp = '2020-03-15'), kommer den att lyckas.
    • Om åtgärden är riktad mot den partitionerade tabellen, men inte filtrerar på partitionsnyckelkolumnerna, misslyckas den. Även om det finns WHERE-satser som endast matchar rader i kolumnpartitioner räcker det inte – partitionsnyckeln måste också filtreras.

Begränsningar

Den här funktionen har fortfarande betydande begränsningar:

  • Komprimering finns på disken, inte i minnet
  • Endast tillägg (inget uppdaterings-/DELETE-stöd)
  • Ingen utrymmesåtertagning (till exempel kan återställda transaktioner fortfarande förbruka diskutrymme)
  • Inget indexstöd, indexgenomsökningar eller bitmappsindexgenomsökningar
  • Inga tidskanningar
  • Inga exempelgenomsökningar
  • Inget TOAST-stöd (stora värden stöds infogade)
  • Inget stöd för ON CONFLICT-instruktioner (förutom DO NOTHING-åtgärder utan angivet mål).
  • Inget stöd för tuppeln lås (SELECT ... FÖR DELA VÄLJER DU ... FÖR UPPDATERING)
  • Inget stöd för serialiserbar isoleringsnivå
  • Stöd för PostgreSQL-serverversioner endast 12+
  • Inget stöd för sekundärnycklar, unika begränsningar eller undantagsbegränsningar
  • Inget stöd för logisk avkodning
  • Inget stöd för parallella genomsökningar inom noden
  • Inget stöd för AFTER ... FÖR VARJE RADutlösare
  • Inga ologgade kolumntabeller
  • Inga temporära kolumntabeller

Nästa steg