Share via


在 Azure Cosmos DB for PostgreSQL 中使用分欄資料表壓縮資料

適用於: Azure Cosmos DB for PostgreSQL (由 PostgreSQL 的 Citus 資料庫延伸模組提供)

Azure Cosmos DB for PostgreSQL 支援僅限附加的單欄式表格儲存體,供分析和資料倉儲工作負載使用。 當資料行 (而非資料列) 連續儲存在磁碟上時,資料會變得更容易壓縮,而且查詢可以更快速地要求資料行子集。

建立表格

若要使用分欄儲存體,請在建立資料表時指定 USING columnar

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

Azure Cosmos DB for PostgreSQL 會在插入時,將資料列「分散為帶狀」轉換成單欄式儲存體。 每個等量都會保留一個交易的資料,或 15 萬個資料列,視何者較少。 (分欄資料表的等量大小和其他參數,可以透過 alter_columnar_table_set 函式加以變更。)

例如,下列陳述式會將五個資料列全部放入相同的等量,因為所有值都會插入單一交易中:

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

最好盡可能建立大型的帶狀分散,因為 Azure Cosmos DB for PostgreSQL 會分別壓縮每條帶狀分散的單欄式資料。 我們可以使用 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

輸出顯示 Azure Cosmos DB for PostgreSQL 使用 zstd 壓縮演算法取得了 1.31 倍的資料壓縮。 壓縮率會比較 a) 已插入資料暫存在記憶體中時的大小,與 b) 在資料最終等量中壓縮的資料大小。

由於其測量方式,壓縮率可能與資料表的資料列和分欄儲存體之間的大小差異不符。 真正發現差異的唯一方法是建構包含相同資料的資料列和分欄資料表,並進行比較。

測量壓縮

讓我們建立具有更多資料的新範例,以對壓縮節省進行基準測試。

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

以相同的大型資料集填入這兩個資料表:

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;

針對此資料,您可以在分欄資料表中看到比 8X 更好的壓縮比率。

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

範例

分欄儲存體適用於資料表資料分割。 如需範例,請參閱 Citus Engine 社群文件,使用分欄儲存體進行封存

Gotchas

  • 分欄儲存體會壓縮每個等量。 針對每個交易都會建立等量,因此每個交易插入一個資料列會將單一資料列放入自己的等量。 單一資料列等量的壓縮和效能會比資料列資料表更糟。 一律大量插入至分欄資料表。
  • 如果您弄亂並且分為一堆小型等量,就會停滯。 唯一的修正方式是建立新的分欄資料表,並在一筆交易中從原始資料複製資料:
    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;
    
  • 基本上,不可壓縮的資料可能是問題,不過選取特定資料行時,分欄儲存體仍然很有用。 不需要將其他資料行載入記憶體中。
  • 在混合資料列和資料行分割的分割資料表上,必須仔細設定更新目標。 將其篩選為只叫用資料列分割。
    • 如果作業的目標為特定資料列分割 (例如,UPDATE p2 SET i = i + 1),則會成功;如果目標為指定分欄分割 (例如,UPDATE p1 SET i = i + 1),則會失敗。
    • 如果作業的目標為資料分割資料表,並且有 WHERE 子句會排除所有分欄分割 (例如 UPDATE parent SET i = i + 1 WHERE timestamp = '2020-03-15'),則會成功。
    • 如果作業的目標為資料分割資料表,但是沒有分割索引鍵資料行的篩選,則會失敗。 即使有 WHERE 子句會比對僅限分欄分割中的資料列,仍然不足夠,還必需要篩選資料分割索引鍵。

限制

這項功能仍然有顯著的限制:

  • 壓縮位於磁片上,不在記憶體中
  • 僅限附加 (不支援 UPDATE/DELETE)
  • 無復原空間 (例如,復原的交易可能仍會耗用磁碟空間)
  • 沒有索引支援、索引掃描或點陣圖索引掃描
  • No tidscans
  • 沒有範例掃描
  • 不支援快顯通知 (內嵌) 支援的大型值
  • 不支援 ON CONFLICT 語句 (除了沒有指定目標的 DO NOTHING 動作)。
  • 不支援 Tuple 鎖定 (SELECT ...FOR SHARE、SELECT ...FOR UPDATE)
  • 不支援可序列化的隔離等級
  • 僅支援 PostgreSQL 伺服器版本 12+
  • 不支援外鍵、唯一條件約束或排除條件約束
  • 不支援邏輯解碼
  • 不支援節點內部平行掃描
  • 不支援 AFTER ...FOR EACH ROW 觸發程序
  • 沒有 UNLOGGED 單欄式資料表
  • 沒有暫存單欄式資料表

下一步