

# Aurora DSQL EXPLAIN プランの操作
<a name="working-with-explain-plans"></a>

Aurora DSQL は PostgreSQL と同様の EXPLAIN プラン構造を使用しますが、分散アーキテクチャと実行モデルを反映するキーが追加されています。

このドキュメントでは、Aurora DSQL EXPLAIN プランの概要を示し、PostgreSQL と比較した類似点と相違点を強調します。Aurora DSQL で使用できるさまざまなタイプのスキャンオペレーションについて説明し、クエリの実行コストを理解するのに役立ちます。

## PostgreSQL と Aurora DSQL EXPLAIN プラン
<a name="postgresql-explain-plans"></a>

 Aurora DSQL は PostgreSQL データベース上に構築され、ほとんどの計画構造を PostgreSQL と共有しますが、クエリの実行と最適化に影響するアーキテクチャ上の主な違いがあります。


| 機能 | PostgreSQL | Aurora DSQL | 
| --- | --- | --- | 
|  データストレージ  |  ヒープストレージ  |  ヒープなし。すべての行は一意の識別子によってインデックスが作成されます  | 
|  プライマリキー  |  プライマリキーインデックスはテーブルデータとは異なります  |  プライマリキーインデックスは、すべての追加列を INCLUDE 列とするテーブルです。  | 
|  セカンダリインデックス  |  標準セカンダリインデックス  |  PostgreSQL と同じように動作し、キー以外の列を含める機能があります。  | 
|  フィルタリング機能  |  インデックス条件、ヒープフィルター  |  インデックス条件、ストレージフィルター、クエリプロセッサフィルター   | 
|  スキャンタイプ  |  シーケンシャルスキャン、インデックススキャン、インデックスのみのスキャン  |  フルスキャン、インデックスのみのスキャン、インデックススキャン  | 
|  クエリの実行  |  データベースのローカル  |  分散 (コンピューティングとストレージは別)  | 

Aurora DSQL は、テーブルデータを個別のヒープではなく、プライマリキーの順序で直接保存します。各行は一意のキー、通常はプライマリキーによって識別されるため、データベースの検索をより効率的に最適化できます。アーキテクチャの違いは、PostgreSQL がシーケンシャルスキャンを選択する可能性がある場合に、Aurora DSQL がインデックスのみのスキャンを使用することが多い理由の説明になります。

もう 1 つの主な違いは、Aurora DSQL がコンピューティングをストレージから分離し、実行パスの早い段階でフィルターを適用してデータの移動を減らし、パフォーマンスを向上させることです。

