Die vorliegende Übersetzung wurde maschinell erstellt. Im Falle eines Konflikts oder eines Widerspruchs zwischen dieser übersetzten Fassung und der englischen Fassung (einschließlich infolge von Verzögerungen bei der Übersetzung) ist die englische Fassung maßgeblich.
Effizientes Bauen mit Funktionen
Benutzerdefinierte Funktionen sind standardmäßig nicht für Single-Shard optimiert, können aber so konfiguriert werden, dass sie als Single-Shard-Operationen ausgeführt werden. Funktionen können Logik kapseln und sicherstellen, dass sie auf Single-Shard-optimierte Weise ausgeführt wird.
Warum Single-Shard-Operationen wichtig sind
Die Ressourcennutzung ist wichtig für Leistung und Kosteneffizienz. Single-Shard-Operationen verbrauchen im Vergleich zu Cross-Shard-Operationen deutlich weniger Ressourcen. Wenn beispielsweise eine Funktion zum Einfügen von einer Million Zeilen ausgeführt wird, verbraucht die Single-Shard-Ausführung etwa 90,5 ACUs gegenüber 126,5 ACUs für die Ausführung über mehrere Shards — eine Verbesserung der Ressourceneffizienz um 35%.
Die Ausführung mit einem einzigen Shard bietet außerdem:
-
35% höherer Durchsatz als bei Cross-Shard-Vorgängen
-
Vorhersehbarere Reaktionszeiten
-
Bessere Skalierbarkeit bei wachsendem Datenvolumen
Single-Shard-Operationen und -Funktionen
Funktionen werden auf Shards ausgeführt, wenn eine der folgenden Voraussetzungen erfüllt ist:
-
Die Funktion wird unveränderlich erstellt und ist in einer für einen einzelnen Shard optimierten Abfrage enthalten
-
Die Funktion wird von einem Benutzer verteilt
Funktionen, die auf Shards ausgeführt werden, sind leistungsfähiger und skalierbarer, da sie dort ausgeführt werden, wo sich die Daten befinden.
Funktionen und Volatilität
Um die Volatilität einer Funktion zu überprüfen, verwenden Sie diese Abfrage für die Systemtabellen von PostgreSQL:
SELECT DISTINCT nspname, proname, provolatile FROM pg_proc PRO JOIN pg_namespace NSP ON PRO.pronamespace = NSP.oid WHERE proname IN ('random', 'md5');
Beispielausgabe:
nspname | proname | provolatile ------------+---------+------------- pg_catalog | md5 | i pg_catalog | random | v (2 rows)
In diesem Beispiel md5() ist sie unveränderlich und random() volatil. Das bedeutet, dass eine für einzelne Shard optimierte Anweisung, die Folgendes beinhaltet, auch md5() weiterhin für einen einzelnen Shard optimiert ist, während eine Anweisung, die Include einschließt, dies nicht tut. random()
Beispiel mit unveränderlicher Funktion:
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)
Beispiel mit flüchtiger Funktion:
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)
Die Ausgabe zeigt, dass nach unten gedrückt und als Single-Shard-Optimierung ausgeführt md5() wird, dies aber nicht der Fall random() ist.
Funktionen verteilen
Eine Funktion, die nur auf Daten auf einem Shard zugreift, sollte auf diesem Shard ausgeführt werden, um Leistungsvorteile zu erzielen. Die Funktion muss verteilt sein und die Funktionssignatur muss den vollständigen Shard-Schlüssel enthalten — alle Spalten im Shard-Schlüssel müssen als Parameter an die Funktion übergeben werden.
Beispielfunktion:
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;
Vor der Verteilung ist die Funktion nicht für einen einzelnen Shard optimiert:
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)
Um die Funktion zu verteilen:
SELECT rds_aurora.limitless_distribute_function( 's1.func1(bigint,bigint,character)', ARRAY['param_a','param_b','param_c'], 's1.t1' );
Nach der Verteilung ist die Funktion für einen einzelnen Shard optimiert:
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)
Sie können die Single-Shard-Optimierung bestätigen, indem Sie die folgende Spalte markieren: 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)
Funktionen und Effizienzmuster
Die datennahe Ausführung von Logik ist effizienter, und Funktionen spielen dabei eine Schlüsselrolle. Es gibt zwei Hauptanwendungsfälle für die Verbesserung der Effizienz mit Funktionen:
-
Extrahieren des Shard-Schlüssels aus komplexen Daten, um eine separate, für einzelne Shard optimierte Funktion aufzurufen
-
Umwandlung von Shard-übergreifenden Workloads in Single-Shard-optimierte Workloads, indem Cross-Shard-Logik von Single-Shard-optimierten Anweisungen getrennt wird
Extrahieren des Shard-Schlüssels aus komplexen Daten
Stellen Sie sich eine Funktion mit Signatur vors3.func3(p_json_doc json), die mehrere Datenbankoperationen ausführt. Diese Operationen werden für alle Shards innerhalb einer Transaktion ausgeführt, die sich über alle Shards erstreckt. Wenn das JSON-Dokument den Shard-Schlüssel enthält, können Sie eine für einzelne Shard optimierte Funktion zur Ausführung der Datenbankoperationen erstellen.
Ursprüngliches Muster:
s3.func3(p_json_doc json) database operation 1; database operation 2; database operation 3;
Optimiertes Muster:
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;
Wo die innere Funktion funktioniert:
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 diesem Muster ist der Shard-Schlüssel in einem komplexen Datentyp gekapselt oder aus anderen Parametern ableitbar. Logik, Datenzugriff und Funktionen können den Shard-Schlüssel bestimmen, extrahieren oder konstruieren und dann eine für einen einzelnen Shard optimierte Funktion aufrufen, die Operationen ausführt, die nur einen einzelnen Shard betreffen. Da sich die Anwendungsschnittstelle nicht ändert, ist die Optimierung vergleichsweise einfach zu testen.
Den Shard-Schlüssel von anderen Funktionen oder Daten fernhalten
Ein anderes Entwurfsmuster gilt, wenn Logik oder Datenzugriff den Shard-Schlüssel berechnet oder bestimmt. Dies ist nützlich, wenn eine Funktion bei den meisten Aufrufen auf einem einzelnen Shard ausgeführt werden kann, gelegentlich aber eine shard-übergreifende Ausführung erfordert.
Ursprüngliches Muster:
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;
Optimiertes Muster mit separaten Funktionen:
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';
Lassen Sie dann die Hauptfunktion entweder die Single-Shard-optimierte oder die Cross-Shard-Version aufrufen:
IF all_whid_local THEN SELECT NEWORD_sso(…) INTO no_s_quantity; ELSE SELECT NEWORD_crosshard(…) INTO no_s_quantity; END IF;
Dieser Ansatz ermöglicht es den meisten Aufrufen, von der Single-Shard-Optimierung zu profitieren und gleichzeitig das korrekte Verhalten in Fällen beizubehalten, die eine Shard-übergreifende Ausführung erfordern.
Es wird nach Single-Shard-Operationen gesucht
Wird verwendetEXPLAIN, um zu überprüfen, ob eine Anweisung für Single-Shard optimiert ist. In der Ausgabe wird ausdrücklich „Single Shard Optimized“ für optimierte Operationen gemeldet.
Shardübergreifender Aufruf vor der Verteilung:
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
Aufruf eines einzelnen Shards nach der Verteilung:
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
Der Unterschied in den Ausführungszeiten zeigt den Leistungsvorteil der Single-Shard-Optimierung.