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

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


The following commit(s) were added to refs/heads/master by this push:
     new 8c8db7b834b minor: add segmentAcquireMode query context to control MSQ 
engine AcquireMode (#20562)
8c8db7b834b is described below

commit 8c8db7b834b78ad2305e6b587a390ab43251fd1f
Author: Clint Wylie <[email protected]>
AuthorDate: Tue Oct 13 23:15:50 2026 -0700

    minor: add segmentAcquireMode query context to control MSQ engine 
AcquireMode (#20562)
---
 .../msq/EmbeddedMSQPartialDownloadsTest.java       | 43 ++++++++++++++++++++++
 .../druid/msq/querykit/BaseLeafStageProcessor.java | 23 +++++-------
 .../groupby/GroupByPreShuffleStageProcessor.java   | 10 +----
 .../msq/querykit/scan/ScanQueryStageProcessor.java | 12 +-----
 .../java/org/apache/druid/query/QueryContext.java  | 15 ++++++++
 .../java/org/apache/druid/query/QueryContexts.java |  7 ++++
 .../apache/druid/segment/loading/AcquireMode.java  |  5 ++-
 .../org/apache/druid/query/QueryContextTest.java   | 23 ++++++++++++
 8 files changed, 106 insertions(+), 32 deletions(-)

diff --git 
a/embedded-tests/src/test/java/org/apache/druid/testing/embedded/msq/EmbeddedMSQPartialDownloadsTest.java
 
b/embedded-tests/src/test/java/org/apache/druid/testing/embedded/msq/EmbeddedMSQPartialDownloadsTest.java
index af3581b6563..1c8a3c58e2b 100644
--- 
a/embedded-tests/src/test/java/org/apache/druid/testing/embedded/msq/EmbeddedMSQPartialDownloadsTest.java
+++ 
b/embedded-tests/src/test/java/org/apache/druid/testing/embedded/msq/EmbeddedMSQPartialDownloadsTest.java
@@ -26,6 +26,7 @@ import org.apache.druid.java.util.common.StringUtils;
 import org.apache.druid.msq.indexing.report.MSQTaskReportPayload;
 import org.apache.druid.msq.util.MultiStageQueryContext;
 import org.apache.druid.query.DruidProcessingConfigTest;
+import org.apache.druid.query.QueryContexts;
 import org.apache.druid.query.http.SqlTaskStatus;
 import org.apache.druid.server.metrics.LatchableEmitter;
 import org.apache.druid.server.metrics.StorageMonitor;
@@ -59,6 +60,10 @@ import java.util.Objects;
  * on-demand deep-storage range reads (so the count is positive), while the 
full-download path does not (the count
  * stays zero because {@code virtualStoragePartialDownloadsEnabled=false} 
makes the worker mount segments as complete
  * cache entries and download them in full instead of via range reads).
+ * <p>
+ * Separately, {@link QueryContexts#SEGMENT_ACQUIRE_MODE} overrides the leaf 
processor's default acquire mode while
+ * partial downloads stay enabled; forcing {@code FULL} shows up as more 
{@link StorageMonitor#VSF_LOAD_BYTES} than the
+ * default {@code PARTIAL} acquire for the same query.
  */
 class EmbeddedMSQPartialDownloadsTest extends EmbeddedClusterTestBase
 {
@@ -151,6 +156,44 @@ class EmbeddedMSQPartialDownloadsTest extends 
EmbeddedClusterTestBase
     );
   }
 
+  @Test
+  void testSegmentAcquireModeQueryContextOverride()
+  {
+    final LatchableEmitter emitter = indexer.latchableEmitter();
+
+    // The GroupBy leaf processor defaults to AcquireMode.PARTIAL, so only the 
columns the query touches are pulled
+    // from deep storage. Forcing FULL via the query context makes the worker 
download the entire segment up front
+    // instead, even though partial downloads remain enabled.
+    final long partialLoadBytes = runMsqSelectAndGetLoadBytes(emitter, 
Collections.emptyMap());
+    final long fullLoadBytes = runMsqSelectAndGetLoadBytes(
+        emitter,
+        Map.of(QueryContexts.SEGMENT_ACQUIRE_MODE, "FULL")
+    );
+
+    Assertions.assertTrue(partialLoadBytes > 0, "expected the default PARTIAL 
acquire to download some bytes");
+    Assertions.assertTrue(
+        fullLoadBytes > partialLoadBytes,
+        StringUtils.format(
+            "expected the FULL acquire to download more bytes[%,d] than the 
PARTIAL acquire[%,d]",
+            fullLoadBytes,
+            partialLoadBytes
+        )
+    );
+  }
+
+  /**
+   * Runs {@link #SELECT_SQL} with the given context and returns the {@link 
StorageMonitor#VSF_LOAD_BYTES} the worker
+   * pulled from deep storage. Each worker task has its own ephemeral cache, 
so every run starts cold.
+   */
+  private long runMsqSelectAndGetLoadBytes(LatchableEmitter emitter, 
Map<String, Object> queryContext)
+  {
+    emitter.awaitMetricQuiescent(StorageMonitor.VSF_LOAD_BYTES, 
MONITOR_QUIESCE_TIMEOUT_MILLIS);
+    emitter.flush();
+    runMsqSelect(queryContext);
+    emitter.awaitMetricQuiescent(StorageMonitor.VSF_LOAD_BYTES, 
MONITOR_QUIESCE_TIMEOUT_MILLIS);
+    return emitter.getMetricEventLongSum(StorageMonitor.VSF_LOAD_BYTES);
+  }
+
   private void runMsqSelect(Map<String, Object> queryContext)
   {
     final SqlTaskStatus status = msqApis.submitTaskSql(queryContext, 
SELECT_SQL, dataSource);
diff --git 
a/multi-stage-query/src/main/java/org/apache/druid/msq/querykit/BaseLeafStageProcessor.java
 
b/multi-stage-query/src/main/java/org/apache/druid/msq/querykit/BaseLeafStageProcessor.java
index 608ea80fe21..42b1866fcbc 100644
--- 
a/multi-stage-query/src/main/java/org/apache/druid/msq/querykit/BaseLeafStageProcessor.java
+++ 
b/multi-stage-query/src/main/java/org/apache/druid/msq/querykit/BaseLeafStageProcessor.java
@@ -266,19 +266,15 @@ public abstract class BaseLeafStageProcessor extends 
BasicStageProcessor
 
 
   /**
-   * Create the {@link ReadableInputQueue} for this stage's base inputs. 
Override to change how the queue acquires
-   * segments: the default acquires them fully up front ({@link 
AcquireMode#FULL}), which is safe for any processor.
-   * Leaf processors that read segments through the async cursor API
-   * ({@link org.apache.druid.segment.CursorFactory#makeCursorHolderAsync}) 
can override to {@link AcquireMode#PARTIAL}
-   * so only the columns a query actually touches are downloaded.
+   * How the {@link ReadableInputQueue} for this stage's base inputs acquires 
segments, unless overridden by
+   * {@link org.apache.druid.query.QueryContexts#SEGMENT_ACQUIRE_MODE}. The 
default acquires them fully up front
+   * ({@link AcquireMode#FULL}), which is safe for any processor. Leaf 
processors that read segments through the async
+   * cursor API ({@link 
org.apache.druid.segment.CursorFactory#makeCursorHolderAsync}) can override to
+   * {@link AcquireMode#PARTIAL} so only the columns a query actually touches 
are downloaded.
    */
-  protected ReadableInputQueue makeReadableInputQueue(
-      final StandardPartitionReader partitionReader,
-      final List<PhysicalInputSlice> slices,
-      final int loadahead
-  )
+  protected AcquireMode getDefaultAcquireMode()
   {
-    return new ReadableInputQueue(partitionReader, slices, loadahead, 
AcquireMode.FULL);
+    return AcquireMode.FULL;
   }
 
   /**
@@ -311,10 +307,11 @@ public abstract class BaseLeafStageProcessor extends 
BasicStageProcessor
     }
 
     final List<PhysicalInputSlice> filteredSlices = 
filterBaseInput(physicalInputSlices);
-    return makeReadableInputQueue(
+    return new ReadableInputQueue(
         new StandardPartitionReader(context),
         filteredSlices,
-        context.segmentLoadAheadCount()
+        context.segmentLoadAheadCount(),
+        
context.workOrder().getWorkerContext().getSegmentAcquireMode(getDefaultAcquireMode())
     );
   }
 
diff --git 
a/multi-stage-query/src/main/java/org/apache/druid/msq/querykit/groupby/GroupByPreShuffleStageProcessor.java
 
b/multi-stage-query/src/main/java/org/apache/druid/msq/querykit/groupby/GroupByPreShuffleStageProcessor.java
index 208c57eefe4..986084b23af 100644
--- 
a/multi-stage-query/src/main/java/org/apache/druid/msq/querykit/groupby/GroupByPreShuffleStageProcessor.java
+++ 
b/multi-stage-query/src/main/java/org/apache/druid/msq/querykit/groupby/GroupByPreShuffleStageProcessor.java
@@ -30,13 +30,11 @@ import org.apache.druid.frame.processor.FrameProcessor;
 import org.apache.druid.frame.write.FrameWriterFactory;
 import org.apache.druid.msq.exec.ExecutionContext;
 import org.apache.druid.msq.exec.FrameContext;
-import org.apache.druid.msq.exec.std.StandardPartitionReader;
 import org.apache.druid.msq.exec.std.StandardStageRunner;
 import org.apache.druid.msq.input.LoadableSegment;
 import org.apache.druid.msq.input.PhysicalInputSlice;
 import org.apache.druid.msq.querykit.BaseLeafStageProcessor;
 import org.apache.druid.msq.querykit.ReadableInput;
-import org.apache.druid.msq.querykit.ReadableInputQueue;
 import org.apache.druid.msq.util.MultiStageQueryContext;
 import org.apache.druid.query.QueryToolChestWarehouse;
 import org.apache.druid.query.aggregation.AggregatorFactory;
@@ -173,15 +171,11 @@ public class GroupByPreShuffleStageProcessor extends 
BaseLeafStageProcessor
   }
 
   @Override
-  protected ReadableInputQueue makeReadableInputQueue(
-      StandardPartitionReader partitionReader,
-      List<PhysicalInputSlice> slices,
-      int loadahead
-  )
+  protected AcquireMode getDefaultAcquireMode()
   {
     // GroupBy reads segments through the async cursor API, so acquire them 
partially: only the columns the query
     // touches are downloaded.
-    return new ReadableInputQueue(partitionReader, slices, loadahead, 
AcquireMode.PARTIAL);
+    return AcquireMode.PARTIAL;
   }
 
   @Override
diff --git 
a/multi-stage-query/src/main/java/org/apache/druid/msq/querykit/scan/ScanQueryStageProcessor.java
 
b/multi-stage-query/src/main/java/org/apache/druid/msq/querykit/scan/ScanQueryStageProcessor.java
index 1e7fbe215c5..3ab7ce98b9b 100644
--- 
a/multi-stage-query/src/main/java/org/apache/druid/msq/querykit/scan/ScanQueryStageProcessor.java
+++ 
b/multi-stage-query/src/main/java/org/apache/druid/msq/querykit/scan/ScanQueryStageProcessor.java
@@ -28,11 +28,8 @@ import org.apache.druid.frame.channel.WritableFrameChannel;
 import org.apache.druid.frame.processor.FrameProcessor;
 import org.apache.druid.frame.write.FrameWriterFactory;
 import org.apache.druid.msq.exec.FrameContext;
-import org.apache.druid.msq.exec.std.StandardPartitionReader;
-import org.apache.druid.msq.input.PhysicalInputSlice;
 import org.apache.druid.msq.querykit.BaseLeafStageProcessor;
 import org.apache.druid.msq.querykit.ReadableInput;
-import org.apache.druid.msq.querykit.ReadableInputQueue;
 import org.apache.druid.query.DataSource;
 import org.apache.druid.query.Druids;
 import org.apache.druid.query.filter.DimFilter;
@@ -45,7 +42,6 @@ import org.apache.druid.segment.loading.AcquireMode;
 import org.apache.druid.sql.calcite.filtration.Filtration;
 
 import javax.annotation.Nullable;
-import java.util.List;
 import java.util.concurrent.atomic.AtomicLong;
 
 @JsonTypeName("scan")
@@ -129,15 +125,11 @@ public class ScanQueryStageProcessor extends 
BaseLeafStageProcessor
   }
 
   @Override
-  protected ReadableInputQueue makeReadableInputQueue(
-      StandardPartitionReader partitionReader,
-      List<PhysicalInputSlice> slices,
-      int loadahead
-  )
+  protected AcquireMode getDefaultAcquireMode()
   {
     // Scan reads segments through the async cursor API, so acquire them 
partially: only the columns the query
     // touches are downloaded.
-    return new ReadableInputQueue(partitionReader, slices, loadahead, 
AcquireMode.PARTIAL);
+    return AcquireMode.PARTIAL;
   }
 
   @Override
diff --git a/processing/src/main/java/org/apache/druid/query/QueryContext.java 
b/processing/src/main/java/org/apache/druid/query/QueryContext.java
index dcb9b4f4319..cc4de1c0f6a 100644
--- a/processing/src/main/java/org/apache/druid/query/QueryContext.java
+++ b/processing/src/main/java/org/apache/druid/query/QueryContext.java
@@ -34,6 +34,7 @@ import org.apache.druid.query.context.QueryContextParameter;
 import org.apache.druid.query.context.QueryContextParameters;
 import org.apache.druid.query.filter.InDimFilter;
 import org.apache.druid.query.filter.TypedInFilter;
+import org.apache.druid.segment.loading.AcquireMode;
 
 import javax.annotation.Nullable;
 import java.io.IOException;
@@ -997,4 +998,18 @@ public class QueryContext
     }
     return QueryContexts.DEFAULT_REALTIME_SEGMENTS_MODE;
   }
+
+  /**
+   * Returns the {@link AcquireMode} set by {@link 
QueryContexts#SEGMENT_ACQUIRE_MODE}, or {@code defaultValue} (the
+   * engine's own choice) if absent. Currently only honored by MSQ leaf stage 
processors.
+   * <p>
+   * {@link AcquireMode#PARTIAL} degrades to {@link AcquireMode#FULL} where 
partial downloads are unavailable, such as
+   * when they are disabled by configuration or the segment is not 
range-readable. Forcing {@link AcquireMode#PARTIAL}
+   * where the engine defaults to {@link AcquireMode#FULL} fails at cursor 
creation if the engine reads segments through
+   * the synchronous cursor API.
+   */
+  public AcquireMode getSegmentAcquireMode(AcquireMode defaultValue)
+  {
+    return getEnum(QueryContexts.SEGMENT_ACQUIRE_MODE, AcquireMode.class, 
defaultValue);
+  }
 }
diff --git a/processing/src/main/java/org/apache/druid/query/QueryContexts.java 
b/processing/src/main/java/org/apache/druid/query/QueryContexts.java
index 2c2204a8683..928eb10f1d5 100644
--- a/processing/src/main/java/org/apache/druid/query/QueryContexts.java
+++ b/processing/src/main/java/org/apache/druid/query/QueryContexts.java
@@ -132,6 +132,13 @@ public class QueryContexts
   public static final String FORCE_PROJECTION = "forceProjections";
   public static final String USE_PROJECTION = "useProjection";
 
+  /**
+   * Overrides the {@link org.apache.druid.segment.loading.AcquireMode} an 
engine uses to acquire segments from the
+   * segment cache; see {@link QueryContext#getSegmentAcquireMode}. Intended 
for testing, debugging, and performance
+   * comparison.
+   */
+  public static final String SEGMENT_ACQUIRE_MODE = "segmentAcquireMode";
+
   // Unique identifier for the query, that is used to map the global shared 
resources (specifically merge buffers) to the
   // query's runtime
   public static final String QUERY_RESOURCE_ID = "queryResourceId";
diff --git 
a/server/src/main/java/org/apache/druid/segment/loading/AcquireMode.java 
b/processing/src/main/java/org/apache/druid/segment/loading/AcquireMode.java
similarity index 90%
rename from 
server/src/main/java/org/apache/druid/segment/loading/AcquireMode.java
rename to 
processing/src/main/java/org/apache/druid/segment/loading/AcquireMode.java
index 28b58fb0849..60c0199a671 100644
--- a/server/src/main/java/org/apache/druid/segment/loading/AcquireMode.java
+++ b/processing/src/main/java/org/apache/druid/segment/loading/AcquireMode.java
@@ -20,9 +20,12 @@
 package org.apache.druid.segment.loading;
 
 /**
- * How {@link SegmentCacheManager#acquireSegment} and {@link 
SegmentCacheManager#acquireCachedSegment} materialize a
+ * How {@code SegmentCacheManager#acquireSegment} and {@code 
SegmentCacheManager#acquireCachedSegment} materialize a
  * segment. Both modes return an open reference that callers must close when 
finished; they differ only in how much
  * of the segment is downloaded before the reference is handed back.
+ * <p>
+ * Engines that support it can be told which mode to use via
+ * {@link org.apache.druid.query.QueryContext#getSegmentAcquireMode}.
  */
 public enum AcquireMode
 {
diff --git 
a/processing/src/test/java/org/apache/druid/query/QueryContextTest.java 
b/processing/src/test/java/org/apache/druid/query/QueryContextTest.java
index 2d2b450d375..e2f3d0c08e5 100644
--- a/processing/src/test/java/org/apache/druid/query/QueryContextTest.java
+++ b/processing/src/test/java/org/apache/druid/query/QueryContextTest.java
@@ -38,6 +38,7 @@ import org.apache.druid.query.context.QueryContextParameters;
 import org.apache.druid.query.filter.DimFilter;
 import org.apache.druid.query.spec.QuerySegmentSpec;
 import org.apache.druid.segment.DimensionHandlerUtils;
+import org.apache.druid.segment.loading.AcquireMode;
 import org.joda.time.DateTimeZone;
 import org.joda.time.Duration;
 import org.joda.time.Interval;
@@ -559,6 +560,28 @@ public class QueryContextTest
     );
   }
 
+  @Test
+  public void testGetSegmentAcquireMode()
+  {
+    assertEquals(AcquireMode.FULL, 
QueryContext.empty().getSegmentAcquireMode(AcquireMode.FULL));
+    assertEquals(AcquireMode.PARTIAL, 
QueryContext.empty().getSegmentAcquireMode(AcquireMode.PARTIAL));
+    assertEquals(
+        AcquireMode.FULL,
+        QueryContext.of(ImmutableMap.of(QueryContexts.SEGMENT_ACQUIRE_MODE, 
"full"))
+                    .getSegmentAcquireMode(AcquireMode.PARTIAL)
+    );
+    assertEquals(
+        AcquireMode.PARTIAL,
+        QueryContext.of(ImmutableMap.of(QueryContexts.SEGMENT_ACQUIRE_MODE, 
"PARTIAL"))
+                    .getSegmentAcquireMode(AcquireMode.FULL)
+    );
+    assertThrows(
+        BadQueryContextException.class,
+        () -> 
QueryContext.of(ImmutableMap.of(QueryContexts.SEGMENT_ACQUIRE_MODE, "dummy"))
+                          .getSegmentAcquireMode(AcquireMode.FULL)
+    );
+  }
+
   @Test
   public void testGetRealtimeSegmentsModeBackwardCompat()
   {


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

Reply via email to