関数を使用した効率向上の構築 - Amazon Aurora

関数を使用した効率向上の構築

ユーザー定義関数は、デフォルトでは単一シャードの最適化は行われませんが、シングルシャードオペレーションとして実行するように設定できます。関数はロジックをカプセル化し、単一シャードの最適化方式で実行できます。

単一シャードオペレーションが重要な理由

リソース使用率は、パフォーマンスとコスト効率にとって重要です。単一シャードオペレーションでは、クロスシャードオペレーションに比べてリソースの使用が大幅に少なくなります。例えば、関数を実行して 100 万行を挿入する場合、単一シャード実行では約 90.5 ACU、クロスシャード実行では 126.5 ACU が使用されるため、リソース効率が 35% 向上します。

単一シャード実行では、以下も提供されます。

  • クロスシャードオペレーションよりもスループットが 35% 高い

  • より予測可能な応答時間

  • データの増加に伴うスケーラビリティの向上

シングルシャードオペレーションと関数

関数は、次のいずれかの前提条件が満たされるとシャードで実行されます。

  • 関数はイミュータブルとして作成され、単一シャード最適化クエリに含まれます。

  • 関数はユーザーによって配布されます

シャードで実行される関数は、データがある場所で実行されるため、パフォーマンスとスケーリングが向上します。

関数とボラティリティ

関数のボラティリティを確認するには、PostgreSQL のシステムテーブルで次のクエリを使用します。

SELECT DISTINCT nspname, proname, provolatile FROM pg_proc PRO JOIN pg_namespace NSP ON PRO.pronamespace = NSP.oid WHERE proname IN ('random', 'md5');

出力例:

  nspname   | proname | provolatile 
------------+---------+-------------
 pg_catalog | md5     | i
 pg_catalog | random  | v
(2 rows)

この例で、md5() はイミュータブルで、random() は変動します。つまり、md5() を含む単一シャード最適化ステートメントは単一シャード最適化のままですが、random() を含むステートメントは最適化されません。

イミュータブル関数の例:

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)

変動性のある関数の例:

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)

出力は、md5() がプッシュダウンされて、単一シャード最適化として実行されていますが、random() はそうではないことを示しています。

関数の配布

1 つのシャードのみのデータにアクセスする関数は、パフォーマンス上の利点を得るために、そのシャードで実行する必要があります。関数を分散し、関数の署名に完全なシャードキーを含める必要があります。シャードキーのすべての列をパラメータとして関数に渡す必要があります。

関数の例

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;

ディストリビューションの前は、関数の単一シャードの最適化は行われません。

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)

関数を配布するには

SELECT rds_aurora.limitless_distribute_function( 's1.func1(bigint,bigint,character)', ARRAY['param_a','param_b','param_c'], 's1.t1' );

ディストリビューションの後、関数の単一シャードの最適化が行われます。

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)

rds_aurora.limitless_stat_statementssso_calls 列を確認することで、単一シャードの最適化を確認できます。

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)

関数と効率のパターン

データの近くでロジックを実行する方が効率的であり、これを実現するには関数が重要な役割を果たします。関数の効率を向上させるには、主に 2 つのユースケースがあります。

  1. 複雑なデータからシャードキーを抽出して別の単一シャード最適化関数を呼び出す

  2. クロスシャードロジックを単一シャード最適化ステートメントから分離して、クロスシャードワークロードを単一シャード最適化に変換する

複雑なデータからシャードキーを抽出する

いくつかのデータベースオペレーションを実行する署名 s3.func3(p_json_doc json) を持つ関数について検討します。これらのオペレーションは、すべてのシャードにまたがるトランザクション内のすべてのシャードで実行されます。JSON ドキュメントにシャードキーが含まれている場合、単一シャード最適化関数を構築してデータベースオペレーションを実行できます。

元のパターン:

s3.func3(p_json_doc json) database operation 1; database operation 2; database operation 3;

最適化パターン:

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;

内部関数は次を実行します。

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;

このパターンでは、シャードキーは複雑なデータ型にカプセル化されるか、他のパラメータから推定されます。ロジック、データアクセス、関数は、シャードキーを決定、抽出、または構築し、単一シャードのみに関するオペレーションを実行する単一シャード最適化関数を呼び出すことができます。アプリケーションインターフェイスは変更されないため、最適化のテストは比較的簡単です。

他の関数またはデータからのシャードキーの遅延

ロジックまたはデータアクセスがシャードキーを計算または決定する場合、別の設計パターンが適用されます。これは、ほとんどの呼び出しで関数を単一のシャードで実行できますが、クロスシャード実行が必要になる場合があります。

元のパターン:

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;

個別の関数で最適化されたパターン:

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

次に、メイン関数で単一シャード最適化バージョンまたはクロスシャードバージョンを呼び出します。

IF all_whid_local THEN SELECT NEWORD_sso(…) INTO no_s_quantity; ELSE SELECT NEWORD_crosshard(…) INTO no_s_quantity; END IF;

このアプローチにより、ほとんどの呼び出しは、クロスシャード実行を必要とするケースで正しい動作を維持しながら、単一シャード最適化の恩恵を受けることができます。

単一シャードオペレーションの確認

EXPLAIN を使用して、ステートメントが単一シャード最適化されているかどうかを確認します。出力は、最適化されたオペレーションの「単一シャードの最適化」を明示的にレポートします。

ディストリビューション前のクロスシャード呼び出し:

                       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

ディストリビューション後の単一シャード呼び出し:

                       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

実行時間の違いは、単一シャード最適化のパフォーマンス上の利点を示しています。