PostgreSQL での EXPLAIN プランの使用の詳細については、「[PostgreSQL EXPLAIN documentation](https://www.postgresql.org/docs/current/using-explain.html)」を参照してください。

## Aurora DSQL EXPLAIN プランの主な要素
<a name="explain-plan-elements"></a>

Aurora DSQL EXPLAIN プランは、フィルタリングが発生する場所やストレージから取得される列を含め、クエリの実行方法に関する詳細情報を提供します。この出力を理解することにより、クエリのパフォーマンスを最適化できます。

インデックス条件  
インデックスのナビゲーションに使用される条件。スキャンされたデータを削減するのに最も効率的なフィルタリング。Aurora DSQL では、インデックス条件を実行プランの複数のレイヤーに適用できます。

計画  
ストレージから取得した列。射影が少ないほど、パフォーマンスが向上します。

ストレージフィルター  
ストレージレベルで適用される条件。クエリプロセッサフィルターよりも効率的です。

クエリプロセッサフィルター  
クエリプロセッサレベルで適用される条件。フィルタリングの前にすべてのデータを転送する必要があるため、データの移動と処理のオーバーヘッドが増加します。

## Aurora DSQL のフィルター
<a name="filtering-and-projection"></a>

Aurora DSQL はストレージからコンピューティングを分離します。つまり、クエリの実行中にフィルターが適用されるポイントはパフォーマンスに大きな影響を与えます。大量のデータが転送される前に適用されるフィルターは、レイテンシーを低減し、効率を向上させます。フィルターの適用が早いほど、処理、移動、スキャンが必要なデータが少なくなり、クエリが高速化されます。

Aurora DSQL は、クエリパスの複数のステージでフィルターを適用できます。これらのステージを理解することは、クエリプランを解釈し、パフォーマンスを最適化する上で重要です。


| レベル | フィルタータイプ | Description | 
| --- | --- | --- | 
| 1 | インデックス条件 |  インデックスのスキャン中に適用されます。ストレージから読み取るデータの量を制限し、コンピューティングレイヤーに送信されるデータを削減します。  | 
| 2 | ストレージフィルター | データがストレージから読み取られた後、コンピューティングに送信される前に適用されます。インデックスの include 列のフィルターの例を以下に示します。データ転送は減少しますが、読み取り量は減少しません。 | 
| 3 | クエリプロセッサフィルター | データがコンピューティングレイヤーに到達した後に適用されます。すべてのデータを最初に転送する必要があるため、レイテンシーとコストが増加します。現在、Aurora DSQL はストレージ上ですべてのフィルタリングと射影オペレーションを実行できないため、一部のクエリはこのタイプのフィルタリングに強制的にフォールバックされる可能性があります。 | 

# Aurora DSQL EXPLAIN プランの読み取り
<a name="reading-dsql-explain-plans"></a>

EXPLAIN プランの読み方を理解することは、クエリのパフォーマンスを最適化するうえで重要です。このセクションでは、Aurora DSQL クエリプランの実際の例を説明し、さまざまなスキャンタイプの動作を示し、フィルターが適用される場所を説明し、最適化の機会を強調します。

## これらの例で使用するサンプルテーブル
<a name="explain-plan-sample-tables"></a>

以下の例では、`transaction` と `account` の 2 つのテーブルを参照します。

`transaction` テーブルにはプライマリキーがないため、Aurora DSQL はクエリ時にテーブルのフルスキャンを実行します。

`account` テーブルには `customer_id` のインデックスがあります。このインデックスには `balance` と `status` がカバーリング列として含まれているため、ベーステーブルから読み取ることなく、特定のクエリをインデックスから直接完結できます。ただし、インデックスには `created_at` が含まれていないため、この列を参照するクエリには追加のテーブルアクセスが必要です。

```
CREATE TABLE transaction (
    account_id uuid,
    transaction_date timestamp,
    description text
);

CREATE TABLE account (
    customer_id uuid,
    balance numeric,
    status varchar,
    created_at timestamp
);

CREATE INDEX ASYNC idx1 ON account (customer_id) INCLUDE (balance, status);
```

## フルスキャンの例
<a name="full-scan-example"></a>

Aurora DSQL には、PostgreSQL と機能的に同じシーケンシャルスキャンとフルスキャンの両方があります。これら 2 つの唯一の違いは、フルスキャンはストレージで追加のフィルタリングを利用できることです。このため、ほとんどの場合、上記のシーケンシャルスキャンが選択されます。類似しているため、より魅力的なフルスキャンの例のみを取り上げます。

フルスキャンは、主にプライマリキーのないテーブルで使用されます。Aurora DSQL プライマリキーはデフォルトでフルカバーインデックスであるため、PostgreSQL がシーケンシャルスキャンを使用する多くの状況では、Aurora DSQL はほとんどの場合、プライマリキーでインデックスのみのスキャンを使用します。他のほとんどのデータベースと同様に、インデックスのないテーブルは正しくスケールされません。

```
EXPLAIN SELECT account_id FROM transaction WHERE transaction_date > '2025-01-01' AND description LIKE '%external%';
```

```
                                                   QUERY PLAN
----------------------------------------------------------------------------------------------------------------
 Full Scan (btree-table) on transaction  (cost=125100.05..177933.38 rows=33333 width=16)
   Filter: (description ~~ '%external%'::text)
   -> Storage Scan on transaction (cost=12510.05..17793.38 rows=66666 width=16)
        Projections: account_id, description
        Filters: (transaction_date > '2025-01-01 00:00:00'::timestamp without time zone)
        -> B-Tree Scan on transaction (cost=12510.05..17793.38 rows=100000 width=30)
```

このプランには、異なるステージに適用される 2 つのフィルターが表示されます。`transaction_date > '2025-01-01'` 条件はストレージレイヤーに適用され、返されるデータの量を減らします。`description LIKE '%external%'` 条件は、データが転送された後、クエリプロセッサで後に適用されるため、効率が低下します。より選択的なフィルターをストレージまたはインデックスレイヤーにプッシュすると、一般的にパフォーマンスが向上します。

## インデックスのみのスキャンの例
<a name="index-only-scan-example"></a>

インデックスのみのスキャンは、ストレージレイヤーへのラウンドトリップが最も少なく、最もフィルタリングできるため、Aurora DSQL で最適なスキャンタイプです。ただし、インデックスのみのスキャンが表示されるからといって、最適なプランとは限りません。発生する可能性のあるさまざまなレベルのフィルタリングのため、フィルタリングが発生する可能性のあるさまざまな場所に引き続き注意することが重要です。

```
EXPLAIN SELECT balance FROM account 
WHERE customer_id = '4b18a761-5870-4d7c-95ce-0a48eca3fceb' 
AND balance > 100 
AND status = 'pending';
```

```
                                  QUERY PLAN
-------------------------------------------------------------------------------
 Index Only Scan using idx1 on account  (cost=725.05..1025.08 rows=8 width=18)
   Index Cond: (customer_id = '4b18a761-5870-4d7c-95ce-0a48eca3fceb'::uuid)
   Filter: (balance > '100'::numeric)
   -> Storage Scan on idx1 (cost=12510.05..17793.38 rows=9 width=16)
        Projections: balance
        Filters: ((status)::text = 'pending'::text)
        -> B-Tree Scan on idx1 (cost=12510.05..17793.38 rows=10 width=30)
            Index Cond: (customer_id = '4b18a761-5870-4d7c-95ce-0a48eca3fceb'::uuid)
```

このプランでは、インデックス条件 `customer_id = '4b18a761-5870-4d7c-95ce-0a48eca3fceb'`) がインデックススキャン中に最初に評価されます。これは、ストレージから読み取るデータの量を制限するため、最も効率的なステージです。ストレージフィルター `status = 'pending'` は、データの読み取り後、コンピューティングレイヤーに送信される前に適用され、転送されるデータ量を削減します。最後に、クエリプロセッサフィルター `balance > 100` は、データが移動された後に最後に実行されるため、最も効率的ではありません。これらのうち、インデックス条件はスキャンされるデータ量を直接制御するため、最高のパフォーマンスを提供します。

