This is an automated email from the ASF dual-hosted git repository.

gortiz pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pinot.git


The following commit(s) were added to refs/heads/master by this push:
     new 6826ae6de3f Remove the write-only op chain cache from 
OpChainSchedulerService (#19443)
6826ae6de3f is described below

commit 6826ae6de3fd96f6c74f35cdbe54d23125a68fc6
Author: Gonzalo Ortiz Jaureguizar <[email protected]>
AuthorDate: Fri Sep 4 18:34:00 2026 +0200

    Remove the write-only op chain cache from OpChainSchedulerService (#19443)
---
 .../runtime/executor/OpChainSchedulerService.java  | 72 +++-------------------
 .../apache/pinot/spi/utils/CommonConstants.java    |  9 +++
 2 files changed, 19 insertions(+), 62 deletions(-)

diff --git 
a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/executor/OpChainSchedulerService.java
 
b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/executor/OpChainSchedulerService.java
index b12ef7a1813..cc20b3d8408 100644
--- 
a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/executor/OpChainSchedulerService.java
+++ 
b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/executor/OpChainSchedulerService.java
@@ -25,7 +25,6 @@ import com.google.common.util.concurrent.FutureCallback;
 import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFutureTask;
 import com.google.common.util.concurrent.MoreExecutors;
-import java.util.ArrayList;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.ExecutorService;
@@ -36,7 +35,6 @@ import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReadWriteLock;
 import java.util.concurrent.locks.ReentrantReadWriteLock;
 import javax.annotation.Nullable;
-import org.apache.commons.lang3.tuple.Pair;
 import org.apache.pinot.common.datatable.StatMap;
 import org.apache.pinot.common.metrics.MseMeter;
 import org.apache.pinot.common.metrics.MseMetrics;
@@ -69,7 +67,6 @@ public class OpChainSchedulerService {
   private final String _instanceId;
   /// This [ExecutorService] must be wrapped with 
[QueryThreadContext#contextAwareExecutorService].
   private final ExecutorService _executorService;
-  private final Cache<OpChainId, Pair<MultiStageOperator, 
QueryExecutionContext>> _opChainCache;
   private final ReadWriteLock[] _queryLocks;
   private final Cache<Long, Boolean> _cancelledQueryCache;
   private final Metrics _metrics = new Metrics();
@@ -85,19 +82,11 @@ public class OpChainSchedulerService {
   /// `_activeOpChainsByRequest.compute(requestId, …)`. The compute() bin-lock 
for the requestId key serializes
   /// all register and decrement operations, keeping the two maps coherent. 
cancel() acquires the query write lock
   /// BEFORE calling compute() so that register()'s read lock cannot overlap 
with the eviction window.
-  ///
-  /// NOTE: `_opChainCache.put()` in registerInternal must stay inside the 
read lock. cancel()'s cache-invalidation
-  /// forEach runs OUTSIDE the write lock (after the cancelled-query cache is 
written). It relies on the read lock
-  /// exclusion to guarantee that no register() call is mid-flight between the 
cache.put and the counter increment
-  /// when the forEach observes the cache entry.
   private final ConcurrentMap<Long, QueryExecutionContext> 
_executionContextByRequest = new ConcurrentHashMap<>();
   private final ConcurrentMap<Long, AtomicInteger> _activeOpChainsByRequest = 
new ConcurrentHashMap<>();
 
   public OpChainSchedulerService(String instanceId, ExecutorService 
executorService, PinotConfiguration config) {
-    this(instanceId, executorService, 
config.getProperty(MultiStageQueryRunner.KEY_OF_OP_STATS_CACHE_SIZE,
-            MultiStageQueryRunner.DEFAULT_OF_OP_STATS_CACHE_SIZE),
-        
config.getProperty(MultiStageQueryRunner.KEY_OF_OP_STATS_CACHE_EXPIRE_MS,
-            MultiStageQueryRunner.DEFAULT_OF_OP_STATS_CACHE_EXPIRE_MS),
+    this(instanceId, executorService,
         
config.getProperty(MultiStageQueryRunner.KEY_OF_CANCELLED_QUERY_CACHE_SIZE,
             MultiStageQueryRunner.DEFAULT_OF_CANCELLED_QUERY_CACHE_SIZE),
         
config.getProperty(MultiStageQueryRunner.KEY_OF_CANCELLED_QUERY_CACHE_EXPIRE_MS,
@@ -106,21 +95,14 @@ public class OpChainSchedulerService {
 
   @VisibleForTesting
   public OpChainSchedulerService(ExecutorService executorService) {
-    this("testServer", executorService, 
MultiStageQueryRunner.DEFAULT_OF_OP_STATS_CACHE_SIZE,
-        MultiStageQueryRunner.DEFAULT_OF_OP_STATS_CACHE_EXPIRE_MS,
-        MultiStageQueryRunner.DEFAULT_OF_CANCELLED_QUERY_CACHE_SIZE,
+    this("testServer", executorService, 
MultiStageQueryRunner.DEFAULT_OF_CANCELLED_QUERY_CACHE_SIZE,
         MultiStageQueryRunner.DEFAULT_OF_CANCELLED_QUERY_CACHE_EXPIRE_MS);
   }
 
-  private OpChainSchedulerService(String instanceId, ExecutorService 
executorService, int opStatsCacheSize,
-      long opStatsCacheExpireMs, int cancelledQueryCacheSize, long 
cancelledQueryCacheExpireMs) {
+  private OpChainSchedulerService(String instanceId, ExecutorService 
executorService, int cancelledQueryCacheSize,
+      long cancelledQueryCacheExpireMs) {
     _instanceId = instanceId;
     _executorService = executorService;
-    _opChainCache = CacheBuilder.newBuilder()
-        .weigher((OpChainId k, Pair<MultiStageOperator, QueryExecutionContext> 
v) -> countOperators(v.getLeft()))
-        .maximumWeight(opStatsCacheSize)
-        .expireAfterWrite(opStatsCacheExpireMs, TimeUnit.MILLISECONDS)
-        .build();
     _queryLocks = new ReadWriteLock[NUM_QUERY_LOCKS];
     for (int i = 0; i < NUM_QUERY_LOCKS; i++) {
       _queryLocks[i] = new ReentrantReadWriteLock();
@@ -175,7 +157,6 @@ public class OpChainSchedulerService {
     OpChainId opChainId = operatorChain.getId();
     long requestId = opChainId.getRequestId();
     MultiStageOperator rootOperator = operatorChain.getRoot();
-    _opChainCache.put(opChainId, Pair.of(rootOperator, executionContext));
     // Track the context for O(1) cancel and increment the per-request active 
opchain count.
     // Both operations are performed inside a single compute() call so that a 
concurrent decrementActiveOpChains()
     // that observes count==0 and removes the context entry cannot race with a 
new putIfAbsent arriving between the
@@ -216,7 +197,6 @@ public class OpChainSchedulerService {
               + ". Stats: " + stats);
         } else {
           LOGGER.debug("({}): Completed {}", operatorChain, stats);
-          _opChainCache.invalidate(opChainId);
         }
       }
     }, null);
@@ -257,15 +237,12 @@ public class OpChainSchedulerService {
       _executorService.submit(listenableFutureTask);
     } catch (RuntimeException e) {
       // The MSE executor is wrapped in HardLimitExecutor + heap throttling, 
so submit() can throw
-      // RejectedExecutionException. When it does, the task never runs, so 
neither the directExecutor FutureCallback
-      // above (which decrements the active-opchain counter and removes the 
per-request context entry) nor runJob's
-      // _opChainCache.invalidate() ever fires. Back out both here: invalidate 
the cache entry this method put()
-      // above so it does not pin the rootOperator tree and 
QueryExecutionContext until TTL/weight eviction (the same
-      // prompt release cancel() performs for cancelled entries), and back out 
the active-opchain bookkeeping so the
-      // per-request context entry does not leak until a later cancel. Then 
rethrow so the caller propagates the
-      // failure as a stage error. The caller, 
QueryRunner#processQueryBlocking, close()s the op chain on this
-      // rethrow, releasing operator resources that the never-fired 
FutureCallback would otherwise have closed.
-      _opChainCache.invalidate(opChainId);
+      // RejectedExecutionException. When it does, the task never runs, so the 
directExecutor FutureCallback above
+      // (which decrements the active-opchain counter and removes the 
per-request context entry) never fires. Back
+      // that bookkeeping out here so the per-request context entry does not 
leak until a later cancel, then rethrow
+      // so the caller propagates the failure as a stage error. The caller, 
QueryRunner#processQueryBlocking,
+      // close()s the op chain on this rethrow, releasing operator resources 
that the never-fired FutureCallback
+      // would otherwise have closed.
       decrementActiveOpChains(requestId);
       throw e;
     }
@@ -351,17 +328,6 @@ public class OpChainSchedulerService {
     } finally {
       writeLock.unlock();
     }
-    // Promptly release memory held by cancelled opchain entries; without 
explicit invalidation they would linger
-    // in the cache until TTL expiry. Running this forEach outside the write 
lock is safe: by the time the write
-    // lock was released, the cancelledQueryCache entry was already written, 
so no new register() calls for this
-    // requestId can add entries. Any register() that was already holding the 
read lock when cancel() began must
-    // have completed (it released the read lock for cancel() to acquire the 
write lock), so its _opChainCache
-    // entry is already visible to this iterator. Double-invalidation of an 
already-evicted entry is a no-op.
-    _opChainCache.asMap().forEach((id, pair) -> {
-      if (id.getRequestId() == requestId) {
-        _opChainCache.invalidate(id);
-      }
-    });
     QueryExecutionContext context = ctxRef.get();
     if (context != null) {
       // terminate() interrupts all registered tasks (via addTask) and sets 
the termination flag so that
@@ -370,24 +336,6 @@ public class OpChainSchedulerService {
     }
   }
 
-  /// Counts the number of operators in the tree rooted at the given operator.
-  private int countOperators(MultiStageOperator root) {
-    // This stack will have at most 2 elements on most stages given that there 
is only 1 join in a stage
-    // and joins only have 2 children.
-    // Some operators (like SetOperator) can have more than 2 children, but 
they are not common.
-    ArrayList<MultiStageOperator> stack = new ArrayList<>(8);
-    stack.add(root);
-    int result = 0;
-    while (!stack.isEmpty()) {
-      result++;
-      MultiStageOperator operator = stack.remove(stack.size() - 1);
-      if (operator.getChildOperators() != null) {
-        stack.addAll(operator.getChildOperators());
-      }
-    }
-    return result;
-  }
-
   private ReadWriteLock getQueryLock(long requestId) {
     return _queryLocks[(int) (requestId & QUERY_LOCK_MASK)];
   }
diff --git 
a/pinot-spi/src/main/java/org/apache/pinot/spi/utils/CommonConstants.java 
b/pinot-spi/src/main/java/org/apache/pinot/spi/utils/CommonConstants.java
index 65fea298890..50cb94b4a15 100644
--- a/pinot-spi/src/main/java/org/apache/pinot/spi/utils/CommonConstants.java
+++ b/pinot-spi/src/main/java/org/apache/pinot/spi/utils/CommonConstants.java
@@ -2748,11 +2748,20 @@ public class CommonConstants {
 
     /// Max number of rows operators stored in the op stats cache.
     /// Although the cache stores stages, each entry has a weight equal to the 
number of operators in the stage.
+    ///
+    /// @deprecated No longer read. The op stats cache it sized was removed 
once its only reader disappeared;
+    /// the key is kept so existing configurations keep starting.
+    @Deprecated
     public static final String KEY_OF_OP_STATS_CACHE_SIZE = 
"pinot.server.query.op.stats.cache.size";
+    @Deprecated
     public static final int DEFAULT_OF_OP_STATS_CACHE_SIZE = 10000;
 
     /// Max time to keep the op stats in the cache.
+    ///
+    /// @deprecated No longer read. See [#KEY_OF_OP_STATS_CACHE_SIZE].
+    @Deprecated
     public static final String KEY_OF_OP_STATS_CACHE_EXPIRE_MS = 
"pinot.server.query.op.stats.cache.ms";
+    @Deprecated
     public static final int DEFAULT_OF_OP_STATS_CACHE_EXPIRE_MS = 600 * 1000;
 
     /// Max number of cancelled queries to keep in the cache.


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to