
사용자는 실행 계획을 분석함으로써 쿼리 복잡성을 파악하여 간소화하고 런타임을 개선할 수 있습니다. 실행 전에 EXPLAIN 명령문을 사용하여 SQL 구문의 유효성을 검사할 수도 있는데, 이렇게 하면 실행 중에 발생할 수 있는 오류를 방지하는 데 도움이 됩니다.
EXPLAIN을 사용하는 방식은 presto에서도 동일하게 사용 가능합니다. 하지만 presto의 경우 다양한 데이터 소스 위에서 동작하는 분산 처리 SQL엔진이므로 다른 데이터베이스에서의 실행 계획과 다소 다릅니다. presto의 동작방식을 이해하고 결과를 해석하려고 한 부분에 대해서 공유하려고 합니다.
Presto는 Facebook 개발자가 여러 데이터 소스에서 대량의 데이터에 대해 대화형 분석을 실행하기 위해 만든 오픈소스 분산 SQL 쿼리 엔진입니다. Presto는 모든 데이터 분석 및 개방형 레이크하우스를 위한 하나의 간단한 ANSI SQL 인터페이스를 제공하는 빠르고 안정적인 엔진입니다.
자세한 설명은 주제와 멀어질 수 있으므로 https://prestodb.io/what-is-presto.html#why_presto 를 참조하시기 바랍니다.
실행 계획을 이해하기 위해 presto의 작동 방식을 먼저 이해해야합니다.
Presto는 하나의 Coordinator와 다수의 Worker로 구성되어 있습니다.