## インデックススキャンの例
<a name="index-scan-example"></a>

インデックススキャンはインデックスのみのスキャンに似ていますが、ベーステーブルに呼び出す追加のステップがあります。Aurora DSQL はストレージフィルターを指定できるため、インデックス呼び出しとルックアップ呼び出しの両方で指定できます。

これを明確にするために、Aurora DSQL はプランを 2 つのノードとして示します。このように、インクルード列の追加がストレージから返される行に関してどの程度役立つかを明確に確認できます。

```
EXPLAIN SELECT balance FROM account 
WHERE customer_id = '4b18a761-5870-4d7c-95ce-0a48eca3fceb'
AND balance > 100 
AND status = 'pending' 
AND created_at > '2025-01-01';
```

```
                                                QUERY PLAN
----------------------------------------------------------------------------------------------------------
 Index Scan using idx1 on account  (cost=728.18..1132.20 rows=3 width=18)
   Filter: (balance > '100'::numeric)
   Index Cond: (customer_id = '4b18a761-5870-4d7c-95ce-0a48eca3fceb'::uuid)
   -> Storage Scan on idx1 (cost=12510.05..17793.38 rows=8 width=16)
        Projections: balance
        Filters: ((status)::text = 'pending'::text)
        -> B-Tree Scan on account (cost=12510.05..17793.38 rows=10 width=30)
            Index Cond: (customer_id = '4b18a761-5870-4d7c-95ce-0a48eca3fceb'::uuid)
   -> Storage Lookup on account (cost=12510.05..17793.38 rows=4 width=16)
        Filters: (created_at > '2025-01-01 00:00:00'::timestamp without time zone)
        -> B-Tree Lookup on transaction (cost=12510.05..17793.38 rows=8 width=30)
```

 このプランでは、フィルタリングが複数のステージ間でどのように行われるかを示します。
+  `customer_id ` のインデックス条件は、データを早期にフィルタリングします。
+ `status` のストレージフィルターは、コンピューティングに送信される前に結果をさらに絞り込みます。
+ `balance` のクエリプロセッサフィルターは、転送後に適用されます。
+ `created_at` のルックアップフィルターは、ベーステーブルから追加の列を取得するときに評価されます。

