

# Athena에서 EXPLAIN 및 EXPLAIN ANALYZE 사용
<a name="athena-explain-statement"></a>

`EXPLAIN` 문은 지정된 SQL 문의 논리적 또는 분산 실행 계획을 보여주거나 SQL 문의 유효성을 검사합니다. 결과를 텍스트 형식 또는 데이터 형식으로 출력하여 그래프로 렌더링할 수 있습니다.

**참고**  
`EXPLAIN` 구문을 사용하지 않고도 쿼리에 대한 논리적 계획 및 분산된 계획의 그래픽 표현을 Athena 콘솔에서 볼 수 있습니다. 자세한 내용은 [SQL 쿼리에 대한 실행 계획 보기](query-plans.md) 섹션을 참조하세요.

`EXPLAIN ANALYZE` 문은 지정된 SQL 문의 분산 실행 계획과 SQL 쿼리의 각 작업 계산 비용을 모두 표시합니다. 결과를 텍스트 또는 JSON 형식으로 출력할 수 있습니다.

## 고려 사항 및 제한 사항
<a name="athena-explain-statement-considerations-and-limitations"></a>

Athena의`EXPLAIN` 및 `EXPLAIN ANALYZE` 문에는 다음과 같은 제한이 있습니다.
+ `EXPLAIN` 쿼리는 어떠한 데이터도 스캔하지 않으므로 Athena는 이에 대해 과금하지 않습니다. 그러나 `EXPLAIN` 쿼리는 AWS Glue를 호출하여 테이블 메타데이터를 검색하기 때문에 호출이 [Glue의 프리 티어 한도](https://aws.amazon.com/free/?all-free-tier.sort-by=item.additionalFields.SortRank&all-free-tier.sort-order=asc&awsf.Free%20Tier%20Categories=categories%23analytics&all-free-tier.q=glue&all-free-tier.q_operator=AND)를 초과할 경우 Glue에서 요금이 발생할 수 있습니다.
+ `EXPLAIN ANALYZE` 쿼리가 실행되면 스캔 데이터를 수행하고 Athena가 스캔한 데이터 양에 대해 요금을 청구하기 때문입니다.
+ Lake Formation에 정의된 행 또는 셀 필터링 정보와 쿼리 통계 정보는 `EXPLAIN` 및 `EXPLAIN ANALYZE`의 출력에 표시되지 않습니다.

## EXPLAIN 구문
<a name="athena-explain-statement-syntax-athena-engine-version-2"></a>

```
EXPLAIN [ ( option [, ...]) ] statement
```

*옵션*은 다음 중 하나일 수 있습니다.

```
FORMAT { TEXT | GRAPHVIZ | JSON }
TYPE { LOGICAL | DISTRIBUTED | VALIDATE | IO }
```

`FORMAT` 옵션이 지정되지 않은 경우 출력은 기본적으로 `TEXT` 형식입니다. `IO` 유형은 쿼리에서 읽는 테이블 및 스키마에 대한 정보를 제공합니다.

## EXPLAIN ANALYZE 구문
<a name="athena-explain-analyze-statement"></a>

`EXPLAIN`에 포함된 출력 외에도 `EXPLAIN ANALYZE` 출력에는 CPU 사용량, 입력 행 수 및 출력 행 수와 같은 지정된 쿼리에 대한 런타임 통계도 포함됩니다.

```
EXPLAIN ANALYZE [ ( option [, ...]) ] statement
```

*옵션*은 다음 중 하나일 수 있습니다.

```
FORMAT { TEXT | JSON }
```

`FORMAT` 옵션이 지정되지 않은 경우 출력은 기본적으로 `TEXT` 형식입니다. `EXPLAIN ANALYZE`에 대한 모든 쿼리는 `DISTRIBUTED`이며, `TYPE` 옵션은 `EXPLAIN ANALYZE`에 사용할 수 없기 때문입니다.

*statement*는 다음 중 하나일 수 있습니다.

```
SELECT
CREATE TABLE AS SELECT
INSERT
UNLOAD
```

## EXPLAIN 예제
<a name="athena-explain-statement-examples"></a>

`EXPLAIN`에 대한 다음 예는 더 간단한 예에서 더 복잡한 예로 진행됩니다.

### 예제 1: EXPLAIN 문을 사용하여 텍스트 형식의 쿼리 계획을 표시
<a name="athena-explain-statement-example-text-query-plan"></a>

다음 예에서 `EXPLAIN`은 Elastic Load Balancing 로그 기반 `SELECT` 쿼리에 대한 실행 계획을 보여줍니다. 형식은 기본적으로 텍스트 출력입니다.

```
EXPLAIN 
SELECT 
   request_timestamp, 
   elb_name, 
   request_ip 
FROM sampledb.elb_logs;
```

#### 결과
<a name="athena-explain-statement-example-text-query-plan-results"></a>

```
- Output[request_timestamp, elb_name, request_ip] => [[request_timestamp, elb_name, request_ip]]
    - RemoteExchange[GATHER] => [[request_timestamp, elb_name, request_ip]]
        - TableScan[awsdatacatalog:HiveTableHandle{schemaName=sampledb, tableName=elb_logs, 
analyzePartitionValues=Optional.empty}] => [[request_timestamp, elb_name, request_ip]]
                LAYOUT: sampledb.elb_logs
                request_ip := request_ip:string:2:REGULAR
                request_timestamp := request_timestamp:string:0:REGULAR
                elb_name := elb_name:string:1:REGULAR
```

### 예제 2: EXPLAIN 문을 사용하여 쿼리 계획을 그래프로 작성
<a name="athena-explain-statement-example-graph-a-query-plan"></a>

Athena 콘솔을 사용하여 쿼리 계획을 그래프로 작성할 수 있습니다. Athena 쿼리 편집기에 다음과 같이 `SELECT` 문을 입력한 다음 **EXPLAIN**을 선택합니다.

```
SELECT 
      c.c_custkey,
      o.o_orderkey,
      o.o_orderstatus
   FROM tpch100.customer c 
   JOIN tpch100.orders o 
       ON c.c_custkey = o.o_custkey
```

Athena 쿼리 편집기의 **Explain** 페이지가 열리고 쿼리에 대한 분산 계획과 논리적 계획이 표시됩니다. 다음 그래프는 예제의 논리적 계획을 보여줍니다.

![\[Athena 쿼리 편집기에서 렌더링한 쿼리 계획의 그래프.\]](http://docs.aws.amazon.com/ko_kr/athena/latest/ug/images/athena-explain-statement-tpch.png)


**중요**  
현재 일부 파티션 필터는 Athena에서 쿼리에 적용하더라도 중첩 연산자 트리 그래프에 표시되지 않을 수도 있습니다. 이러한 필터의 효과를 확인하려면 쿼리에서 `EXPLAIN` 또는 `EXPLAIN ANALYZE`를 실행하고 결과를 확인합니다.

Athena 콘솔에서 쿼리 계획 그래프 작성 기능 사용에 대한 자세한 내용은 [SQL 쿼리에 대한 실행 계획 보기](query-plans.md) 단원을 참조하세요.

### 예제 3: EXPLAIN 문을 사용하여 파티션 정리 확인
<a name="athena-explain-statement-example-verify-partition-pruning"></a>

분할된 테이블을 쿼리할 때 분할된 키에 필터링 조건자를 사용하면 쿼리 엔진은 조건자를 분할된 키에 적용해 읽는 데이터의 양을 줄입니다.

다음 예제에서는 `EXPLAIN` 쿼리를 사용하여 분할된 테이블에 대한 `SELECT` 쿼리의 파티션 정리를 확인합니다. 먼저 `CREATE TABLE` 문은 `tpch100.orders_partitioned` 테이블을 생성합니다. 테이블은 `o_orderdate` 열에서 분할됩니다.

```
CREATE TABLE `tpch100.orders_partitioned`(
  `o_orderkey` int, 
  `o_custkey` int, 
  `o_orderstatus` string, 
  `o_totalprice` double, 
  `o_orderpriority` string, 
  `o_clerk` string, 
  `o_shippriority` int, 
  `o_comment` string)
PARTITIONED BY ( 
  `o_orderdate` string)
ROW FORMAT SERDE 
  'org.apache.hadoop.hive.ql.io.parquet.serde.ParquetHiveSerDe' 
STORED AS INPUTFORMAT 
  'org.apache.hadoop.hive.ql.io.parquet.MapredParquetInputFormat' 
OUTPUTFORMAT 
  'org.apache.hadoop.hive.ql.io.parquet.MapredParquetOutputFormat'
LOCATION
  's3://amzn-s3-demo-bucket/<your_directory_path>/'
```

`SHOW PARTITIONS` 명령으로 표시된 것처럼 `tpch100.orders_partitioned` 테이블에는 `o_orderdate`에 여러 개의 파티션이 있습니다.

```
SHOW PARTITIONS tpch100.orders_partitioned;

o_orderdate=1994
o_orderdate=2015
o_orderdate=1998
o_orderdate=1995
o_orderdate=1993
o_orderdate=1997
o_orderdate=1992
o_orderdate=1996
```

다음 `EXPLAIN` 쿼리는 지정된 `SELECT` 문의 파티션 정리를 확인합니다.

```
EXPLAIN 
SELECT 
   o_orderkey, 
   o_custkey, 
   o_orderdate 
FROM tpch100.orders_partitioned
WHERE o_orderdate = '1995'
```

#### 결과
<a name="athena-explain-statement-example-verify-partition-pruning-results"></a>

```
Query Plan
- Output[o_orderkey, o_custkey, o_orderdate] => [[o_orderkey, o_custkey, o_orderdate]]
    - RemoteExchange[GATHER] => [[o_orderkey, o_custkey, o_orderdate]]
        - TableScan[awsdatacatalog:HiveTableHandle{schemaName=tpch100, tableName=orders_partitioned, 
analyzePartitionValues=Optional.empty}] => [[o_orderkey, o_custkey, o_orderdate]]
                LAYOUT: tpch100.orders_partitioned
                o_orderdate := o_orderdate:string:-1:PARTITION_KEY
                    :: [[1995]]
                o_custkey := o_custkey:int:1:REGULAR
                o_orderkey := o_orderkey:int:0:REGULAR
```

결과에서 굵은 텍스트는 `o_orderdate = '1995'` 조건자가 `PARTITION_KEY`에 적용되었음을 나타냅니다.

### 예제 4: EXPLAIN 쿼리를 사용하여 조인 순서 및 조인 유형 확인
<a name="athena-explain-statement-example-check-join-order-and-type"></a>

다음 `EXPLAIN` 쿼리는 `SELECT` 문의 조인 순서 및 조인 유형을 확인합니다. 이와 같은 쿼리를 사용하면 쿼리 메모리 사용량을 검사하여 `EXCEEDED_LOCAL_MEMORY_LIMIT` 오류가 발생할 확률을 줄일 수 있습니다.

```
EXPLAIN (TYPE DISTRIBUTED)
   SELECT 
      c.c_custkey, 
      o.o_orderkey,
      o.o_orderstatus
   FROM tpch100.customer c 
   JOIN tpch100.orders o 
       ON c.c_custkey = o.o_custkey 
   WHERE c.c_custkey = 123
```

#### 결과
<a name="athena-explain-statement-example-check-join-order-and-type-results"></a>

```
Query Plan
Fragment 0 [SINGLE]
    Output layout: [c_custkey, o_orderkey, o_orderstatus]
    Output partitioning: SINGLE []
    Stage Execution Strategy: UNGROUPED_EXECUTION
    - Output[c_custkey, o_orderkey, o_orderstatus] => [[c_custkey, o_orderkey, o_orderstatus]]
        - RemoteSource[1] => [[c_custkey, o_orderstatus, o_orderkey]]

Fragment 1 [SOURCE]
    Output layout: [c_custkey, o_orderstatus, o_orderkey]
    Output partitioning: SINGLE []
    Stage Execution Strategy: UNGROUPED_EXECUTION
    - CrossJoin => [[c_custkey, o_orderstatus, o_orderkey]]
            Distribution: REPLICATED
        - ScanFilter[table = awsdatacatalog:HiveTableHandle{schemaName=tpch100, 
tableName=customer, analyzePartitionValues=Optional.empty}, grouped = false, 
filterPredicate = ("c_custkey" = 123)] => [[c_custkey]]
                LAYOUT: tpch100.customer
                c_custkey := c_custkey:int:0:REGULAR
        - LocalExchange[SINGLE] () => [[o_orderstatus, o_orderkey]]
            - RemoteSource[2] => [[o_orderstatus, o_orderkey]]

Fragment 2 [SOURCE]
    Output layout: [o_orderstatus, o_orderkey]
    Output partitioning: BROADCAST []
    Stage Execution Strategy: UNGROUPED_EXECUTION
    - ScanFilterProject[table = awsdatacatalog:HiveTableHandle{schemaName=tpch100, 
tableName=orders, analyzePartitionValues=Optional.empty}, grouped = false, 
filterPredicate = ("o_custkey" = 123)] => [[o_orderstatus, o_orderkey]]
            LAYOUT: tpch100.orders
            o_orderstatus := o_orderstatus:string:2:REGULAR
            o_custkey := o_custkey:int:1:REGULAR
            o_orderkey := o_orderkey:int:0:REGULAR
```

예제 쿼리는 성능 향상을 위해 크로스 조인으로 최적화되었습니다. 결과는 `tpch100.orders`가 `BROADCAST` 배포 유형으로 배포될 예정임을 보여줍니다. 이는 `tpch100.orders` 테이블이 조인 작업을 수행하는 모든 노드에 배포될 것임을 의미합니다. `BROADCAST` 배포 유형은 `tpch100.orders` 테이블의 모든 필터링된 결과가 조인 작업을 수행하는 각 노드의 메모리에 부합할 것을 요구합니다.

그러나 `tpch100.customer` 테이블은 `tpch100.orders`보다 작습니다. `tpch100.customer`는 더 적은 메모리를 필요로 하므로 쿼리를 `tpch100.orders` 대신에 `BROADCAST tpch100.customer`에 다시 작성할 수 있습니다. 이렇게 하면 쿼리가 `EXCEEDED_LOCAL_MEMORY_LIMIT` 오류를 수신할 가능성이 낮아집니다. 이 전략은 다음 사항을 가정합니다.
+ `tpch100.customer.c_custkey`가 `tpch100.customer` 테이블에서 고유합니다.
+ `tpch100.customer`와 `tpch100.orders` 사이에 일대다 매핑 관계가 있습니다.

다음 예제에서는 다시 작성된 쿼리를 보여줍니다.

```
SELECT 
    c.c_custkey,
    o.o_orderkey,
    o.o_orderstatus
FROM tpch100.orders o
JOIN tpch100.customer c -- the filtered results of tpch100.customer are distributed to all nodes.
    ON c.c_custkey = o.o_custkey 
WHERE c.c_custkey = 123
```

### 예제 5: EXPLAIN 쿼리를 사용하여 영향이 없는 조건자 제거
<a name="athena-explain-statement-example-remove-unneeded-predicates"></a>

`EXPLAIN` 쿼리를 사용하여 필터링 조건자의 영향을 확인할 수 있습니다. 다음 예제와 같이 결과를 사용하여 영향이 없는 조건자를 제거할 수 있습니다.

```
EXPLAIN
   SELECT 
      c.c_name
   FROM tpch100.customer c
   WHERE c.c_custkey = CAST(RANDOM() * 1000 AS INT)
   AND c.c_custkey BETWEEN 1000 AND 2000
   AND c.c_custkey = 1500
```

#### 결과
<a name="athena-explain-statement-example-remove-unneeded-predicates-results"></a>

```
Query Plan
- Output[c_name] => [[c_name]]
    - RemoteExchange[GATHER] => [[c_name]]
        - ScanFilterProject[table = 
awsdatacatalog:HiveTableHandle{schemaName=tpch100, 
tableName=customer, analyzePartitionValues=Optional.empty}, 
filterPredicate = (("c_custkey" = 1500) AND ("c_custkey" = 
CAST(("random"() * 1E3) AS int)))] => [[c_name]]
                LAYOUT: tpch100.customer
                c_custkey := c_custkey:int:0:REGULAR
                c_name := c_name:string:1:REGULAR
```

결과의 `filterPredicate`는 옵티마이저가 원래 세 개인 조건자를 두 개의 조건자로 병합하고 애플리케이션의 순서를 변경했음을 보여줍니다.

```
filterPredicate = (("c_custkey" = 1500) AND ("c_custkey" = CAST(("random"() * 1E3) AS int)))
```

결과에 따르면 `AND c.c_custkey BETWEEN 1000 AND 2000` 조건자는 아무런 영향이 없으므로 쿼리 결과의 변화 없이 이 조건자를 제거할 수 있습니다.

`EXPLAIN` 쿼리의 결과에 사용된 용어에 관한 자세한 내용은 [Athena EXPLAIN 문 결과 이해](athena-explain-statement-understanding.md) 단원을 참조하세요.

## EXPLAIN ANALYZE 예제
<a name="athena-explain-analyze-examples"></a>

다음 예에서는 `EXPLAIN ANALYZE` 쿼리 및 출력 예를 보여줍니다.

### 예제 1: EXPLAIN ANALYZE를 사용하여 텍스트 형식으로 쿼리 계획 및 계산 비용 표시
<a name="athena-explain-analyze-example-cflogs-text"></a>

다음 예에서 `EXPLAIN ANALYZE`는 CloudFront 로그의 `SELECT` 쿼리에 대한 실행 계획 및 계산 비용을 보여줍니다. 형식은 기본적으로 텍스트 출력입니다.

```
EXPLAIN ANALYZE SELECT FROM cloudfront_logs LIMIT 10
```

#### 결과
<a name="athena-explain-analyze-example-cflogs-text-results"></a>

```
 Fragment 1
     CPU: 24.60ms, Input: 10 rows (1.48kB); per task: std.dev.: 0.00, Output: 10 rows (1.48kB)
     Output layout: [date, time, location, bytes, requestip, method, host, uri, status, referrer,\
       os, browser, browserversion]
Limit[10] => [[date, time, location, bytes, requestip, method, host, uri, status, referrer, os,\
  browser, browserversion]]
             CPU: 1.00ms (0.03%), Output: 10 rows (1.48kB)
             Input avg.: 10.00 rows, Input std.dev.: 0.00%
LocalExchange[SINGLE] () => [[date, time, location, bytes, requestip, method, host, uri, status, referrer, os,\
 browser, browserversion]]
                 CPU: 0.00ns (0.00%), Output: 10 rows (1.48kB)
                 Input avg.: 0.63 rows, Input std.dev.: 387.30%
RemoteSource[2] => [[date, time, location, bytes, requestip, method, host, uri, status, referrer, os,\
  browser, browserversion]]
                     CPU: 1.00ms (0.03%), Output: 10 rows (1.48kB)
                     Input avg.: 0.63 rows, Input std.dev.: 387.30%

 Fragment 2
     CPU: 3.83s, Input: 998 rows (147.21kB); per task: std.dev.: 0.00, Output: 20 rows (2.95kB)
     Output layout: [date, time, location, bytes, requestip, method, host, uri, status, referrer, os,\
       browser, browserversion]
LimitPartial[10] => [[date, time, location, bytes, requestip, method, host, uri, status, referrer, os,\
  browser, browserversion]]
             CPU: 5.00ms (0.13%), Output: 20 rows (2.95kB)
             Input avg.: 166.33 rows, Input std.dev.: 141.42%
TableScan[awsdatacatalog:HiveTableHandle{schemaName=default, tableName=cloudfront_logs,\
  analyzePartitionValues=Optional.empty}, 
grouped = false] => [[date, time, location, bytes, requestip, method, host, uri, st
                 CPU: 3.82s (99.82%), Output: 998 rows (147.21kB)
                 Input avg.: 166.33 rows, Input std.dev.: 141.42%
                 LAYOUT: default.cloudfront_logs
                 date := date:date:0:REGULAR
                 referrer := referrer:string:9:REGULAR
                 os := os:string:10:REGULAR
                 method := method:string:5:REGULAR
                 bytes := bytes:int:3:REGULAR
                 browser := browser:string:11:REGULAR
                 host := host:string:6:REGULAR
                 requestip := requestip:string:4:REGULAR
                 location := location:string:2:REGULAR
                 time := time:string:1:REGULAR
                 uri := uri:string:7:REGULAR
                 browserversion := browserversion:string:12:REGULAR
                 status := status:int:8:REGULAR
```

### 예제 2: EXPLAIN ANALYZE를 사용하여 JSON 형식으로 쿼리 계획 표시
<a name="athena-explain-analyze-example-cflogs-json"></a>

다음 예에서는 CloudFront 로그 기반 `SELECT` 쿼리에 대한 실행 계획 및 계산 비용을 보여줍니다. 이 예에서는 JSON을 출력 형식으로 지정합니다.

```
EXPLAIN ANALYZE (FORMAT JSON) SELECT * FROM cloudfront_logs LIMIT 10
```

#### 결과
<a name="athena-explain-analyze-example-cflogs-json-results"></a>

```
{ 
    "fragments": [{ 
        "id": "1", 
 
        "stageStats": { 
            "totalCpuTime": "3.31ms", 
            "inputRows": "10 rows", 
            "inputDataSize": "1514B", 
            "stdDevInputRows": "0.00", 
            "outputRows": "10 rows", 
            "outputDataSize": "1514B" 
        }, 
        "outputLayout": "date, time, location, bytes, requestip, method, host,\
           uri, status, referrer, os, browser, browserversion", 
 
        "logicalPlan": { 
            "1": [{ 
                "name": "Limit", 
                "identifier": "[10]", 
                "outputs": ["date", "time", "location", "bytes", "requestip", "method", "host",\
                  "uri", "status", "referrer", "os", "browser", "browserversion"], 
                "details": "", 
                "distributedNodeStats": { 
                    "nodeCpuTime": "0.00ns", 
                    "nodeOutputRows": 10, 
                    "nodeOutputDataSize": "1514B", 
                    "operatorInputRowsStats": [{ 
                        "nodeInputRows": 10.0, 
                        "nodeInputRowsStdDev": 0.0 
                    }] 
                }, 
                "children": [{ 
                    "name": "LocalExchange", 
                    "identifier": "[SINGLE] ()", 
                    "outputs": ["date", "time", "location", "bytes", "requestip", "method", "host",\
                      "uri", "status", "referrer", "os", "browser", "browserversion"], 
                    "details": "", 
                    "distributedNodeStats": { 
                        "nodeCpuTime": "0.00ns", 
                        "nodeOutputRows": 10, 
                        "nodeOutputDataSize": "1514B", 
                        "operatorInputRowsStats": [{ 
                            "nodeInputRows": 0.625, 
                            "nodeInputRowsStdDev": 387.2983346207417 
                        }] 
                    }, 
                    "children": [{ 
                        "name": "RemoteSource", 
                        "identifier": "[2]", 
                        "outputs": ["date", "time", "location", "bytes", "requestip", "method", "host",\
                          "uri", "status", "referrer", "os", "browser", "browserversion"], 
                        "details": "", 
                        "distributedNodeStats": { 
                            "nodeCpuTime": "0.00ns", 
                            "nodeOutputRows": 10, 
                            "nodeOutputDataSize": "1514B", 
                            "operatorInputRowsStats": [{ 
                                "nodeInputRows": 0.625, 
                                "nodeInputRowsStdDev": 387.2983346207417 
                            }] 
                        }, 
                        "children": [] 
                    }] 
                }] 
            }] 
        } 
    }, { 
        "id": "2", 
 
        "stageStats": { 
            "totalCpuTime": "1.62s", 
            "inputRows": "500 rows", 
            "inputDataSize": "75564B", 
            "stdDevInputRows": "0.00", 
            "outputRows": "10 rows", 
            "outputDataSize": "1514B" 
        }, 
        "outputLayout": "date, time, location, bytes, requestip, method, host, uri, status,\
           referrer, os, browser, browserversion", 
 
        "logicalPlan": { 
            "1": [{ 
                "name": "LimitPartial", 
                "identifier": "[10]", 
                "outputs": ["date", "time", "location", "bytes", "requestip", "method", "host", "uri",\
                  "status", "referrer", "os", "browser", "browserversion"], 
                "details": "", 
                "distributedNodeStats": { 
                    "nodeCpuTime": "0.00ns", 
                    "nodeOutputRows": 10, 
                    "nodeOutputDataSize": "1514B", 
                    "operatorInputRowsStats": [{ 
                        "nodeInputRows": 83.33333333333333, 
                        "nodeInputRowsStdDev": 223.60679774997897 
                    }] 
                }, 
                "children": [{ 
                    "name": "TableScan", 
                    "identifier": "[awsdatacatalog:HiveTableHandle{schemaName=default,\
                       tableName=cloudfront_logs, analyzePartitionValues=Optional.empty},\
                       grouped = false]", 
                    "outputs": ["date", "time", "location", "bytes", "requestip", "method", "host", "uri",\
                       "status", "referrer", "os", "browser", "browserversion"], 
                    "details": "LAYOUT: default.cloudfront_logs\ndate := date:date:0:REGULAR\nreferrer :=\
                       referrer: string:9:REGULAR\nos := os:string:10:REGULAR\nmethod := method:string:5:\
                       REGULAR\nbytes := bytes:int:3:REGULAR\nbrowser := browser:string:11:REGULAR\nhost :=\
                       host:string:6:REGULAR\nrequestip := requestip:string:4:REGULAR\nlocation :=\
                       location:string:2:REGULAR\ntime := time:string:1: REGULAR\nuri := uri:string:7:\
                       REGULAR\nbrowserversion := browserversion:string:12:REGULAR\nstatus :=\
                       status:int:8:REGULAR\n", 
                    "distributedNodeStats": { 
                        "nodeCpuTime": "1.62s", 
                        "nodeOutputRows": 500, 
                        "nodeOutputDataSize": "75564B", 
                        "operatorInputRowsStats": [{ 
                            "nodeInputRows": 83.33333333333333, 
                            "nodeInputRowsStdDev": 223.60679774997897 
                        }] 
                    }, 
                    "children": [] 
                }] 
            }] 
        } 
    }] 
}
```

## 추가 리소스
<a name="athena-explain-statement-additional-resources"></a>

자세한 내용은 다음 리소스를 참조하세요.
+  [Athena EXPLAIN 문 결과 이해](athena-explain-statement-understanding.md)
+  [SQL 쿼리에 대한 실행 계획 보기](query-plans.md)
+  [완료된 쿼리에 대한 통계 및 실행 세부 정보 보기](query-stats.md)
+ Trino [https://trino.io/docs/current/sql/explain.html](https://trino.io/docs/current/sql/explain.html) 설명서
+ Trino [https://trino.io/docs/current/sql/explain-analyze.html](https://trino.io/docs/current/sql/explain-analyze.html) 설명서
+  *AWS 빅 데이터 블로그*의 [Optimize Federated Query Performance using EXPLAIN and EXPLAIN ANALYZE in Amazon Athena](https://aws.amazon.com/blogs/big-data/optimize-federated-query-performance-using-explain-and-explain-analyze-in-amazon-athena/) 

[![AWS Videos](http://img.youtube.com/vi/https://www.youtube.com/embed/7JUyTqglmNU/0.jpg)](http://www.youtube.com/watch?v=https://www.youtube.com/embed/7JUyTqglmNU)


# Athena EXPLAIN 문 결과 이해
<a name="athena-explain-statement-understanding"></a>

이 주제에서는 Athena `EXPLAIN` 문 결과에 사용되는 연산 용어에 대한 간략한 안내를 제공합니다.

## EXPLAIN 문 출력 유형
<a name="athena-explain-statement-understanding-explain-plan-types"></a>

`EXPLAIN` 문 출력은 다음 두 유형 중 하나입니다.
+ **논리적 계획(Logical plan)** - SQL 엔진이 문을 실행하는 데 사용하는 논리적 계획을 표시합니다. 이 옵션의 구문은 `EXPLAIN` 또는 `EXPLAIN (TYPE LOGICAL)`입니다.
+ **배포된 계획(Distributed plan)** - 배포된 환경의 실행 계획을 표시합니다. 출력은 처리 단계인 조각들을 보여줍니다. 각 계획 조각은 하나 이상의 노드에서 처리됩니다. 조각을 처리하는 노드 간에는 데이터를 교환할 수 있습니다. 이 옵션의 구문은 `EXPLAIN (TYPE DISTRIBUTED)`입니다.

  배포된 계획의 출력에서 조각(처리 단계)은 `Fragment` *number* [*fragment\$1type*]으로 표시됩니다. 여기서 *number*는 0부터 시작하는 정수이고 *fragment\$1type*은 조각이 노드에 의해 실행되는 방식을 지정합니다. 데이터 교환의 레이아웃에 대한 통찰력을 제공하는 조각 유형은 다음 표에 설명되어 있습니다.  
**배포된 계획 조각 유형**    
[\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/ko_kr/athena/latest/ug/athena-explain-statement-understanding.html)

## Exchange
<a name="athena-explain-statement-understanding-exchange-types"></a>

교환 관련 용어는 작업자 노드 간에 데이터가 교환되는 방식을 설명합니다. 전송은 로컬 또는 원격일 수 있습니다.

**LocalExchange [*exchange\$1type*] **  
쿼리의 여러 단계에 대해 작업자 노드 내에서 로컬로 데이터를 전송합니다. *exchange\$1type*의 값은 이 단원의 뒤에서 설명한 대로 논리적 교환 또는 배포된 교환 유형 중 하나일 수 있습니다.

**RemoteExchange [*exchange\$1type*] **  
쿼리의 여러 단계에 대해 작업자 노드 간에 데이터를 전송합니다. *exchange\$1type*의 값은 이 단원의 뒤에서 설명한 대로 논리적 교환 또는 배포된 교환 유형 중 하나일 수 있습니다.

### 논리적 교환 유형
<a name="athena-explain-statement-understanding-exchange-types-logical"></a>

다음 교환 유형은 논리적 계획의 교환 단계 중에 수행되는 작업에 대해 설명합니다.
+ **`GATHER`** - 하나의 작업자 노드가 다른 모든 작업자 노드로부터 출력을 수집합니다. 예를 들어, select 쿼리의 마지막 단계에서 모든 노드의 결과를 수집하고 결과를 Amazon S3에 기록합니다.
+ **`REPARTITION`** - 다음 연산자에 적용하는 데 필요한 분할 스키마를 기반으로 특정 작업자에게 행 데이터를 보냅니다.
+ **`REPLICATE`** - 행 데이터를 모든 작업자에게 복사합니다.

### 배포된 교환 유형
<a name="athena-explain-statement-understanding-exchange-types-distributed"></a>

다음 교환 유형은 배포된 계획의 노드 간에 데이터가 교환될 때 데이터의 레이아웃을 나타냅니다.
+ **`HASH`** - 교환이 해시 함수를 사용하여 여러 대상에 데이터를 배포합니다.
+ **`SINGLE`** - 교환이 하나의 대상에 데이터를 배포합니다.

## 스캔
<a name="athena-explain-statement-understanding-scanning"></a>

다음 용어는 쿼리 중에 데이터를 스캔하는 방식을 설명합니다.

**TableScan **  
Amazon S3 또는 Apache Hive 커넥터에서 온 테이블의 소스 데이터를 스캔하고 필터 조건자로부터 발생된 파티션 정리를 적용합니다.

**ScanFilter **  
Amazon S3 또는 Apache Hive 커넥터에서 온 테이블의 소스 데이터를 스캔하고 필터 조건자 및 파티션 정리 전반에 적용되지 않은 추가 필터 조건자로부터 발생된 파티션 정리를 적용합니다.

**ScanFilterProject **  
먼저 Amazon S3 또는 Apache Hive 커넥터에서 온 테이블의 소스 데이터를 스캔하고 필터 조건자 및 파티션 정리 전반에 적용되지 않은 추가 필터 조건자로부터 발생된 파티션 정리를 적용합니다. 그런 다음 출력 데이터의 메모리 레이아웃을 새로운 프로젝션으로 수정하여 후속 단계의 성능을 향상시킵니다.

## 조인
<a name="athena-explain-statement-understanding-join"></a>

두 테이블 간에 데이터를 조인합니다. 조인은 조인 유형 및 배포 유형별로 범주화할 수 있습니다.

### 조인 유형
<a name="athena-explain-statement-understanding-join-types"></a>

조인 유형은 조인 작업이 발생하는 방식을 정의합니다.

**CrossJoin** - 조인된 두 테이블의 데카르트 곱을 산출합니다.

**InnerJoin** - 두 테이블에서 일치하는 값을 가진 레코드를 선택합니다.

**LeftJoin** - 왼쪽 테이블의 모든 레코드와 오른쪽 테이블의 일치하는 레코드를 선택합니다. 일치하는 항목이 없으면 오른쪽의 결과는 NULL입니다.

**RightJoin** - 오른쪽 테이블의 모든 레코드와 왼쪽 테이블의 일치하는 레코드를 선택합니다. 일치하는 항목이 없으면 왼쪽의 결과는 NULL입니다.

**FullJoin** - 왼쪽 또는 오른쪽 테이블 레코드에서 일치항목이 있는 모든 레코드를 선택합니다. 조인된 테이블에는 두 테이블의 모든 레코드가 포함되며 양쪽에서 누락된 일치 항목에 대해서는 NULL을 채웁니다.

**참고**  
성능상의 이유로 쿼리 엔진은 조인 쿼리를 다른 조인 유형으로 다시 작성하여 같은 결과를 낼 수 있습니다. 예를 들어, 하나의 테이블에 조건자가 있는 내부 조인 쿼리는 `CrossJoin`으로 다시 작성할 수 있습니다. 이렇게 하면 조건자가 테이블의 스캔 단계로 푸시다운되어 더 적은 데이터가 검색됩니다.

### 조인 배포 유형
<a name="athena-explain-statement-understanding-join-distribution-types"></a>

배포 유형은 조인 작업이 수행될 때 작업자 노드 간에 데이터가 교환되는 방식을 정의합니다.

**분할식(Partitioned)** - 왼쪽 테이블과 오른쪽 테이블이 모든 작업자 노드에서 모두 해시 분할됩니다. 분할식 배포는 각 노드에서 더 적은 메모리를 소비합니다. 분할된 배포는 복제된 조인보다 훨씬 느릴 수 있습니다. 두 개의 큰 테이블을 조인할 때 분할식 조인이 적합합니다.

**복제식(Replicated)**- 조인 작업을 수행하기 위해 한 테이블은 모든 작업자 노드에서 해시 분할되고 다른 테이블은 모든 작업자 노드에 복제됩니다. 복제식 배포는 분할식 조인보다 훨씬 빠를 수 있지만 각 작업자 노드에서 더 많은 메모리를 소비합니다. 복제된 테이블이 너무 크면 작업자 노드에서 메모리 부족 오류가 발생할 수 있습니다. 조인된 테이블 중 하나가 작은 경우 복제식 조인이 적합합니다.