Ao processar grandes volumes de dados (por exemplo, na escala de bilhões de linhas), a atualização de uma materialized view do PostgreSQL torna-se extremamente lenta. Dados desatualizados prejudicam a eficiência de análises de BI e relatórios. O recurso In-Memory Column Index (IMCI) do PolarDB for PostgreSQL reduz significativamente o tempo de atualização da materialized view , melhorando a atualidade dos dados e agilizando análises e relatórios de BI.
Visão geral da solução
O IMCI é o mecanismo de aceleração analítica fornecido pelo PolarDB for PostgreSQL. Ele cria um índice columnstore sobre uma tabela row-store e mantém automaticamente os dados row-store e o índice columnstore sincronizados. Ao executar agregações complexas ou consultas com join, o banco de dados calcula o resultado usando o índice columnstore, oferecendo um desempenho muito superior ao de uma varredura tradicional em row-store.
A ideia central desta solução é criar um índice columnstore nas tabelas base que sustentam uma materialized view, de modo que tanto a criação inicial quanto as atualizações subsequentes da view sejam aceleradas.
Pré-requisitos
-
Versões do cluster:
PostgreSQL 14 (versão secundária do mecanismo 2.0.14.10.20.0 ou posterior)
PostgreSQL 15 (versão secundária do mecanismo 2.0.15.15.7.0 ou posterior)
PostgreSQL 16 (versão secundária do mecanismo 2.0.16.8.3.0 ou posterior)
PostgreSQL 17 (versão secundária do mecanismo 2.0.17.7.5.0 ou posterior)
NotaVocê pode visualizar a versão secundária do mecanismo no console ou executando a instrução
SHOW polardb_version;. Se a versão secundária do mecanismo não atender aos requisitos, atualize a versão secundária do mecanismo. -
Defina o parâmetro
wal_levelcomological. Essa configuração adiciona as informações necessárias para decodificação lógica ao write-ahead logging (WAL).NotaÉ possível definir o parâmetro wal_level no console. A modificação desse parâmetro reinicia o cluster. Planeje suas operações comerciais adequadamente e proceda com cautela.
A tabela source deve ter uma chave primária, e a coluna da chave primária deve ser incluída ao criar o índice columnstore. Recomenda-se usar o tipo de dados
SERIALouBIGSERIALpara a chave primária, pois isso melhora significativamente a eficiência da sincronização de dados.Crie apenas um índice columnstore por tabela.
Observações de uso
Cada tabela pode ter apenas um índice columnstore.
Índices columnstore não podem ser modificados. Para adicionar colunas a um índice columnstore, recrie o índice.
Preparação
Prepare o ambiente
Um cluster PolarDB for PostgreSQL elegível.
-
Ative o recurso de índice columnstore.
O método para ativar o IMCI varia conforme a versão secundária do mecanismo do seu cluster PolarDB for PostgreSQL:
-
No banco de dados alvo (banco de dados de negócios), instale a extensão pg_hint_plan. Essa extensão permite usar hints especiais no estilo de comentários para ajustar planos de consulta já escolhidos.
CREATE EXTENSION pg_hint_plan; -
No banco de dados de sistema
postgres, instale a extensão pg_cron (tarefas agendadas). Essa extensão executa tarefas automaticamente em um horário ou intervalo especificado.-
Mude para o banco de dados.
\c postgres; -
Instale a extensão.
CREATE EXTENSION pg_cron;
-
Prepare os dados
No banco de dados alvo (banco de dados de negócios), crie a tabela customers e a tabela orders, crie índices columnstore nelas e insira dados de teste.
-
Mude para o banco de dados alvo (banco de dados de negócios). Este exemplo usa
testdb.\c testdb; -
Crie as tabelas e insira os dados.
-- Create the customers table and its columnstore index. CREATE TABLE customers ( customer_id SERIAL PRIMARY KEY, customer_name VARCHAR(100), email VARCHAR(100) ); CREATE INDEX idx_customers_csi ON customers USING csi; -- Create the orders table and its columnstore index. CREATE TABLE orders ( order_id SERIAL PRIMARY KEY, order_date DATE, amount DECIMAL(10, 2), customer_id INT REFERENCES customers(customer_id) ); CREATE INDEX idx_orders_csi ON orders USING csi; -- Insert data into the customers table. INSERT INTO customers (customer_name, email) VALUES ('Alice', 'alice@example.com'), ('Bob', 'bob@example.com'), ('Charlie', 'charlie@example.com'); -- Insert data into the orders table. INSERT INTO orders (order_date, amount, customer_id) VALUES ('2025-06-01', 200.00, 1), ('2025-06-02', 150.00, 2), ('2025-06-03', 300.00, 1), ('2025-06-04', 100.00, 3);
Crie a materialized view
Ao criar a materialized view, use hints para forçar o otimizador de consultas a calcular a view por meio do índice columnstore.
/*+ SET(polar_csi.enable_query on) SET(polar_csi.cost_threshold 0) SET(polar_csi.max_parallel_workers 6) SET(polar_csi.memory_limit 10240) */CREATE MATERIALIZED VIEW mv_customer_orders AS
SELECT
c.customer_name AS customer_name,
o.order_date AS order_date,
o.amount AS amount
FROM
orders o
JOIN
customers c ON o.customer_id = c.customer_id;
Parâmetros de hint
|
Parâmetro |
Descrição |
|
|
Permite que a consulta use o índice columnstore. |
|
|
Força o otimizador a usar o índice columnstore definindo o limiar de custo como 0. |
|
|
Define o grau de paralelismo para computação columnstore. Recomenda-se um valor que não exceda o número de núcleos de CPU no nó. |
|
|
Define a memória disponível para computação, em MB. |
O parâmetro polar_csi.max_parallel_workers era anteriormente chamado polar_csi.exec_parallel em versões anteriores do kernel. Para versões do kernel que não suportam polar_csi.max_parallel_workers, use polar_csi.exec_parallel em seu lugar.
-
PostgreSQL 14:
Use
polar_csi.exec_parallelnas versões 2.0.14.20.42.0 e anteriores.Use
polar_csi.max_parallel_workersnas versões 2.0.14.20.43.0 e posteriores.
-
PostgreSQL 16:
Use
polar_csi.exec_parallelnas versões 2.0.16.11.15.0 e anteriores.Use
polar_csi.max_parallel_workersnas versões 2.0.16.13.16.0 e posteriores.
Atualize a materialized view
Crie a função de atualização
O processo de atualização é encapsulado em uma função. Recomendamos a função a seguir porque ela substitui a nova view com segurança, preservando seus índices e propriedade.
A função a seguir é fornecida como referência. Ela garante uma substituição segura da view, mas você ainda deve testá-la minuciosamente em seu próprio ambiente antes de usá-la em produção.
-- view_name is the name of the materialized view. schema_name is the schema in which the view resides (defaults to current_schema). new_owner is the owner assigned to the newly created view.
CREATE OR REPLACE FUNCTION refresh_materialized_view_safely_using_csi(
view_name TEXT,
schema_name TEXT DEFAULT NULL,
new_owner TEXT DEFAULT NULL
)
RETURNS BOOL
LANGUAGE plpgsql
AS $$
DECLARE
view_definition TEXT;
new_view_name TEXT;
old_view_name TEXT;
index_record RECORD;
index_creation_sql TEXT;
explain_result TEXT;
target_schema TEXT;
qualified_old_name TEXT;
qualified_new_name TEXT;
current_owner TEXT;
grant_record RECORD;
BEGIN
-- Determine the target schema (use the input parameter or the current schema).
IF schema_name IS NULL THEN
target_schema := current_schema();
ELSE
target_schema := schema_name;
END IF;
-- Construct fully qualified table names.
qualified_old_name := format('%I.%I', target_schema, view_name);
qualified_new_name := format('%I.%I', target_schema, view_name || '_new');
RAISE NOTICE 'Operating in schema: %', target_schema;
-- Verify that the materialized view exists.
IF NOT EXISTS (
SELECT 1 FROM pg_matviews
WHERE matviewname = view_name
AND schemaname = target_schema
) THEN
RAISE EXCEPTION 'Materialized view "%" does not exist in schema "%"', view_name, target_schema;
END IF;
-- Retrieve the definition and current owner of the materialized view.
SELECT m.definition, p.rolname INTO view_definition, current_owner
FROM pg_matviews m
JOIN pg_class c ON m.matviewname = c.relname AND m.schemaname = target_schema
JOIN pg_roles p ON c.relowner = p.oid
WHERE m.matviewname = view_name
AND m.schemaname = target_schema;
IF view_definition IS NULL THEN
RAISE EXCEPTION 'Failed to retrieve definition for materialized view "%"', view_name;
END IF;
-- Set the names for the old and new views.
old_view_name := view_name;
new_view_name := view_name || '_new';
-- IMCI performance parameters.
SET LOCAL polar_csi.cost_threshold = 0;
-- Print the query plan.
RAISE NOTICE 'Query plan for materialized view refresh:';
FOR explain_result IN EXECUTE format('/*+ SET(polar_csi.enable_query on) */ EXPLAIN CREATE MATERIALIZED VIEW %s AS %s', qualified_new_name, view_definition) LOOP
RAISE NOTICE '%', explain_result;
END LOOP;
BEGIN
-- Create the new materialized view.
EXECUTE format('/*+ SET(polar_csi.enable_query on) */ CREATE MATERIALIZED VIEW %s AS %s', qualified_new_name, view_definition);
-- If a new owner is specified, set the owner.
IF new_owner IS NOT NULL THEN
-- Verify that the user exists.
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = new_owner) THEN
RAISE EXCEPTION 'Role "%" does not exist', new_owner;
END IF;
EXECUTE format('ALTER MATERIALIZED VIEW %s OWNER TO %I', qualified_new_name, new_owner);
RAISE NOTICE 'Changed owner from "%" to "%"', current_owner, new_owner;
END IF;
-- Copy all indexes from the old view to the new view.
FOR index_record IN
SELECT indexname, indexdef
FROM pg_indexes
WHERE tablename = old_view_name
AND schemaname = target_schema
LOOP
-- Replace the old view name with the new view name.
index_creation_sql := regexp_replace(
index_record.indexdef,
' ON ' || target_schema || '.' || old_view_name || ' ',
' ON ' || target_schema || '.' || new_view_name || ' ',
'i'
);
-- Handle the special case of UNIQUE indexes.
index_creation_sql := regexp_replace(
index_creation_sql,
'INDEX ' || index_record.indexname || ' ON',
'INDEX ' || index_record.indexname || '_new ON',
'i'
);
RAISE NOTICE 'Creating index: %', index_creation_sql;
EXECUTE index_creation_sql;
END LOOP;
-- Copy view permissions.
RAISE NOTICE 'Restoring permissions to new view %.%', target_schema, new_view_name;
FOR grant_record IN
SELECT
(acl).grantee::regrole::text AS grantee,
(acl).privilege_type
FROM
pg_class c
JOIN pg_namespace n ON c.relnamespace = n.oid
CROSS JOIN aclexplode(c.relacl) AS acl
WHERE
n.nspname = target_schema
AND c.relname = old_view_name
LOOP
CONTINUE WHEN grant_record.grantee IS NULL;
-- RAISE NOTICE 'Granting % ON %.% TO %',
-- grant_record.privilege_type, target_schema, new_view_name, grant_record.grantee;
EXECUTE format(
'GRANT %s ON %I.%I TO %s',
grant_record.privilege_type,
target_schema,
new_view_name,
quote_ident(grant_record.grantee)
);
END LOOP;
-- Drop the old materialized view.
EXECUTE format('DROP MATERIALIZED VIEW %s', qualified_old_name);
-- Rename the new materialized view to the original name.
EXECUTE format('ALTER MATERIALIZED VIEW %s RENAME TO %I', qualified_new_name, old_view_name);
-- Rename the indexes (remove the _new suffix).
FOR index_record IN
SELECT indexname
FROM pg_indexes
WHERE tablename = old_view_name
AND schemaname = target_schema
LOOP
IF position('_new' in index_record.indexname) > 0 THEN
EXECUTE format(
'ALTER INDEX %I.%I RENAME TO %I',
target_schema,
index_record.indexname,
replace(index_record.indexname, '_new', '')
);
END IF;
END LOOP;
RETURN TRUE;
EXCEPTION
WHEN OTHERS THEN
RAISE EXCEPTION 'Failed to refresh materialized view: %', SQLERRM;
RETURN FALSE;
END;
END;
$$;
Parâmetros
|
Parâmetro |
Descrição |
|
|
Nome da função. Pode ser alterado para atender às necessidades do seu negócio. |
|
|
Nome da materialized view. |
|
|
Schema onde a materialized view reside. O padrão é current_schema. |
|
|
Novo proprietário da materialized view após sua recriação. |
|
|
Permite que a consulta use o índice columnstore. |
|
|
Força o otimizador a usar o índice columnstore definindo o limiar de custo como 0. |
|
|
Define o grau de paralelismo para computação columnstore. Recomenda-se um valor que não exceda o número de núcleos de CPU no nó. |
|
|
Define a memória disponível para computação, em MB. |
O parâmetro polar_csi.max_parallel_workers era anteriormente chamado polar_csi.exec_parallel em versões anteriores do kernel. Para versões do kernel que não suportam polar_csi.max_parallel_workers, use polar_csi.exec_parallel em seu lugar.
-
PostgreSQL 14:
Use
polar_csi.exec_parallelnas versões 2.0.14.20.42.0 e anteriores.Use
polar_csi.max_parallel_workersnas versões 2.0.14.20.43.0 e posteriores.
-
PostgreSQL 16:
Use
polar_csi.exec_parallelnas versões 2.0.16.11.15.0 e anteriores.Use
polar_csi.max_parallel_workersnas versões 2.0.16.13.16.0 e posteriores.
Tarefas só podem ser criadas no banco de dados de sistema
postgres, e apenas uma conta privilegiada pode criá-las.Especifique o proprietário da materialized view recriada para que usuários regulares ainda possam ler a view após sua recriação por uma conta privilegiada. Caso precise ajustar outras configurações relacionadas a permissões, modifique a função de atualização definida acima.
Ao usar o
pg_cronpara agendar atualizações, certifique-se de que o intervalo da tarefa seja estritamente maior que a duração real da atualização; caso contrário, as tarefas se acumularão. Como uma atualização grava dados, ela geralmente é muito mais lenta do que um simplesSELECT.-
Mude para o banco de dados.
\c postgres; -
Crie a tarefa agendada. Substitua os parâmetros relevantes pelos seus valores reais.
NotaSubstitua
<mv_name>pelo nome real da materialized view.Substitua
<database_name>pelo nome real do banco de dados de negócios.Substitua
<schema_name>pelo nome real do schema.Substitua
<user_name>pelo nome de usuário real.
Sintaxe
SELECT cron.schedule_in_database( 'refresh_mv_customer_orders', -- Task name (customizable) '*/5 * * * *', -- Cron expression, for example, run every 5 minutes $$SELECT refresh_materialized_view_safely_using_csi('<mv_name>', '<schema_name>', '<user_name>')$$, '<database_name>' );Exemplo
SELECT cron.schedule_in_database( 'refresh_mv_customer_orders', -- Task name (customizable) '*/5 * * * *', -- Cron expression, for example, run every 5 minutes $$SELECT refresh_materialized_view_safely_using_csi('mv_customer_orders', 'public', 'polarpg')$$, 'testdb' );
Execute a atualização
Refresh manually
Quando seu negócio exigir, chame a função manualmente para acionar uma atualização. Substitua o nome na chamada pelo nome real da sua materialized view. Este exemplo usa mv_customer_orders.
SELECT refresh_materialized_view_safely_using_csi('mv_customer_orders');
Schedule refreshes with pg_cron
Create a scheduled task
Mude para o banco de dados de sistema postgres e use o pg_cron para especificar o nome da tarefa, o intervalo e a operação. Para mais informações, consulte a extensão pg_cron (tarefas agendadas).
View scheduled tasks
Execute a seguinte instrução SQL para visualizar as tarefas agendadas configuradas.
SELECT * FROM cron.job;
Saída esperada:
jobid | schedule | command | nodename | nodeport | database | username | active | jobname
-------+-------------+----------------------------------------------------------------------------------------------+----------+----------+----------+----------+--------+----------------------------
1 | */5 * * * * | SELECT refresh_materialized_view_safely_using_csi('mv_customer_orders', 'public', 'polarpg') | /data/. | 3000 | testdb | polarpg | t | refresh_mv_customer_orders
(1 row)
Delete a scheduled task
Se não precisar mais de atualizações agendadas, execute a seguinte instrução SQL para excluir a tarefa agendada.
SELECT cron.unschedule('refresh_my_materialized_view');
View task execution details
Execute a seguinte instrução SQL para visualizar os detalhes de execução das tarefas agendadas.
SELECT * FROM cron.job_run_details;
Saída esperada:
jobid | runid | job_pid | database | username | command | status | return_message | start_time | end_time
-------+-------+---------+----------+----------+----------------------------------------------------------------------------------------------+-----------+----------------+-------------------------------+-------------------------------
1 | 1 | 76537 | testdb | polarpg | SELECT refresh_materialized_view_safely_using_csi('mv_customer_orders', 'public', 'polarpg') | succeeded | 1 row | 2025-08-27 08:35:00.007231+00 | 2025-08-27 08:35:00.024946+00
(1 rows)
Consulte a materialized view
Execute a seguinte instrução SQL para consultar a materialized view. Substitua o nome pelo nome real da sua materialized view. Este exemplo usa mv_customer_orders.
Antes de executar a instrução, mude para o seu banco de dados de negócios real.
SELECT customer_name, COUNT(*) FROM mv_customer_orders GROUP BY customer_name;