Compactar dados com tabelas colunares no Azure Cosmos DB para PostgreSQL
APLICA-SE A: Azure Cosmos DB para PostgreSQL (alimentado pela extensão de banco de dados Citus para PostgreSQL)
O Azure Cosmos DB para PostgreSQL dá suporte ao armazenamento de tabelas colunares somente acréscimo para cargas de trabalho analíticas e de data warehousing. Quando colunas (em vez de linhas) são armazenadas contíguamente no disco, os dados tornam-se mais compressí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 para PostgreSQL converte linhas em armazenamento colunar em "faixas" durante a inserção. Cada faixa contém o valor de dados de uma transação, ou 150000 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 a seguir coloca todas as cinco linhas na mesma faixa, porque todos os valores sã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 fazer listras grandes quando possível, porque o Azure Cosmos DB para PostgreSQL compacta dados colunares separadamente por faixa. Podemos ver fatos sobre nossa tabela colunar, como taxa de compressão, número de listras e linhas médias por listra 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 para PostgreSQL usou o algoritmo de compactação zstd para obter compactação de dados 1,31x. A taxa de compressão compara a) o tamanho dos dados inseridos como foram preparados na memória contra b) o tamanho desses dados compactados em sua eventual faixa.
Devido à forma como é medida, a taxa de compressão pode ou não corresponder à diferença de tamanho entre o armazenamento em linha e colunar de uma tabela. A única maneira de realmente encontrar essa diferença é construir uma tabela de linha e coluna que contenha os mesmos dados e comparar.
Medição da compressão
Vamos criar um novo exemplo com mais dados para avaliar a economia de compressã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 conjunto de dados grande:
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 superior a 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 o particionamento de tabelas. Para obter um exemplo, consulte a documentação da comunidade do Citus Engine, arquivamento com armazenamento colunar.
Gotchas
- Compressas de armazenamento colunar por listra. As listras são criadas por transação, portanto, inserir uma linha por transação colocará linhas únicas em suas próprias listras. A compactação e o desempenho de listras de linha única serão piores do que uma tabela de linhas. Insira sempre em massa numa tabela colunar.
- Se você errar e colunar um monte de listras minúsculas, você está preso.
A única correção é criar uma nova tabela colunar e copiar dados do 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 compressíveis podem ser um problema, embora o armazenamento colunar ainda seja útil ao selecionar colunas específicas. Ele não precisa carregar as outras colunas na memória.
- Em uma tabela particionada com uma mistura de partições de linha e coluna, as atualizações devem ser cuidadosamente direcionadas. Filtre-os para atingir apenas as partições de linha.
- Se a operação for direcionada a uma partição de linha específica (por exemplo,
UPDATE p2 SET i = i + 1
), ela será bem-sucedida; se direcionada a uma partição colunar especificada (por exemplo,UPDATE p1 SET i = i + 1
), ela falhará. - Se a operação for direcionada à tabela particionada e tiver uma cláusula WHERE que exclua todas as partições colunares (por exemplo
UPDATE parent SET i = i + 1 WHERE timestamp = '2020-03-15'
), ela terá êxito. - Se a operação for direcionada para a tabela particionada, mas não filtrar nas colunas de chave de partição, ela falhará. Mesmo que existam cláusulas WHERE que correspondam a linhas apenas em partições colunares, isso não é suficiente - a chave de partição também deve ser filtrada.
- Se a operação for direcionada a uma partição de linha específica (por exemplo,
Limitações
Este recurso ainda tem limitações significativas:
- A compactação está no disco, não na memória
- Somente apêndice (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, varreduras de índice ou varreduras de índice de bitmap
- Sem tidscans
- Sem varreduras de amostra
- Sem suporte a TOAST (grandes valores suportados em linha)
- Não há suporte para instruções ON CONFLICT (exceto ações DO NOTHING sem destino especificado).
- Sem suporte para bloqueios de tupla (SELECT ... PARA COMPARTILHAR, SELECIONE ... PARA ATUALIZAÇÃO)
- Sem suporte para o nível de isolamento serializável
- Suporte apenas para versões de servidor PostgreSQL 12+
- Sem suporte para chaves estrangeiras, restrições exclusivas ou restrições de exclusão
- Sem suporte para decodificação lógica
- Sem suporte para verificações paralelas intra-nó
- Sem suporte para AFTER ... PARA CADA LINHA gatilhos
- Sem tabelas colunares não registadas
- Sem tabelas colunares TEMPORÁRIAS
Próximos passos
- Veja um exemplo de armazenamento colunar em um tutorial de série temporal do Citus (link externo).