頻繁に使用される列を `INCLUDE` フィールドとして追加すると、多くの場合、このルックアップが解消され、パフォーマンスが向上します。

## ベストプラクティス
<a name="best-practices"></a>
+ **フィルターをインデックス化された列に合わせて**、フィルタリングを早めにプッシュします。
+ **INCLUDE 列を使用して**、インデックスのみのスキャンを許可し、ルックアップを回避します。
+ パフォーマンスの問題を調査するときに、**行数の見積もりを検証**します。Aurora DSQL は、データ変更率に基づいてバックグラウンドで `ANALYZE` を実行することで、統計を自動的に管理します。見積りが不正確に見える場合は、`ANALYZE` を手動で実行して統計を即座に更新できます。
+ 大きなテーブルに対する**インデックス化されていないクエリを回避し**、ワークロードの高いフルスキャンを防ぎます。

# EXPLAIN ANALYZE の DPU について
<a name="understanding-dpus-explain-analyze"></a>

Aurora DSQL は、`EXPLAIN ANALYZE VERBOSE` プラン出力に**ステートメントレベル**の分散処理ユニット (DPU) 情報を提供し、開発中のクエリコストをより詳細に可視化します。このセクションでは、DPU とは何か、`EXPLAIN ANALYZE VERBOSE` 出力でそれらを解釈する方法について説明します。

## DPU とは
<a name="what-is-dpu"></a>

分散処理ユニット (DPU) は、Aurora DSQL によって実行された作業の標準化された測定単位です。これは以下で構成されます。
+ **ComputeDPU** – SQL クエリの実行にかかった時間
+ **ReadDPU** – ストレージからデータを読み取るために使用されるリソース
+ **WriteDPU** - ストレージへのデータの書き込みに使用されるリソース
+ **MultiRegionWriteDPU** – マルチリージョン設定でピア接続されたクラスターへの書き込みをレプリケートするために使用されるリソース

## EXPLAIN ANALYZE VERBOSE での DPU の使用
<a name="dpu-usage-explain-analyze"></a>

Aurora DSQL は `EXPLAIN ANALYZE VERBOSE` を拡張して、ステートメントレベルの DPU 使用量の見積もりを出力の最後に含めます。これにより、クエリコストがすぐに可視化され、ワークロードのコスト要因の特定、クエリのパフォーマンスの調整、リソース使用量の予測に役立ちます。

次の例は、EXPLAIN ANALYZE VERBOSE 出力に含まれるステートメントレベルの DPU 見積もりを解釈する方法を示しています。

### 例 1: SELECT クエリ
<a name="select-query-example"></a>

```
EXPLAIN ANALYZE VERBOSE SELECT * FROM test_table;
```

```
QUERY PLAN
----------------------------------------------------
Index Only Scan using test_table_pkey on public.test_table  (cost=125100.05..171100.05 rows=1000000 width=36) (actual time=2.973..4.482 rows=120 loops=1)
  Output: id, context
  -> Storage Scan on test_table_pkey (cost=125100.05..171100.05 rows=1000000 width=36) (actual rows=120 loops=1)
      Projections: id, context
      -> B-Tree Scan on test_table_pkey (cost=125100.05..171100.05 rows=1000000 width=36) (actual rows=120 loops=1)
Query Identifier: qymgw1m77maoe
Planning Time: 11.415 ms
Execution Time: 4.528 ms
Statement DPU Estimate:
  Compute: 0.01607 DPU
  Read: 0.04312 DPU
  Write: 0.00000 DPU
  Total: 0.05919 DPU
```

この例では、SELECT ステートメントはインデックスのみのスキャンを実行するため、ほとんどのコストは、ストレージから取得されたデータを表す読み取り DPU (0.04312) と、結果を処理して返すために使用されるコンピューティングリソースを反映したコンピューティング DPU (0.01607) から発生します。クエリはデータを変更しないため、書き込み DPU はありません。合計 DPU (0.05919) は、コンピューティング \$1 読み取り \$1 書き込みの合計です。

### 例 2: INSERT クエリ
<a name="insert-query-example"></a>

```
EXPLAIN ANALYZE VERBOSE INSERT INTO test_table VALUES (1, 'name1'), (2, 'name2'), (3, 'name3');
```

