

# 関数を使用した効率向上の構築
<a name="limitless-performance-functions"></a>

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

## 単一シャードオペレーションが重要な理由
<a name="limitless-functions-importance"></a>

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

単一シャード実行では、以下も提供されます。
+ クロスシャードオペレーションよりもスループットが 35% 高い
+ より予測可能な応答時間
+ データの増加に伴うスケーラビリティの向上

## シングルシャードオペレーションと関数
<a name="limitless-functions-sso"></a>

関数は、次のいずれかの前提条件が満たされるとシャードで実行されます。
+ 関数はイミュータブルとして作成され、単一シャード最適化クエリに含まれます。
+ 関数はユーザーによって配布されます

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

## 関数とボラティリティ
<a name="limitless-functions-volatility"></a>

関数のボラティリティを確認するには、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()` はそうではないことを示しています。

## 関数の配布
<a name="limitless-functions-distributing"></a>

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_statements` の `sso_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)
```

## 関数と効率のパターン
<a name="limitless-functions-efficiency-patterns"></a>

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

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

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

### 複雑なデータからシャードキーを抽出する
<a name="limitless-functions-encapsulated-key"></a>

いくつかのデータベースオペレーションを実行する署名 `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;
```

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

### 他の関数またはデータからのシャードキーの遅延
<a name="limitless-functions-deferred-key"></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;
```

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

## 単一シャードオペレーションの確認
<a name="limitless-functions-checking-sso"></a>

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

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