Le traduzioni sono generate tramite traduzione automatica. In caso di conflitto tra il contenuto di una traduzione e la versione originale in Inglese, quest'ultima prevarrà.
Costruire per l'efficienza con le funzioni
Le funzioni definite dall'utente non sono ottimizzate a shard singolo per impostazione predefinita, ma possono essere configurate per essere eseguite come operazioni a shard singolo. Le funzioni possono incapsulare la logica e garantire che venga eseguita in modo ottimizzato per un singolo shard.
Perché le operazioni a shard singolo sono importanti
L'utilizzo delle risorse è importante per le prestazioni e l'efficienza dei costi. Le operazioni a shard singolo utilizzano un numero significativamente inferiore di risorse rispetto alle operazioni con shard incrociati. Ad esempio, quando si esegue una funzione per inserire un milione di righe, l'esecuzione single-shard utilizza circa 90,5 ACUs rispetto ai 126,5 ACUs per l'esecuzione cross-shard, con un miglioramento del 35% dell'efficienza delle risorse.
L'esecuzione a shard singolo fornisce inoltre:
-
Throughput superiore del 35% rispetto alle operazioni cross-shard
-
Tempi di risposta più prevedibili
-
Migliore scalabilità man mano che i dati crescono
Operazioni e funzioni a shard singolo
Le funzioni vengono eseguite sugli shard quando viene soddisfatto uno di questi prerequisiti:
-
La funzione viene creata come immutabile e inclusa in una query ottimizzata a shard singolo
-
La funzione è distribuita da un utente
Le funzioni eseguite sugli shard offrono prestazioni e scalabilità migliori perché vengono eseguite dove si trovano i dati.
Funzioni e volatilità
Per verificare la volatilità di una funzione, usa questa query sulle tabelle di sistema di PostgreSQL:
SELECT DISTINCT nspname, proname, provolatile FROM pg_proc PRO JOIN pg_namespace NSP ON PRO.pronamespace = NSP.oid WHERE proname IN ('random', 'md5');
Output di esempio:
nspname | proname | provolatile ------------+---------+------------- pg_catalog | md5 | i pg_catalog | random | v (2 rows)
In questo esempio, md5() è immutabile ed è volatile. random() Ciò significa che un'istruzione ottimizzata per shard singolo che include md5() rimane ottimizzata per shard singolo, mentre un'istruzione che include no. random()
Esempio con funzione immutabile:
EXPLAIN ANALYZE SELECT pg_catalog.md5('123') FROM s1.t1 WHERE col_a = 776586194 AND col_b = 654849524 AND col_c = '3ac2f2affb02987159ccd6ebd23e1ae5';
QUERY PLAN
----------------------------------------------------
Foreign Scan (cost=100.00..101.00 rows=100 width=0)
(actual time=3.409..3.409 rows=1 loops=1)
Single Shard Optimized
Planning Time: 0.313 ms
Execution Time: 4.253 ms
(4 rows)
Esempio con funzione volatile:
EXPLAIN ANALYZE SELECT pg_catalog.random() FROM s1.t1 WHERE col_a = 776586194 AND col_b = 654849524 AND col_c = '3ac2f2affb02987159ccd6ebd23e1ae5';
QUERY PLAN ------------------------------------------------------ Foreign Scan on t1_fs00001 t1 (cost=100.00..15905.15 rows=1 width=8) (actual time=0.658..0.658 rows=1 loops=1) Planning Time: 0.263 ms Execution Time: 2.892 ms (3 rows)
L'output mostra che md5() viene premuto ed eseguito come ottimizzato per shard singolo, mentre non lo è. random()
Funzioni di distribuzione
Una funzione che accede ai dati su un solo shard deve essere eseguita su quello shard per ottenere vantaggi in termini di prestazioni. La funzione deve essere distribuita e la firma della funzione deve includere la chiave shard completa: tutte le colonne della chiave shard devono essere passate come parametri alla funzione.
Funzione di esempio:
CREATE OR REPLACE FUNCTION s1.func1( param_a bigint, param_b bigint, param_c char(100) ) RETURNS int AS $$ DECLARE res int; BEGIN SELECT COUNT(*) INTO res FROM s1.t1 WHERE s1.t1.col_a = param_a AND s1.t1.col_b = param_b AND s1.t1.col_c = param_c; RETURN res; END $$ LANGUAGE plpgsql;
Prima della distribuzione, la funzione non è ottimizzata per un singolo shard:
EXPLAIN ANALYZE SELECT * FROM s1.func1(776586194, 654849524, '3ac2f2affb02987159ccd6ebd23e1ae5');
QUERY PLAN
------------------------------------------------------------------------------------------------------
Function Scan on func1 (cost=0.25..0.26 rows=1 width=4)
(actual time=37.503..37.503 rows=1 loops=1)
Planning Time: 0.901 ms
Execution Time: 51.647 ms
(3 rows)
Per distribuire la funzione:
SELECT rds_aurora.limitless_distribute_function( 's1.func1(bigint,bigint,character)', ARRAY['param_a','param_b','param_c'], 's1.t1' );
Dopo la distribuzione, la funzione è ottimizzata per shard singolo:
EXPLAIN ANALYZE SELECT * FROM s1.func1(776586194, 654849524, '3ac2f2affb02987159ccd6ebd23e1ae5');
QUERY PLAN
------------------------------------------------------------------------------------------------
Foreign Scan (cost=100.00..101.00 rows=100 width=0)
(actual time=4.332..4.333 rows=1 loops=1)
Single Shard Optimized
Planning Time: 0.857 ms
Execution Time: 5.116 ms
(4 rows)
Puoi confermare l'ottimizzazione a shard singolo controllando la colonna in: sso_calls rds_aurora.limitless_stat_statements
subcluster_id | subcluster_type | calls | sso_calls | query --------------+-----------------+-------+-----------+-------------------------------------- 2 | router | 2 | 1 | SELECT * FROM s1.func1( $1, $2, $3 ) 3 | router | 1 | 1 | SELECT * FROM s1.func1( $1, $2, $3 ) (2 rows)
Funzioni e modelli di efficienza
L'esecuzione della logica in prossimità dei dati è più efficiente e le funzioni svolgono un ruolo chiave a tal fine. Esistono due casi d'uso principali per migliorare l'efficienza delle funzioni:
-
Estrazione della chiave shard da dati complessi per richiamare una funzione ottimizzata a shard singolo separata
-
Trasformazione dei carichi di lavoro cross-shard in single-shard ottimizzati separando la logica cross-shard dalle istruzioni ottimizzate a shard singolo
Estrazione della chiave shard da dati complessi
Consideriamo una funzione con firma s3.func3(p_json_doc json) che esegue diverse operazioni sul database. Queste operazioni verranno eseguite su tutti gli shard all'interno di una transazione che si estende su tutti gli shard. Se il documento JSON contiene la chiave shard, puoi creare una funzione ottimizzata per shard singolo per eseguire le operazioni del database.
Modello originale:
s3.func3(p_json_doc json) database operation 1; database operation 2; database operation 3;
Pattern ottimizzato:
s3.func3(p_json_doc json) DECLARE v_a bigint; BEGIN v_a := (p_json_doc->>'field_a')::bigint; SELECT s3.func3_INNER(v_a, p_json_doc); END;
Dove funziona la funzione interna:
s3.func3_INNER(p_a, p_json_doc) database operation 1 WHERE shard_key = p_a; database operation 2 WHERE shard_key = p_a; database operation 3 WHERE shard_key = p_a;
In questo modello, la chiave shard è incapsulata in un tipo di dati complesso o deducibile da altri parametri. La logica, l'accesso ai dati e le funzioni possono determinare, estrarre o costruire la chiave shard, quindi richiamare una funzione ottimizzata per shard singolo che esegue operazioni su un solo shard. Poiché l'interfaccia dell'applicazione non cambia, l'ottimizzazione è relativamente facile da testare.
Rimandare la chiave shard da altre funzioni o dati
Un altro modello di progettazione si applica quando la logica o l'accesso ai dati calcola o determina la chiave shard. Ciò è utile quando una funzione può essere eseguita su un singolo shard per la maggior parte delle chiamate, ma occasionalmente richiede l'esecuzione tra shard.
Modello originale:
NEWORD(INTEGER, …) RETURNS NUMERIC DECLARE all_whid_local := true; LOOP through the order lines Generate warehouse ID; IF generated warehouse ID == input warehouse ID THEN ol_supply_whid := input warehouse ID; ELSE all_whid_local := false; ol_supply_whid := generated warehouse ID; END IF; … END LOOP; … RETURN no_s_quantity;
Modello ottimizzato con funzioni separate:
CREATE OR REPLACE FUNCTION NEWORD_sso(no_w_id INTEGER, …) RETURNS NUMERIC … RETURN no_s_quantity; … END; LANGUAGE 'plpgsql'; SELECT rds_aurora.limitless_distribute_function( 'NEWORD_sso(int,…)', ARRAY['no_w_id'], 'warehouse' ); CREATE OR REPLACE FUNCTION NEWORD_crosshard(no_w_id INTEGER, …) RETURNS NUMERIC … RETURN no_s_quantity; … END; LANGUAGE 'plpgsql';
Quindi fai in modo che la funzione principale chiami la versione ottimizzata per shard singolo o per shard incrociato:
IF all_whid_local THEN SELECT NEWORD_sso(…) INTO no_s_quantity; ELSE SELECT NEWORD_crosshard(…) INTO no_s_quantity; END IF;
Questo approccio consente alla maggior parte delle chiamate di trarre vantaggio dall'ottimizzazione a shard singolo mantenendo al contempo il comportamento corretto nei casi che richiedono l'esecuzione su shard incrociati.
Verifica delle operazioni a shard singolo
Utilizzato EXPLAIN per verificare se un'istruzione è ottimizzata per shard singolo. L'output riporta esplicitamente «Single Shard Optimized» per operazioni ottimizzate.
Invocazione Cross-shard prima della distribuzione:
QUERY PLAN
---------------------------------------------------------------------
Function Scan on func1 (cost=0.25..0.26 rows=1 width=4)
(actual time=59.622..59.623 rows=1 loops=1)
Planning Time: 0.925 ms
Execution Time: 60.211 ms
Invocazione a shard singolo dopo la distribuzione:
QUERY PLAN
----------------------------------------------------------------------
Foreign Scan (cost=100.00..101.00 rows=100 width=0)
(actual time=4.576..4.577 rows=1 loops=1)
Single Shard Optimized
Planning Time: 1.483 ms
Execution Time: 5.404 ms
La differenza nei tempi di esecuzione dimostra i vantaggi in termini di prestazioni dell'ottimizzazione a shard singolo.