Esaminare gli anti-pattern per le query distribuite

Completato

Poiché Azure Cosmos DB per PostgreSQL offre funzionalità distribuite estendendo PostgreSQL, è compatibile con i costrutti PostgreSQL. Gli utenti possono usare la maggior parte degli strumenti e delle funzionalità nell'ecosistema PostgreSQL avanzato ed estendibile per le tabelle distribuite. Tuttavia, alcune funzionalità di SQL non sono supportate per le query che combinano informazioni da più nodi e, mentre esistono alcune soluzioni alternative suggerite, la maggior parte di queste query è considerata anti-pattern. Gli anti-pattern sono query che potrebbero restituire i dati richiesti, ma hanno conseguenze impreviste, ad esempio prestazioni scarse, spostamento dei dati non necessario e altri comportamenti indesiderati. È consigliabile valutare se un database distribuito è appropriato per la situazione se si tenta di provare numerose soluzioni alternative o se è necessario scrivere query seguendo i modelli anti-pattern. È possibile rivalutare la selezione delle colonne di distribuzione in ognuna delle tabelle.

Join senza specificare una colonna di distribuzione

Uso delle CTE per unire tabelle in colonne non di distribuzione

Quando una query SQL non è supportata, un modo per risolvere il problema consiste nell'usare espressioni di tabella comuni (CTE), che usano ciò che viene definito esecuzione pull-push.

Si supponga che la payment_merchants tabella fosse ancora una tabella distribuita. L'esecuzione della query seguente per un join con la payment_events tabella avrà esito negativo:

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

La query genera un errore:

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

Per completare la query, è possibile usare un CTE per aggirare le limitazioni di join:

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

Il CTE consente di eseguire correttamente il join tra tabelle non con percorso condiviso, ma a quale costo? L'esecuzione di un oggetto EXPLAIN nella query rivela il costo dell'esecuzione di una query come questo esempio.

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)

La prima parte dell'output rivela che era necessario creare un sottopiano distribuito. Questo sottopiano indica che il coordinatore deve eseguire il push della query all'interno del CTE ai ruoli di lavoro per l'esecuzione ed eseguire il pull dei risultati. Il coordinatore invierà i risultati intermedi dal CTE verso il basso ai ruoli di lavoro da usare nella query di join eseguita in ogni ruolo di lavoro. Anche se questa tecnica funziona, è considerata un anti-modello di query distribuito a causa della casualità dei dati necessaria tra i nodi per completare la query.

Pertanto, è consigliabile aggiungere i filtri e i limiti più specifici alla query interna il più possibile o aggregare la tabella. Ciò riduce il sovraccarico di rete che può causare una query di questo tipo. Per altre informazioni su questa ottimizzazione, vedere Subquery/CTE Network Overhead nella documentazione di Citus.

Uso di tabelle temporanee

Esistono ancora alcune query non supportate anche con l'uso dell'esecuzione push-pull tramite sottoquery. Uno di essi usa i set di raggruppamento in una tabella distribuita.

Nell'applicazione di pagamento è stata creata una tabella denominata payment_events, distribuita dalla user_id colonna . L'esempio seguente esegue una query per trovare i primi eventi per un set pre-selezionato di utenti, raggruppati per combinazioni di tipo di evento e ID commerciante. Un modo pratico per costruire questo tipo di query consiste nel raggruppare i set. Tuttavia, come accennato, questa funzionalità non è ancora supportata nelle query distribuite:

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

C'è però un trucco. È possibile eseguire il pull delle informazioni pertinenti al coordinatore come tabella temporanea ed eseguire la query su una tabella locale.

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

La creazione di una tabella temporanea nel coordinatore è un'ultima risorsa. È limitato dalle dimensioni e dalla CPU del disco del nodo.