```
QUERY PLAN
----------------------------------------------------
Insert on public.test_table  (cost=0.00..0.04 rows=0 width=0) (actual time=0.055..0.056 rows=0 loops=1)
  ->  Values Scan on "*VALUES*"  (cost=0.00..0.04 rows=3 width=122) (actual time=0.003..0.008 rows=3 loops=1)
        Output: "*VALUES*".column1, "*VALUES*".column2
Query Identifier: jtkjkexhjotbo
Planning Time: 0.068 ms
Execution Time: 0.543 ms
Statement DPU Estimate:
  Compute: 0.01550 DPU
  Read: 0.00307 DPU (Transaction minimum: 0.00375)
  Write: 0.01875 DPU (Transaction minimum: 0.05000)
  Total: 0.03732 DPU
```

このステートメントは主に書き込みを実行するため、ほとんどのコストは書き込み DPU に関連付けられます。Compute DPU (0.01550) は、値を処理して挿入するために行われた作業を表します。読み取り DPU (0.00307) は、マイナーシステム読み取り (カタログルックアップまたはインデックスチェックの場合) を反映します。

読み取りおよび書き込み DPU の横に表示されるトランザクションの最小値に注意してください。これらは、*オペレーションに読み取りまたは書き込みが含まれている場合にのみ*適用されるトランザクションあたりのベースラインコストを示します。すべてのトランザクションで 0.00375 読み取り DPU または 0.05 書き込み DPU 料金が自動的に発生するわけではありません。代わりに、これらの最小値は、コスト集計中に、そのトランザクション内で読み取りまたは書き込みが行われた場合にのみ、トランザクションレベルで適用されます。このスコープの違いにより、`EXPLAIN ANALYZE VERBOSE` のステートメントレベルの見積もりが、CloudWatch または請求データで報告されたトランザクションレベルのメトリクスと正確に一致しない場合があります。

## 最適化のための DPU 情報の使用
<a name="using-dpu-information-optimization"></a>

ステートメントごとの DPU 見積もりは、実行時間を超えてクエリを最適化する強力な方法を提供します。一般的ユースケースには以下が含まれます。
+ **コスト認識:** クエリが他のクエリと比較してどのくらいのコストがかかるかを把握します。
+ **スキーマの最適化:** インデックスまたはスキーマの変更がパフォーマンスとリソース効率の両方に与える影響を比較します。
+ **Budget Planning:** 観測された DPU 使用量に基づいてワークロードコストを見積もります。
+ **クエリ比較:** 代替クエリアプローチを相対的な DPU 消費量で評価します。

## DPU 情報の解釈
<a name="interpreting-dpu-information"></a>

`EXPLAIN ANALYZE VERBOSE` の DPU データを使用する場合は、次のベストプラクティスに注意してください。
+ **方向的に使用する:** 報告されたDPUを、CloudWatchメトリクスや請求データとの厳密な一致ではなく、クエリの*相対的な*コストを理解する手段として扱います。`EXPLAIN ANALYZE VERBOSE` はステートメントレベルのコストを報告し、CloudWatch はトランザクションレベルのアクティビティを集計するため、差異が良そうされます。CloudWatch には、`EXPLAIN ANALYZE VERBOSE` が意図的に除外するバックグラウンドオペレーション (ANALYZE や圧縮など) とトランザクションオーバーヘッド (`BEGIN`/`COMMIT`) も含まれています。
+ **実行間の DPU の変動は分散システムでは正常**であり、エラーを示すものではありません。キャッシュ、実行計画の変更、同時実行、データ分散のシフトなどの要因により、同じクエリが 1 回の実行から次の実行まで異なるリソースを消費する可能性があります。
+ **バッチスモールオペレーション:** ワークロードが多数のスモールステートメントを発行する場合は、それらをより大きなオペレーション (10MB を超えない) にバッチ処理することを検討してください。これにより、四捨五入オーバーヘッドが削減され、より意味のあるコスト見積もりが生成されます。
+ **請求ではなく調整に使用する:** `EXPLAIN ANALYZE VERBOSE` の DPU データは、コスト認識、クエリ調整、最適化のために設計されています。請求グレードのメトリクスではありません。信頼できるコストと使用状況データについては、常に CloudWatch メトリクスまたは毎月の請求レポートに依存します。