Compactar dados com tabelas colunares no Azure Cosmos DB for PostgreSQL
APLICA-SE AO: Azure Cosmos DB for PostgreSQL (da plataforma da extensão de dados Citus para PostgreSQL)
O Azure Cosmos DB for PostgreSQL dá suporte apenas ao armazenamento de tabelas colunar com acréscimo para cargas de trabalho analíticas e de armazenamento de dados. Quando colunas (em vez de linhas) são armazenadas contiguamente no disco, os dados ficam mais compactáveis e as consultas podem solicitar um subconjunto de colunas mais rapidamente.
Criar uma tabela
Para usar o armazenamento colunar, especifique USING columnar
ao criar uma tabela:
CREATE TABLE contestant (
handle TEXT,
birthdate DATE,
rating INT,
percentile FLOAT,
country CHAR(3),
achievements TEXT[]
) USING columnar;
O Azure Cosmos DB for PostgreSQL converte linhas em armazenamento colunar em "faixas" durante a inserção. Cada faixa mantém dados equivalentes aos de uma transação ou 150.000 linhas, o que for menor. (O tamanho da faixa e outros parâmetros de uma tabela colunar podem ser alterados com a função alter_columnar_table_set.)
Por exemplo, a instrução abaixo coloca todas as cinco linhas na mesma faixa porque todos os valores estão inseridos em uma única transação:
-- 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}');
É melhor criar faixas mais largas sempre que possível, pois o Azure Cosmos DB for PostgreSQL comprime dados colunares separadamente por cada faixa. Podemos ver fatos sobre nossa tabela colunar, como taxa de compactação, número de faixas e média de linhas por faixa, usando 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
A saída mostra que o Azure Cosmos DB for PostgreSQL usou o algoritmo de compactação zstd para alcançar a compactação de dados de 1,31x. A taxa de compactação compara a) o tamanho dos dados inseridos como estavam preparados na memória com b) o tamanho dos dados compactados na sua faixa.
Considerando como é medida, a taxa de compactação pode ou não corresponder à diferença de tamanho entre o armazenamento de linha e colunar de uma tabela. A única maneira de realmente descobrir essa diferença é criar uma tabela colunar e de linha que contenha os mesmos dados e comparar.
Medir a compactação
Vamos criar um exemplo com mais dados para fazer o parâmetro de comparação da economia de compactação.
-- 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;
Preencha ambas as tabelas com o mesmo grande conjuntos de dados:
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;
Para esses dados, você pode ver uma taxa de compactação melhor que 8X na tabela colunar.
SELECT pg_total_relation_size('perf_row')::numeric/
pg_total_relation_size('perf_columnar') AS compression_ratio;
compression_ratio
--------------------
8.0196135873627944
(1 row)
Exemplo
O armazenamento colunar funciona bem com particionamento de tabela. Para ver um exemplo, confira a documentação da comunidade da Citus Engine, Como arquivar com armazenamento colunar.
Armadilhas
- O armazenamento colunar compacta por faixa. As faixas são criadas por transação, ou seja, a inserção de uma linha por transação colocará linhas individuais em suas próprias faixas. A compactação e o desempenho de faixas de uma única linha serão piores do que uma tabela de linhas. Insira sempre em massa em uma tabela colunar.
- Se cometer um erro e colunarizar várias linhas pequenas, você não poderá desfazê-lo.
O único remédio é criar outra tabela colunar e copiar dados da original em uma transação:
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;
- Fundamentalmente, dados não compactáveis podem ser um problema, embora o armazenamento colunar ainda possa ser útil na escolha de colunas específicas. Não é necessário carregar as outras colunas na memória.
- Em uma tabela particionada com uma mistura de partições de coluna e linha, as atualizações precisam ser direcionadas com cuidado. Filtre-as para alcançar apenas as partições de linha.
- Se a operação for direcionada para uma partição de linha específica (por exemplo,
UPDATE p2 SET i = i + 1
), ela será bem-sucedida; se for direcionada a uma partição colunar específica (por exemplo,UPDATE p1 SET i = i + 1
), ela falhará. - Se a operação for direcionada à tabela particionada e houver uma cláusula WHERE que exclui todas as partições colunares (por exemplo,
UPDATE parent SET i = i + 1 WHERE timestamp = '2020-03-15'
), ela será bem-sucedida. - Se a operação for direcionada à tabela particionada, mas não filtrar as colunas de chave da partição, ela falhará. Mesmo que haja cláusulas WHERE que correspondam a colunas apenas em partições colunares, não é suficiente; a chave de partição também precisa ser filtrada.
- Se a operação for direcionada para uma partição de linha específica (por exemplo,
Limitações
Esse recurso ainda tem limitações significativas:
- A compactação está no disco, não na memória
- Somente acréscimo (sem suporte a UPDATE/DELETE)
- Sem recuperação de espaço (por exemplo, transações revertidas ainda podem consumir espaço em disco)
- Sem suporte a índice, exames de índice ou exames de índice de bitmap
- Sem tidscans
- Nenhum exemplo de exame
- Não há suporte ao sistema (valores grandes com suporte embutido)
- Não há suporte para instruções ON CONFLICT (exceto ações DO NOTHING sem nenhum destino especificado).
- Não há suporte para bloqueios de tupla (SELECT ... FOR SHARE, SELECT ... FOR UPDATE)
- Não há suporte para o nível de isolamento serializável
- Suporte para o servidor PostgreSQL, somente versões 12+
- Não há suporte para chaves estrangeiras, restrições exclusivas ou restrições de exclusão
- Não há suporte para decodificação lógica
- Não há suporte para exames paralelos dentro do nó
- Não há suporte para gatilhos AFTER ... FOR EACH ROW
- Nenhuma tabela de coluna UNLOGGED
- Nenhuma tabela de coluna TEMPORARY
Próximas etapas
- Confira um exemplo de armazenamento colunar em um tutorial de série temporal da Citus (link externo).