diff --git a/presto-common/src/main/java/com/facebook/presto/common/RuntimeMetricName.java b/presto-common/src/main/java/com/facebook/presto/common/RuntimeMetricName.java index 465b1c0de3cfe..33f3867a7f0f6 100644 --- a/presto-common/src/main/java/com/facebook/presto/common/RuntimeMetricName.java +++ b/presto-common/src/main/java/com/facebook/presto/common/RuntimeMetricName.java @@ -56,4 +56,6 @@ private RuntimeMetricName() // Size of the data retrieved by read call to storage public static final String STORAGE_READ_DATA_BYTES = "storageReadDataBytes"; public static final String WRITTEN_FILES_COUNT = "writtenFilesCount"; + public static final String HISTORY_OPTIMIZER_QUERY_REGISTRATION_GET_PLAN_NODE_HASHES = "historyOptimizerQueryRegistrationGetPlanNodeHashes"; + public static final String HISTORY_OPTIMIZER_QUERY_REGISTRATION_GET_STATISTICS = "historyOptimizerQueryRegistrationGetStatistics"; } diff --git a/presto-main/src/main/java/com/facebook/presto/cost/HistoryBasedPlanStatisticsCalculator.java b/presto-main/src/main/java/com/facebook/presto/cost/HistoryBasedPlanStatisticsCalculator.java index a79b108b849cb..5db802c2e8836 100644 --- a/presto-main/src/main/java/com/facebook/presto/cost/HistoryBasedPlanStatisticsCalculator.java +++ b/presto-main/src/main/java/com/facebook/presto/cost/HistoryBasedPlanStatisticsCalculator.java @@ -36,7 +36,11 @@ import static com.facebook.presto.SystemSessionProperties.getHistoryBasedOptimizerTimeoutLimit; import static com.facebook.presto.SystemSessionProperties.getHistoryInputTableStatisticsMatchingThreshold; +import static com.facebook.presto.SystemSessionProperties.isVerboseRuntimeStatsEnabled; import static com.facebook.presto.SystemSessionProperties.useHistoryBasedPlanStatisticsEnabled; +import static com.facebook.presto.common.RuntimeMetricName.HISTORY_OPTIMIZER_QUERY_REGISTRATION_GET_PLAN_NODE_HASHES; +import static com.facebook.presto.common.RuntimeMetricName.HISTORY_OPTIMIZER_QUERY_REGISTRATION_GET_STATISTICS; +import static com.facebook.presto.common.RuntimeUnit.NANO; import static com.facebook.presto.common.plan.PlanCanonicalizationStrategy.historyBasedPlanCanonicalizationStrategyList; import static com.facebook.presto.cost.HistoricalPlanStatisticsUtil.getPredictedPlanStatistics; import static com.facebook.presto.sql.planner.iterative.Plans.resolveGroupReferences; @@ -85,17 +89,31 @@ public boolean registerPlan(PlanNode root, Session session, long startTimeInNano } ImmutableList.Builder planNodesWithHash = ImmutableList.builder(); Iterable planNodeIterable = forTree(PlanNode::getSources).depthFirstPreOrder(root); + boolean enableVerboseRuntimeStats = isVerboseRuntimeStatsEnabled(session); + long profileStartTime = 0; for (PlanNode plan : planNodeIterable) { if (checkTimeOut(startTimeInNano, timeoutInMilliseconds)) { historyBasedStatisticsCacheManager.setHistoryBasedQueryRegistrationTimeout(session.getQueryId()); return false; } if (plan.getStatsEquivalentPlanNode().isPresent()) { - planNodesWithHash.addAll(getPlanNodeHashes(plan, session).values()); + if (enableVerboseRuntimeStats) { + profileStartTime = System.nanoTime(); + } + planNodesWithHash.addAll(getPlanNodeHashes(plan, session, false).values()); + if (enableVerboseRuntimeStats) { + session.getRuntimeStats().addMetricValue(HISTORY_OPTIMIZER_QUERY_REGISTRATION_GET_PLAN_NODE_HASHES, NANO, System.nanoTime() - profileStartTime); + } } } try { + if (enableVerboseRuntimeStats) { + profileStartTime = System.nanoTime(); + } historyBasedStatisticsCacheManager.getStatisticsCache(session.getQueryId(), historyBasedPlanStatisticsProvider, getHistoryBasedOptimizerTimeoutLimit(session).toMillis()).getAll(planNodesWithHash.build()); + if (enableVerboseRuntimeStats) { + session.getRuntimeStats().addMetricValue(HISTORY_OPTIMIZER_QUERY_REGISTRATION_GET_STATISTICS, NANO, System.nanoTime() - profileStartTime); + } } catch (ExecutionException e) { throw new RuntimeException("Unable to register plan: ", e.getCause()); @@ -132,7 +150,7 @@ public Supplier getHistoryBasedPlanStatistic return historyBasedPlanStatisticsProvider; } - private Map getPlanNodeHashes(PlanNode plan, Session session) + private Map getPlanNodeHashes(PlanNode plan, Session session, boolean cacheOnly) { if (!useHistoryBasedPlanStatisticsEnabled(session) || !plan.getStatsEquivalentPlanNode().isPresent()) { return ImmutableMap.of(); @@ -142,8 +160,10 @@ private Map getPlanNodeHashes(Pl ImmutableMap.Builder allHashesBuilder = ImmutableMap.builder(); for (PlanCanonicalizationStrategy strategy : historyBasedPlanCanonicalizationStrategyList()) { - Optional hash = planCanonicalInfoProvider.hash(session, statsEquivalentPlanNode, strategy); - allHashesBuilder.put(strategy, new PlanNodeWithHash(statsEquivalentPlanNode, hash)); + Optional hash = planCanonicalInfoProvider.hash(session, statsEquivalentPlanNode, strategy, cacheOnly); + if (hash.isPresent()) { + allHashesBuilder.put(strategy, new PlanNodeWithHash(statsEquivalentPlanNode, hash)); + } } return allHashesBuilder.build(); @@ -156,7 +176,7 @@ private PlanNodeStatsEstimate getStatistics(PlanNode planNode, Session session, } PlanNode plan = resolveGroupReferences(planNode, lookup); - Map allHashes = getPlanNodeHashes(plan, session); + Map allHashes = getPlanNodeHashes(plan, session, true); Map statistics = ImmutableMap.of(); try { @@ -172,7 +192,7 @@ private PlanNodeStatsEstimate getStatistics(PlanNode planNode, Session session, for (PlanCanonicalizationStrategy strategy : historyBasedPlanCanonicalizationStrategyList()) { for (Map.Entry entry : statistics.entrySet()) { if (allHashes.containsKey(strategy) && entry.getKey().getHash().isPresent() && allHashes.get(strategy).equals(entry.getKey())) { - Optional> inputTableStatistics = getPlanNodeInputTableStatistics(plan, session); + Optional> inputTableStatistics = getPlanNodeInputTableStatistics(plan, session, true); if (inputTableStatistics.isPresent()) { PlanStatistics predictedPlanStatistics = getPredictedPlanStatistics(entry.getValue(), inputTableStatistics.get(), historyMatchingThreshold); if (predictedPlanStatistics.getConfidence() > 0) { @@ -188,13 +208,13 @@ private PlanNodeStatsEstimate getStatistics(PlanNode planNode, Session session, return delegateStats; } - private Optional> getPlanNodeInputTableStatistics(PlanNode plan, Session session) + private Optional> getPlanNodeInputTableStatistics(PlanNode plan, Session session, boolean cacheOnly) { if (!useHistoryBasedPlanStatisticsEnabled(session) || !plan.getStatsEquivalentPlanNode().isPresent()) { return Optional.empty(); } PlanNode statsEquivalentPlanNode = plan.getStatsEquivalentPlanNode().get(); - return planCanonicalInfoProvider.getInputTableStatistics(session, statsEquivalentPlanNode); + return planCanonicalInfoProvider.getInputTableStatistics(session, statsEquivalentPlanNode, cacheOnly); } } diff --git a/presto-main/src/main/java/com/facebook/presto/sql/planner/CachingPlanCanonicalInfoProvider.java b/presto-main/src/main/java/com/facebook/presto/sql/planner/CachingPlanCanonicalInfoProvider.java index 57604e0085ccd..1e724ba2fc59a 100644 --- a/presto-main/src/main/java/com/facebook/presto/sql/planner/CachingPlanCanonicalInfoProvider.java +++ b/presto-main/src/main/java/com/facebook/presto/sql/planner/CachingPlanCanonicalInfoProvider.java @@ -14,6 +14,7 @@ package com.facebook.presto.sql.planner; import com.facebook.presto.Session; +import com.facebook.presto.SystemSessionProperties; import com.facebook.presto.common.plan.PlanCanonicalizationStrategy; import com.facebook.presto.cost.HistoryBasedStatisticsCacheManager; import com.facebook.presto.metadata.Metadata; @@ -36,6 +37,7 @@ import java.util.Optional; import static com.facebook.presto.SystemSessionProperties.getHistoryBasedOptimizerTimeoutLimit; +import static com.facebook.presto.common.RuntimeUnit.NANO; import static com.facebook.presto.common.plan.PlanCanonicalizationStrategy.historyBasedPlanCanonicalizationStrategyList; import static com.google.common.hash.Hashing.sha256; import static java.nio.charset.StandardCharsets.UTF_8; @@ -57,42 +59,68 @@ public CachingPlanCanonicalInfoProvider(HistoryBasedStatisticsCacheManager histo } @Override - public Optional hash(Session session, PlanNode planNode, PlanCanonicalizationStrategy strategy) + public Optional hash(Session session, PlanNode planNode, PlanCanonicalizationStrategy strategy, boolean cacheOnly) { CacheKey key = new CacheKey(planNode, strategy); - return loadValue(session, key).map(PlanNodeCanonicalInfo::getHash); + return loadValue(session, key, cacheOnly).map(PlanNodeCanonicalInfo::getHash); } @Override - public Optional> getInputTableStatistics(Session session, PlanNode planNode) + public Optional> getInputTableStatistics(Session session, PlanNode planNode, boolean cacheOnly) { CacheKey key = new CacheKey(planNode, historyBasedPlanCanonicalizationStrategyList().get(0)); - return loadValue(session, key).map(PlanNodeCanonicalInfo::getInputTableStatistics); + return loadValue(session, key, cacheOnly).map(PlanNodeCanonicalInfo::getInputTableStatistics); } - private Optional loadValue(Session session, CacheKey key) + private Optional loadValue(Session session, CacheKey key, boolean cacheOnly) { long startTimeInNano = System.nanoTime(); + long profileStartTime = 0; long timeoutInMilliseconds = getHistoryBasedOptimizerTimeoutLimit(session).toMillis(); + boolean enableVerboseRuntimeStats = SystemSessionProperties.isVerboseRuntimeStatsEnabled(session); Map cache = historyBasedStatisticsCacheManager.getCanonicalInfoCache(session.getQueryId()); PlanNodeCanonicalInfo result = cache.get(key); - if (result != null) { - return Optional.of(result); + if (result != null || cacheOnly) { + return Optional.ofNullable(result); } CanonicalPlanGenerator.Context context = new CanonicalPlanGenerator.Context(); + if (enableVerboseRuntimeStats) { + profileStartTime = System.nanoTime(); + } key.getNode().accept(new CanonicalPlanGenerator(key.getStrategy(), objectMapper, session), context); + if (enableVerboseRuntimeStats) { + profileTime("CanonicalPlanGenerator", profileStartTime, session); + } + if (loadValueTimeout(startTimeInNano, timeoutInMilliseconds)) { + return Optional.empty(); + } for (Map.Entry entry : context.getCanonicalPlans().entrySet()) { CanonicalPlan canonicalPlan = entry.getValue(); PlanNode plan = entry.getKey(); + if (enableVerboseRuntimeStats) { + profileStartTime = System.nanoTime(); + } String hashValue = hashCanonicalPlan(canonicalPlan, objectMapper); + if (enableVerboseRuntimeStats) { + profileTime("HashCanonicalPlan", profileStartTime, session); + } + if (loadValueTimeout(startTimeInNano, timeoutInMilliseconds)) { + return Optional.empty(); + } // Compute input table statistics for the plan node. This is useful in history based optimizations, // where historical plan statistics are reused if input tables are similar in size across runs. ImmutableList.Builder inputTableStatisticsBuilder = ImmutableList.builder(); + if (enableVerboseRuntimeStats) { + profileStartTime = System.nanoTime(); + } for (TableScanNode scanNode : context.getInputTables().get(plan)) { if (loadValueTimeout(startTimeInNano, timeoutInMilliseconds)) { - break; + return Optional.empty(); } - inputTableStatisticsBuilder.add(getPlanStatisticsForTable(session, scanNode)); + inputTableStatisticsBuilder.add(getPlanStatisticsForTable(session, scanNode, enableVerboseRuntimeStats)); + } + if (enableVerboseRuntimeStats) { + profileTime("GetPlanStatisticsForTable", profileStartTime, session); } cache.put(new CacheKey(plan, key.getStrategy()), new PlanNodeCanonicalInfo(hashValue, inputTableStatisticsBuilder.build())); } @@ -107,7 +135,12 @@ private boolean loadValueTimeout(long startTimeInNano, long timeoutInMillisecond return NANOSECONDS.toMillis(System.nanoTime() - startTimeInNano) > timeoutInMilliseconds; } - private PlanStatistics getPlanStatisticsForTable(Session session, TableScanNode table) + private void profileTime(String name, long startProfileTime, Session session) + { + session.getRuntimeStats().addMetricValue(String.format("CachingPlanCanonicalInfoProvider:%s", name), NANO, System.nanoTime() - startProfileTime); + } + + private PlanStatistics getPlanStatisticsForTable(Session session, TableScanNode table, boolean profileRuntime) { InputTableCacheKey key = new InputTableCacheKey(new TableHandle( table.getTable().getConnectorId(), @@ -119,7 +152,14 @@ private PlanStatistics getPlanStatisticsForTable(Session session, TableScanNode if (planStatistics != null) { return planStatistics; } + long startProfileTime = 0; + if (profileRuntime) { + startProfileTime = System.nanoTime(); + } TableStatistics tableStatistics = metadata.getTableStatistics(session, key.getTableHandle(), key.getColumnHandles(), key.getConstraint()); + if (profileRuntime) { + profileTime("ReadFromMetaData", startProfileTime, session); + } planStatistics = new PlanStatistics(tableStatistics.getRowCount(), tableStatistics.getTotalSize(), 1, JoinNodeStatistics.empty(), TableWriterNodeStatistics.empty()); cache.put(key, planStatistics); return planStatistics; diff --git a/presto-main/src/main/java/com/facebook/presto/sql/planner/PlanCanonicalInfoProvider.java b/presto-main/src/main/java/com/facebook/presto/sql/planner/PlanCanonicalInfoProvider.java index 2bd36313a84a1..afc673ad41364 100644 --- a/presto-main/src/main/java/com/facebook/presto/sql/planner/PlanCanonicalInfoProvider.java +++ b/presto-main/src/main/java/com/facebook/presto/sql/planner/PlanCanonicalInfoProvider.java @@ -32,16 +32,18 @@ public interface PlanCanonicalInfoProvider * @param session Session for query being run * @param planNode Plan node to hash * @param strategy Strategy to canonicalize the plan node + * @param cacheOnly Only fetch from cache, and return Optional.empty() if set to true and no entry found in cache * @return Hash of the plan node. Returns Optional.empty() if unable to hash. */ - Optional hash(Session session, PlanNode planNode, PlanCanonicalizationStrategy strategy); + Optional hash(Session session, PlanNode planNode, PlanCanonicalizationStrategy strategy, boolean cacheOnly); /** * Canonicalize the plan, and return statistics of input tables. Output order is consistent with * plan canonicalization. * @param session Session for query being run * @param planNode Plan node to hash + * @param cacheOnly Only fetch from cache, and return Optional.empty() if set to true and no entry found in cache * @return Statistics of leaf input tables to plan node, ordered by a consistent canonicalization strategy. */ - Optional> getInputTableStatistics(Session session, PlanNode planNode); + Optional> getInputTableStatistics(Session session, PlanNode planNode, boolean cacheOnly); } diff --git a/presto-main/src/main/java/com/facebook/presto/sql/planner/PlanNodeCanonicalInfo.java b/presto-main/src/main/java/com/facebook/presto/sql/planner/PlanNodeCanonicalInfo.java index 9d45e987458b7..8401b96b5d876 100644 --- a/presto-main/src/main/java/com/facebook/presto/sql/planner/PlanNodeCanonicalInfo.java +++ b/presto-main/src/main/java/com/facebook/presto/sql/planner/PlanNodeCanonicalInfo.java @@ -84,8 +84,8 @@ public static List getCanonicalInfo( continue; } PlanNode statsEquivalentPlanNode = node.getStatsEquivalentPlanNode().get(); - Optional hash = planCanonicalInfoProvider.hash(session, statsEquivalentPlanNode, strategy); - Optional> inputTableStatistics = planCanonicalInfoProvider.getInputTableStatistics(session, statsEquivalentPlanNode); + Optional hash = planCanonicalInfoProvider.hash(session, statsEquivalentPlanNode, strategy, true); + Optional> inputTableStatistics = planCanonicalInfoProvider.getInputTableStatistics(session, statsEquivalentPlanNode, true); if (hash.isPresent() && inputTableStatistics.isPresent()) { result.add(new CanonicalPlanWithInfo(new CanonicalPlan(statsEquivalentPlanNode, strategy), new PlanNodeCanonicalInfo(hash.get(), inputTableStatistics.get()))); } diff --git a/presto-main/src/test/java/com/facebook/presto/sql/planner/TestCachingPlanCanonicalInfoProvider.java b/presto-main/src/test/java/com/facebook/presto/sql/planner/TestCachingPlanCanonicalInfoProvider.java index 8064113204f95..200c964eb854d 100644 --- a/presto-main/src/test/java/com/facebook/presto/sql/planner/TestCachingPlanCanonicalInfoProvider.java +++ b/presto-main/src/test/java/com/facebook/presto/sql/planner/TestCachingPlanCanonicalInfoProvider.java @@ -33,6 +33,7 @@ import static com.facebook.presto.testing.TestingSession.testSessionBuilder; import static com.google.common.graph.Traverser.forTree; import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; public class TestCachingPlanCanonicalInfoProvider @@ -72,13 +73,25 @@ public void testCache() return; } for (PlanCanonicalizationStrategy strategy : historyBasedPlanCanonicalizationStrategyList()) { - planCanonicalInfoProvider.hash(session, child.getStatsEquivalentPlanNode().get(), strategy).get(); + planCanonicalInfoProvider.hash(session, child.getStatsEquivalentPlanNode().get(), strategy, false).get(); } }); // Assert that size of cache remains same, meaning all needed hashes were already cached. assertEquals(planCanonicalInfoProvider.getCacheSize(), 5L * historyBasedPlanCanonicalizationStrategyList().size()); planCanonicalInfoProvider.getHistoryBasedStatisticsCacheManager().invalidate(session.getQueryId()); assertEquals(planCanonicalInfoProvider.getCacheSize(), 0); + + forTree(PlanNode::getSources).depthFirstPreOrder(root).forEach(child -> { + if (!child.getStatsEquivalentPlanNode().isPresent()) { + return; + } + for (PlanCanonicalizationStrategy strategy : historyBasedPlanCanonicalizationStrategyList()) { + // Only read from cache, hence will return Optional.empty() as the cache is already invalidated + assertFalse(planCanonicalInfoProvider.hash(session, child.getStatsEquivalentPlanNode().get(), strategy, true).isPresent()); + } + }); + // Assert that cache is not populated as we only read from cache without populating with cache miss + assertEquals(planCanonicalInfoProvider.getCacheSize(), 0); } private Session createSession() diff --git a/presto-spark-base/src/test/java/com/facebook/presto/spark/planner/TestPrestoSparkStatsCalculator.java b/presto-spark-base/src/test/java/com/facebook/presto/spark/planner/TestPrestoSparkStatsCalculator.java index c0459fe173ba9..f44759eced9d1 100644 --- a/presto-spark-base/src/test/java/com/facebook/presto/spark/planner/TestPrestoSparkStatsCalculator.java +++ b/presto-spark-base/src/test/java/com/facebook/presto/spark/planner/TestPrestoSparkStatsCalculator.java @@ -116,7 +116,7 @@ public void testUsesHboStatsWhenMatchRuntime() .registerVariable(planBuilder.variable("c1")) .filter(planBuilder.rowExpression("c1 IS NOT NULL"), planBuilder.values(planBuilder.variable("c1"))); - Optional hash = historyBasedPlanStatisticsCalculator.getPlanCanonicalInfoProvider().hash(session, statsEquivalentRemoteSource, REMOVE_SAFE_CONSTANTS); + Optional hash = historyBasedPlanStatisticsCalculator.getPlanCanonicalInfoProvider().hash(session, statsEquivalentRemoteSource, REMOVE_SAFE_CONSTANTS, false); InMemoryHistoryBasedPlanStatisticsProvider historyBasedPlanStatisticsProvider = (InMemoryHistoryBasedPlanStatisticsProvider) historyBasedPlanStatisticsCalculator.getHistoryBasedPlanStatisticsProvider().get(); historyBasedPlanStatisticsProvider.putStats(ImmutableMap.of(