Examinar antipadrões para consultas distribuídas

Concluído

Como o Azure Cosmos DB para PostgreSQL fornece funcionalidade distribuída estendendo o PostgreSQL, ele é compatível com construções do PostgreSQL. Os usuários podem usar a maioria das ferramentas e recursos do ecossistema PostgreSQL rico e extensível para tabelas distribuídas. No entanto, alguns recursos SQL não são suportados para consultas que combinam informações de vários nós e, embora haja algumas soluções alternativas sugeridas, a maioria dessas consultas é considerada anti-padrões. Antipadrões são consultas que podem retornar os dados solicitados, mas têm consequências indesejadas, como desempenho insatisfatório, movimentação desnecessária de dados e outros comportamentos indesejados. Você deve considerar se um banco de dados distribuído é apropriado para sua situação se você estiver tentando várias soluções alternativas ou precisando escrever consultas seguindo antipadrões. Você pode reavaliar a seleção da coluna de distribuição em cada uma das tabelas.

Junta-se sem especificar uma coluna de distribuição

Usando CTEs para unir tabelas em colunas que não são de distribuição

Quando uma consulta SQL não é suportada, uma maneira de contornar ela é usando expressões de tabela comuns (CTEs), que usam o que é conhecido como execução pull-push.

Suponha que a payment_merchants tabela ainda era uma tabela distribuída. A execução da seguinte consulta para associá-la à payment_events tabela falharia:

SELECT name, event_id
FROM payment_events AS e
LEFT JOIN payment_merchants m ON e.merchant_id = m.merchant_id;

A consulta resulta em um erro:

ERROR: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns

Para concluir a consulta, você pode usar um CTE para contornar as limitações de junção:

WITH merchants AS (SELECT * FROM payment_merchants)
SELECT name, event_id FROM payment_events LEFT JOIN merchants USING (merchant_id);

O CTE permite que você execute com sucesso a junção entre tabelas não colocalizadas, mas a que custo? A execução de um EXPLAIN na consulta revela o custo de execução de uma consulta como este exemplo.

EXPLAIN
    WITH merchants AS (SELECT * FROM payment_merchants)
    SELECT name, event_id FROM payment_events LEFT JOIN merchants USING (merchant_id);
Custom Scan (Citus Adaptive)  (cost=0.00..0.00 rows=100000 width=40)
   ->  Distributed Subplan 15_1
         ->  Custom Scan (Citus Adaptive)  (cost=0.00..0.00 rows=100000 width=72)
               Task Count: 32
               Tasks Shown: One of 32
               ->  Task
                     Node: host=private-w0.learn-cosmosdb-postgresql.postgres.database.azure.com port=5432 dbname=citus
                     ->  Seq Scan on payment_merchants_102491 payment_merchants  (cost=0.00..34.39 rows=1539 width=72)
   Task Count: 32
   Tasks Shown: One of 32
   ->  Task
         Node: host=private-w0.learn-cosmosdb-postgresql.postgres.database.azure.com port=5432 dbname=citus
         ->  Merge Right Join  (cost=470.48..522.83 rows=4482 width=40)
               Merge Cond: (intermediate_result.merchant_id = payment_events.merchant_id)
               ->  Sort  (cost=59.83..62.33 rows=1000 width=40)
                     Sort Key: intermediate_result.merchant_id
                     ->  Function Scan on read_intermediate_result intermediate_result  (cost=0.00..10.00 rows=1000 width=40)
               ->  Sort  (cost=410.65..421.86 rows=4482 width=16)
                     Sort Key: payment_events.merchant_id
                     ->  Seq Scan on payment_events_102232 payment_events  (cost=0.00..138.82 rows=4482 width=16)

A primeira parte do resultado revela que foi necessário criar um subplano distribuído. Esse subplano significa que o coordenador tem que empurrar a consulta dentro do CTE para os trabalhadores para execução e puxar os resultados de volta. O coordenador enviará os resultados intermediários do CTE para os trabalhadores para serem usados na consulta de junção executada em cada trabalhador. Embora essa técnica funcione, ela é considerada um antipadrão de consulta distribuída devido ao embaralhamento de dados necessário entre os nós para concluir a consulta.

Assim, é melhor adicionar os filtros e limites mais específicos possível à consulta interna ou agregar a tabela. Isso reduz a sobrecarga de rede que essa consulta pode causar. Para obter mais informações sobre essa otimização, consulte Subquery/CTE Network Overhead na documentação do Citus.

Usando tabelas temporárias

Ainda há algumas consultas que não são suportadas , mesmo com o uso da execução push-pull por meio de subconsultas. Um deles é usar conjuntos de agrupamento em uma tabela distribuída.

No aplicativo de pagamento, você criou uma tabela chamada payment_events, distribuída user_id pela coluna. O exemplo abaixo o consulta para encontrar os primeiros eventos para um conjunto pré-selecionado de usuários, agrupados por combinações de tipo de evento e ID do comerciante. Uma maneira conveniente de construir esse tipo de consulta é com conjuntos de agrupamento. No entanto, como mencionado, esse recurso ainda não é suportado em consultas distribuídas:

-- This will not work

SELECT user_id, merchant_id, event_type,
    GROUPING(merchant_id, event_type),
    MIN(created_at)
FROM payment_events
GROUP BY user_id, ROLLUP(merchant_id, event_type);
ERROR:  could not run distributed query with GROUPING
HINT:  Consider using an equality filter on the distributed table's partition column.

Mas há um truque. Você pode extrair as informações relevantes para o coordenador como uma tabela temporária e executar a consulta em uma tabela local.

CREATE TEMP TABLE results AS (
    SELECT user_id, merchant_id, event_type, created_at
    FROM payment_events
);

SELECT user_id, merchant_id, event_type,
    GROUPING(merchant_id, event_type),
    MIN(created_at)
FROM results
GROUP BY user_id, ROLLUP(merchant_id, event_type);

Criar uma tabela temporária no coordenador é um último recurso. É limitado pelo tamanho do disco e CPU do nó.