cgivre commented on code in PR #3086:
URL: https://github.com/apache/drill/pull/3086#discussion_r4389658093


##########
exec/java-exec/src/main/java/org/apache/drill/exec/planner/sql/DrillSqlWorker.java:
##########
@@ -295,7 +305,95 @@ private static PhysicalPlan getQueryPlan(QueryContext 
context, String sql, Point
       
context.getOptions().setLocalOption(ExecConstants.RETURN_RESULT_SET_FOR_DDL, 
true);
     }
 
-    return handler.getPlan(sqlNode);
+    // Keep the original SQL if no safe snapshot is available or cache 
preparation fails.
+    SqlNode planningSql = sqlNode;
+    Consumer<PhysicalPlan> prepareCacheInsert = null;
+    if (context.getOptions().getOption(PlannerSettings.PLAN_CACHE)
+        && !context.getSession().hasTemporaryTables()
+        && !hasMutableAliases(context)
+        && PlanCacheEligibility.isSafeToCache(sqlNode, 
context.getFunctionRegistry())) {
+      try {
+        PlanCache cache = context.getDrillbitContext().getPlanCache();
+        long cacheGeneration = cache.getGeneration();
+        // Capture the context once, before lookup and planning. Changes during
+        // planning invalidate this entry on its first hit.
+        PlanCache.ContextSnapshot snapshot = PlanCache.ContextSnapshot.resolve(
+            parser.getDefaultSchema(), sqlNode, context);
+        if (snapshot != null) {
+          PlanCacheParameterizer.Candidate candidate =
+              PlanCacheParameterizer.parameterize(sqlNode, 
parser.getTypeFactory());
+          String key = context.getQueryUserName() + '\n'
+              + context.getSession().getDefaultSchemaPath() + '\n'
+              + snapshot.keyFingerprint() + '\n'
+              + candidate.template;
+          PlanCache.Entry entry = cache.get(key);
+          if (entry != null) {
+            try {
+              if (entry.matchesContext(snapshot)) {
+                PhysicalPlan bound = entry.bind(candidate.literals,
+                    context.getDrillbitContext().getPlanReader());
+                PlanCache.bindTextPlan(textPlan, entry.getTextPlan(), 
candidate.literals);
+                cache.recordBind();
+                context.setPlanCacheHit();
+                logger.debug("Plan cache hit");
+                return bound;
+              }
+              logger.debug("Plan cache entry invalidated because its context 
changed; replanning");
+            } catch (RuntimeException | IOException e) {
+              logger.debug("Plan cache entry invalidated because rebinding 
failed; replanning", e);
+            }
+            cache.invalidate(key);
+          } else {
+            logger.debug("Plan cache miss");
+          }
+          cache.recordMiss();
+          final PhysicalPlanReader cacheReader =
+              context.getDrillbitContext().getPlanReader();
+          // Prepare insertion after planning; Foreman publishes it only after 
the query succeeds.
+          prepareCacheInsert = planned -> {
+            String templateTextPlan = PlanCache.bindTextPlan(textPlan, 
candidate.literals);
+            final String json;
+            try {
+              // Capture before parallelization/execution mutates the 
operators.
+              // Pending publication and the writer queue retain only 
immutable JSON.
+              json = cacheReader.writeJson(planned);
+            } catch (IOException | RuntimeException e) {
+              logger.debug("Plan could not be serialized for the plan cache; 
skipping publication", e);
+              return;
+            }
+            context.setPendingPlanCacheInsert(() -> 
cache.writeAfterSuccess(key, json,
+                templateTextPlan, cacheReader, snapshot, cacheGeneration));
+          };
+          // On a cache miss, plan the parameterized SQL to produce a reusable 
template.
+          planningSql = candidate.sql;
+        }
+      } catch (RuntimeException e) {
+        logger.debug("Plan cache attempt fell back to ordinary planning", e);
+      }
+    }
+
+    // Plan exactly once, outside cache fallback. Validation can mutate the 
SQL tree,
+    // so retrying a failed planning attempt could reuse partially rewritten 
nodes.
+    PhysicalPlan planned = handler.getPlan(planningSql);

Review Comment:
   My biggest concern are queries randomly failing and not giving the user a 
clear reason of what happened and how to fix it.  But it sounds like that won't 
happen here, so that's good!



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to