출처 : Presto_SQL_on_Everything
EXPLAIN [ ( option [, ...] ) ] statement
option
FORMAT { TEXT | GRAPHVIZ | JSON }
TYPE { LOGICAL | DISTRIBUTED | VALIDATE | IO }
명령문의 logical 또는 distributed execution plan을 표시하거나 명령문의 validate를 검사합니다. fragmented plan을 보려면 TYPE DISTRIBUTED 옵션을 사용하십시오.
presto:tiny> EXPLAIN SELECT regionkey, count(*) FROM nation GROUP BY 1;
Query Plan
----------------------------------------------------------------------------------------------------------
- Output[regionkey, _col1] => [regionkey:bigint, count:bigint]
_col1 := count
- RemoteExchange[GATHER] => regionkey:bigint, count:bigint
- Aggregate(FINAL)[regionkey] => [regionkey:bigint, count:bigint]
count := "count"("count_8")
- LocalExchange[HASH][$hashvalue] ("regionkey") => regionkey:bigint, count_8:bigint, $hashvalue:bigint
- RemoteExchange[REPARTITION][$hashvalue_9] => regionkey:bigint, count_8:bigint, $hashvalue_9:bigint
- Project[] => [regionkey:bigint, count_8:bigint, $hashvalue_10:bigint]
$hashvalue_10 := "combine_hash"(BIGINT '0', COALESCE("$operator$hash_code"("regionkey"), 0))
- Aggregate(PARTIAL)[regionkey] => [regionkey:bigint, count_8:bigint]
count_8 := "count"(*)
- TableScan[tpch:tpch:nation:sf0.1, originalConstraint = true] => [regionkey:bigint]
regionkey := tpch:regionkey
Logical Plan을 읽는 방법.
Exchange: worker noder간에 데이터가 교환되는 방식. local or remote
LocalExchange [exchange_type]: 쿼리의 여러 단계에 대해 작업자 노드 내에서 로컬로 데이터를 전송합니다.
RemoteExchange [exchange_type]: 쿼리의 여러 단계에 대해 작업자 노드 간에 데이터를 전송합니다.
Exchange types
Logical Exchange types
Distributed Exchange types
Project : 테이블에서 선택한 column과 변환된 값을 사용하여 다음 추가 처리를 위해 다음 operator로 전달할 수 있습니다.
SCAN: data를 스캔하는 방식
presto:tiny> EXPLAIN (TYPE DISTRIBUTED) SELECT regionkey, count(*) FROM nation GROUP BY 1;
Query Plan
----------------------------------------------------------------------------------------------
Fragment 0 [SINGLE]
Output layout: [regionkey, count]
Output partitioning: SINGLE []
- Output[regionkey, _col1] => [regionkey:bigint, count:bigint]
_col1 := count
- RemoteSource[1] => [regionkey:bigint, count:bigint]
Fragment 1 [HASH]
Output layout: [regionkey, count]
Output partitioning: SINGLE []
- Aggregate(FINAL)[regionkey] => [regionkey:bigint, count:bigint]
count := "count"("count_8")
- LocalExchange[HASH][$hashvalue] ("regionkey") => regionkey:bigint, count_8:bigint, $hashvalue:bigint
- RemoteSource[2] => [regionkey:bigint, count_8:bigint, $hashvalue_9:bigint]
Fragment 2 [SOURCE]
Output layout: [regionkey, count_8, $hashvalue_10]
Output partitioning: HASH [regionkey][$hashvalue_10]
- Project[] => [regionkey:bigint, count_8:bigint, $hashvalue_10:bigint]
$hashvalue_10 := "combine_hash"(BIGINT '0', COALESCE("$operator$hash_code"("regionkey"), 0))
- Aggregate(PARTIAL)[regionkey] => [regionkey:bigint, count_8:bigint]
count_8 := "count"(*)
- TableScan[tpch:tpch:nation:sf0.1, originalConstraint = true] => [regionkey:bigint]
regionkey := tpch:regionkey
Distributed Plan을 읽는 방법.
fragment type은 Presto node에서 fragment가 실행되는 방식과 fragment 간에 배포되는 방식을 지정합니다.
presto:tiny> EXPLAIN (TYPE VALIDATE) SELECT regionkey, count(*) FROM nation GROUP BY 1;
Valid
-------
true
presto:hive> EXPLAIN (TYPE IO, FORMAT JSON) INSERT INTO test_nation SELECT * FROM nation WHERE regionkey = 2;
Query Plan
-----------------------------------
{
"inputTableColumnInfos" : [ {
"table" : {
"catalog" : "hive",
"schemaTable" : {
"schema" : "tpch",
"table" : "nation"
}
},
"columns" : [ {
"columnName" : "regionkey",
"type" : "bigint",
"domain" : {
"nullsAllowed" : false,
"ranges" : [ {
"low" : {
"value" : "2",
"bound" : "EXACTLY"
},
"high" : {
"value" : "2",
"bound" : "EXACTLY"
}
} ]
}
} ]
} ],
"outputTable" : {
"catalog" : "hive",
"schemaTable" : {
"schema" : "tpch",
"table" : "test_nation"
}
}
}
EXPLAIN ANALYZE [VERBOSE] statement
각 작업의 수행시간이나 데이터 크기까지 알고 싶으면 EXPLAIN ANALYZE 를 사용하면 됩니다.
presto:sf1> EXPLAIN ANALYZE SELECT count(*), clerk FROM orders WHERE orderdate > date '1995-01-01' GROUP BY clerk;
Query Plan
-----------------------------------------------------------------------------------------------
Fragment 1 [HASH]
Cost: CPU 88.57ms, Input: 4000 rows (148.44kB), Output: 1000 rows (28.32kB)
Output layout: [count, clerk]
Output partitioning: SINGLE []
- Project[] => [count:bigint, clerk:varchar(15)]
Cost: 26.24%, Input: 1000 rows (37.11kB), Output: 1000 rows (28.32kB), Filtered: 0.00%
Input avg.: 62.50 lines, Input std.dev.: 14.77%
- Aggregate(FINAL)[clerk][$hashvalue] => [clerk:varchar(15), $hashvalue:bigint, count:bigint]
Cost: 16.83%, Output: 1000 rows (37.11kB)
Input avg.: 250.00 lines, Input std.dev.: 14.77%
count := "count"("count_8")
- LocalExchange[HASH][$hashvalue] ("clerk") => clerk:varchar(15), count_8:bigint, $hashvalue:bigint
Cost: 47.28%, Output: 4000 rows (148.44kB)
Input avg.: 4000.00 lines, Input std.dev.: 0.00%
- RemoteSource[2] => [clerk:varchar(15), count_8:bigint, $hashvalue_9:bigint]
Cost: 9.65%, Output: 4000 rows (148.44kB)
Input avg.: 4000.00 lines, Input std.dev.: 0.00%
Fragment 2 [tpch:orders:1500000]
Cost: CPU 14.00s, Input: 818058 rows (22.62MB), Output: 4000 rows (148.44kB)
Output layout: [clerk, count_8, $hashvalue_10]
Output partitioning: HASH [clerk][$hashvalue_10]
- Aggregate(PARTIAL)[clerk][$hashvalue_10] => [clerk:varchar(15), $hashvalue_10:bigint, count_8:bigint]
Cost: 4.47%, Output: 4000 rows (148.44kB)
Input avg.: 204514.50 lines, Input std.dev.: 0.05%
Collisions avg.: 5701.28 (17569.93% est.), Collisions std.dev.: 1.12%
count_8 := "count"(*)
- ScanFilterProject[table = tpch:tpch:orders:sf1.0, originalConstraint = ("orderdate" > "$literal$date"(BIGINT '9131')), filterPredicate = ("orderdate" > "$literal$date"(BIGINT '9131'))] => [cler
Cost: 95.53%, Input: 1500000 rows (0B), Output: 818058 rows (22.62MB), Filtered: 45.46%
Input avg.: 375000.00 lines, Input std.dev.: 0.00%
$hashvalue_10 := "combine_hash"(BIGINT '0', COALESCE("$operator$hash_code"("clerk"), 0))
orderdate := tpch:orderdate
clerk := tpch:clerk
참고. Amazon Athena의 SQL 엔진은 실제로 오픈 소스 분산 SQL 쿼리 엔진인 Presto를 기반으로 하지만 Amazon Web Services 에코시스템(aws s3, aws glue 등)에서 데이터를 쿼리하도록 맞춤화 및 최적화되었습니다.