사용자는 실행 계획을 분석함으로써 쿼리 복잡성을 파악하여 간소화하고 런타임을 개선할 수 있습니다. 실행 전에 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 등)에서 데이터를 쿼리하도록 맞춤화 및 최적화되었습니다.