This is an automated email from the ASF dual-hosted git repository.
gianm 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 3e8da56c809 fix: dedicated ephemeral storage loading pool for tasks
(#19658)
3e8da56c809 is described below
commit 3e8da56c80992f472dee2690010d49ebbef6ced8
Author: Clint Wylie <[email protected]>
AuthorDate: Mon Jul 20 11:24:12 2026 -0700
fix: dedicated ephemeral storage loading pool for tasks (#19658)
---
.../druid/guice/ClusterTestingModuleTest.java | 2 +-
.../common/SegmentCacheManagerFactory.java | 43 ++++++--
.../common/SegmentCacheManagerFactoryTest.java | 109 +++++++++++++++++++++
.../task/ClientCompactionTaskQuerySerdeTest.java | 4 +-
.../common/task/CompactionTaskRunBase.java | 2 +-
.../indexing/common/task/CompactionTaskTest.java | 4 +-
.../indexing/common/task/IngestionTestBase.java | 2 +-
.../druid/indexing/common/task/TaskBuilder.java | 2 +-
.../AbstractMultiPhaseParallelIndexingTest.java | 2 +-
.../AbstractParallelIndexSupervisorTaskTest.java | 2 +-
.../compact/OverlordCompactionSchedulerTest.java | 4 +-
.../overlord/SingleTaskBackgroundRunnerTest.java | 2 +-
.../druid/indexing/overlord/TaskLifecycleTest.java | 2 +-
.../indexing/overlord/TestTaskToolboxFactory.java | 2 +-
.../SeekableStreamIndexTaskTestBase.java | 2 +-
.../indexing/worker/WorkerTaskManagerTest.java | 2 +-
.../druid/msq/test/CalciteMSQTestsHelper.java | 2 +-
.../org/apache/druid/msq/test/MSQTestBase.java | 2 +-
.../guice/annotations/EphemeralStorageLoading.java | 37 +++++++
.../org/apache/druid/guice/StorageNodeModule.java | 15 +++
.../druid/segment/loading/SegmentLoaderConfig.java | 41 ++++++++
.../segment/loading/StorageLoadingThreadPool.java | 3 +-
.../apache/druid/guice/StorageNodeModuleTest.java | 22 +++++
.../segment/loading/SegmentLoaderConfigTest.java | 18 ++++
.../loading/StorageLoadingThreadPoolTest.java | 62 ++++++++++++
.../java/org/apache/druid/cli/CliPeonTest.java | 2 +-
26 files changed, 361 insertions(+), 29 deletions(-)
diff --git
a/extensions-core/testing-tools/src/test/java/org/apache/druid/guice/ClusterTestingModuleTest.java
b/extensions-core/testing-tools/src/test/java/org/apache/druid/guice/ClusterTestingModuleTest.java
index 63decde23a5..9e98dfcda87 100644
---
a/extensions-core/testing-tools/src/test/java/org/apache/druid/guice/ClusterTestingModuleTest.java
+++
b/extensions-core/testing-tools/src/test/java/org/apache/druid/guice/ClusterTestingModuleTest.java
@@ -191,7 +191,7 @@ public class ClusterTestingModuleTest
null,
indexIO,
new NoopCoordinatorClient(),
- new SegmentCacheManagerFactory(indexIO, MAPPER),
+ SegmentCacheManagerFactory.createWithOwnedPool(indexIO, MAPPER),
new TaskConfigBuilder().build()
);
final ParallelIndexIOConfig ioConfig = new ParallelIndexIOConfig(
diff --git
a/indexing-service/src/main/java/org/apache/druid/indexing/common/SegmentCacheManagerFactory.java
b/indexing-service/src/main/java/org/apache/druid/indexing/common/SegmentCacheManagerFactory.java
index cf5dfc7d0e9..402e4be10c5 100644
---
a/indexing-service/src/main/java/org/apache/druid/indexing/common/SegmentCacheManagerFactory.java
+++
b/indexing-service/src/main/java/org/apache/druid/indexing/common/SegmentCacheManagerFactory.java
@@ -21,8 +21,12 @@ package org.apache.druid.indexing.common;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.util.Providers;
+import org.apache.druid.guice.annotations.EphemeralStorageLoading;
import org.apache.druid.guice.annotations.Json;
import org.apache.druid.segment.IndexIO;
+import org.apache.druid.segment.loading.AcquireMode;
import
org.apache.druid.segment.loading.LeastBytesUsedStorageLocationSelectorStrategy;
import org.apache.druid.segment.loading.SegmentCacheManager;
import org.apache.druid.segment.loading.SegmentLoaderConfig;
@@ -43,26 +47,52 @@ public class SegmentCacheManagerFactory
{
private final IndexIO indexIO;
private final ObjectMapper jsonMapper;
+ /**
+ * Resolved lazily (only on the {@code virtualStorage=true} path of {@link
#manufacturate}) so that injecting this
+ * factory into a component that only builds non-virtual caches (e.g. {@code
DruidInputSource}) does not force the
+ * process-wide {@link EphemeralStorageLoading} pool to be created on
processes that never do on-demand loading
+ * (Overlord, MiddleManager).
+ */
+ private final Provider<StorageLoadingThreadPool> loadingThreadPoolProvider;
@Inject
public SegmentCacheManagerFactory(
IndexIO indexIO,
- @Json ObjectMapper mapper
+ @Json ObjectMapper mapper,
+ @EphemeralStorageLoading Provider<StorageLoadingThreadPool>
loadingThreadPoolProvider
)
{
this.indexIO = indexIO;
this.jsonMapper = mapper;
+ this.loadingThreadPoolProvider = loadingThreadPoolProvider;
+ }
+
+ /**
+ * Creates a factory whose {@code virtualStorage=true} managers get their
own private loading pool of default size,
+ * instead of the process-wide, lifecycle-managed {@link
EphemeralStorageLoading} pool that Guice injects. Intended
+ * for tests and manual construction where that shared pool is not available
via injection; the created pool is not
+ * lifecycle-managed, which is fine for short-lived JVMs.
+ */
+ public static SegmentCacheManagerFactory createWithOwnedPool(IndexIO
indexIO, ObjectMapper mapper)
+ {
+ return new SegmentCacheManagerFactory(
+ indexIO,
+ mapper,
+ Providers.of(StorageLoadingThreadPool.createFromConfig(new
SegmentLoaderConfig().setVirtualStorage(true)))
+ );
}
/**
- * Creates a new {@link SegmentCacheManager} backed by a new storage
location in {@code storageDir}, and a new
- * loading thread pool of default size.
+ * Creates a new {@link SegmentCacheManager} backed by a new storage
location in {@code storageDir}. When
+ * {@code virtualStorage} is true, the returned manager uses this factory's
process-wide
+ * {@link EphemeralStorageLoading} loading pool (shared across all per-task
caches and stopped by the lifecycle)
+ * rather than creating its own.
*
* @param storageDir storage location
* @param maxSize size limit, or null for no limit
* @param virtualStorage whether to configure the cache manager in ephemeral
virtual storage mode. In this mode,
- * loading is triggered by {@link
SegmentCacheManager#acquireSegment(DataSegment)}, and
- * segment files are deleted as soon as all holds are
closed.
+ * loading is triggered by {@link
SegmentCacheManager#acquireSegment(DataSegment, AcquireMode)},
+ * and segment files are deleted as soon as all holds
are closed.
*/
public SegmentCacheManager manufacturate(File storageDir, Long maxSize,
boolean virtualStorage)
{
@@ -77,11 +107,10 @@ public class SegmentCacheManagerFactory
.setVirtualStorage(virtualStorage)
.setVirtualStorageIsEphemeral(virtualStorage);
final List<StorageLocation> storageLocations =
loaderConfig.toStorageLocations();
- final StorageLoadingThreadPool loadingThreadPool =
StorageLoadingThreadPool.createFromConfig(loaderConfig);
return new SegmentLocalCacheManager(
storageLocations,
loaderConfig,
- loadingThreadPool,
+ virtualStorage ? loadingThreadPoolProvider.get() :
StorageLoadingThreadPool.none(),
new LeastBytesUsedStorageLocationSelectorStrategy(storageLocations),
indexIO,
jsonMapper
diff --git
a/indexing-service/src/test/java/org/apache/druid/indexing/common/SegmentCacheManagerFactoryTest.java
b/indexing-service/src/test/java/org/apache/druid/indexing/common/SegmentCacheManagerFactoryTest.java
new file mode 100644
index 00000000000..87622425794
--- /dev/null
+++
b/indexing-service/src/test/java/org/apache/druid/indexing/common/SegmentCacheManagerFactoryTest.java
@@ -0,0 +1,109 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.druid.indexing.common;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.inject.Provider;
+import com.google.inject.util.Providers;
+import org.apache.druid.java.util.emitter.EmittingLogger;
+import org.apache.druid.segment.TestHelper;
+import org.apache.druid.segment.TestIndex;
+import org.apache.druid.segment.loading.SegmentCacheManager;
+import org.apache.druid.segment.loading.SegmentLoaderConfig;
+import org.apache.druid.segment.loading.StorageLoadingThreadPool;
+import org.apache.druid.server.metrics.NoopServiceEmitter;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import java.io.File;
+
+class SegmentCacheManagerFactoryTest
+{
+ @TempDir
+ File tempDir;
+
+ private ObjectMapper jsonMapper;
+
+ @BeforeAll
+ static void setUpClass()
+ {
+ EmittingLogger.registerEmitter(new NoopServiceEmitter());
+ }
+
+ @BeforeEach
+ void setUp()
+ {
+ jsonMapper = TestHelper.makeJsonMapper();
+ }
+
+ @Test
+ void testVirtualStorageManagersShareTheInjectedPool()
+ {
+ final StorageLoadingThreadPool shared =
+ StorageLoadingThreadPool.createFromConfig(new
SegmentLoaderConfig().withVirtualStorage(true));
+ try {
+ final SegmentCacheManagerFactory factory =
+ new SegmentCacheManagerFactory(TestIndex.INDEX_IO, jsonMapper,
Providers.of(shared));
+ final SegmentCacheManager m1 = factory.manufacturate(new File(tempDir,
"a"), null, true);
+ final SegmentCacheManager m2 = factory.manufacturate(new File(tempDir,
"b"), null, true);
+
+ Assertions.assertSame(shared, m1.getLoadingThreadPool());
+ Assertions.assertSame(shared, m2.getLoadingThreadPool());
+ }
+ finally {
+ shared.stop();
+ }
+ }
+
+ @Test
+ void testNonVirtualStorageDoesNotResolveTheLoadingPool()
+ {
+ // Injecting the factory into a virtualStorage=false-only consumer (e.g.
DruidInputSource) must not force the
+ // ephemeral loading pool to be created. The provider is only resolved on
the virtualStorage=true path.
+ final Provider<StorageLoadingThreadPool> throwingProvider = () -> {
+ throw new AssertionError("ephemeral loading pool must not be resolved
for virtualStorage=false");
+ };
+ final SegmentCacheManagerFactory factory =
+ new SegmentCacheManagerFactory(TestIndex.INDEX_IO, jsonMapper,
throwingProvider);
+
+ final SegmentCacheManager m = factory.manufacturate(new File(tempDir,
"c"), null, false);
+ Assertions.assertFalse(m.getLoadingThreadPool().isAvailable());
+ }
+
+ @Test
+ void testCreateWithOwnedPoolBuildsOneAvailablePoolPerFactory()
+ {
+ final SegmentCacheManagerFactory factory =
+ SegmentCacheManagerFactory.createWithOwnedPool(TestIndex.INDEX_IO,
jsonMapper);
+ final SegmentCacheManager m1 = factory.manufacturate(new File(tempDir,
"d"), null, true);
+ final SegmentCacheManager m2 = factory.manufacturate(new File(tempDir,
"e"), null, true);
+ try {
+ Assertions.assertTrue(m1.getLoadingThreadPool().isAvailable());
+ // createWithOwnedPool builds one pool and shares it across the
factory's manufacturate calls.
+ Assertions.assertSame(m1.getLoadingThreadPool(),
m2.getLoadingThreadPool());
+ }
+ finally {
+ m1.getLoadingThreadPool().stop();
+ }
+ }
+}
diff --git
a/indexing-service/src/test/java/org/apache/druid/indexing/common/task/ClientCompactionTaskQuerySerdeTest.java
b/indexing-service/src/test/java/org/apache/druid/indexing/common/task/ClientCompactionTaskQuerySerdeTest.java
index f33548e36b3..4427ddb0d93 100644
---
a/indexing-service/src/test/java/org/apache/druid/indexing/common/task/ClientCompactionTaskQuerySerdeTest.java
+++
b/indexing-service/src/test/java/org/apache/druid/indexing/common/task/ClientCompactionTaskQuerySerdeTest.java
@@ -215,7 +215,7 @@ public class ClientCompactionTaskQuerySerdeTest
binder.bind(ChatHandlerProvider.class).toInstance(new
ChatHandlerProvider());
binder.bind(RowIngestionMetersFactory.class).toInstance(ROW_INGESTION_METERS_FACTORY);
binder.bind(CoordinatorClient.class).toInstance(COORDINATOR_CLIENT);
- binder.bind(SegmentCacheManagerFactory.class).toInstance(new
SegmentCacheManagerFactory(TestIndex.INDEX_IO, objectMapper));
+
binder.bind(SegmentCacheManagerFactory.class).toInstance(SegmentCacheManagerFactory.createWithOwnedPool(TestIndex.INDEX_IO,
objectMapper));
binder.bind(AppenderatorsManager.class).toInstance(APPENDERATORS_MANAGER);
binder.bind(OverlordClient.class).toInstance(new
NoopOverlordClient());
}
@@ -379,7 +379,7 @@ public class ClientCompactionTaskQuerySerdeTest
{
CompactionTask.Builder compactionTaskBuilder = new CompactionTask.Builder(
"datasource",
- new SegmentCacheManagerFactory(TestIndex.INDEX_IO, MAPPER)
+ SegmentCacheManagerFactory.createWithOwnedPool(TestIndex.INDEX_IO,
MAPPER)
)
.inputSpec(new CompactionIntervalSpec(Intervals.of("2019/2020"),
"testSha256OfSortedSegmentIds"), true)
.tuningConfig(
diff --git
a/indexing-service/src/test/java/org/apache/druid/indexing/common/task/CompactionTaskRunBase.java
b/indexing-service/src/test/java/org/apache/druid/indexing/common/task/CompactionTaskRunBase.java
index 7d20385c465..267cc906881 100644
---
a/indexing-service/src/test/java/org/apache/druid/indexing/common/task/CompactionTaskRunBase.java
+++
b/indexing-service/src/test/java/org/apache/druid/indexing/common/task/CompactionTaskRunBase.java
@@ -233,7 +233,7 @@ public abstract class CompactionTaskRunBase
temporaryFolder.create();
reportsFile = temporaryFolder.newFile();
testUtils = new TestUtils();
- segmentCacheManagerFactory = new
SegmentCacheManagerFactory(TestIndex.INDEX_IO, testUtils.getTestObjectMapper());
+ segmentCacheManagerFactory =
SegmentCacheManagerFactory.createWithOwnedPool(TestIndex.INDEX_IO,
testUtils.getTestObjectMapper());
objectMapper = testUtils.getTestObjectMapper();
objectMapper.registerSubtypes(new NamedType(LocalLoadSpec.class, "local"));
diff --git
a/indexing-service/src/test/java/org/apache/druid/indexing/common/task/CompactionTaskTest.java
b/indexing-service/src/test/java/org/apache/druid/indexing/common/task/CompactionTaskTest.java
index 5c8a49ed482..8a8d403e160 100644
---
a/indexing-service/src/test/java/org/apache/druid/indexing/common/task/CompactionTaskTest.java
+++
b/indexing-service/src/test/java/org/apache/druid/indexing/common/task/CompactionTaskTest.java
@@ -309,7 +309,7 @@ public class CompactionTaskTest
binder.bind(RowIngestionMetersFactory.class).toInstance(TEST_UTILS.getRowIngestionMetersFactory());
binder.bind(CoordinatorClient.class).toInstance(COORDINATOR_CLIENT);
binder.bind(SegmentCacheManagerFactory.class)
- .toInstance(new
SegmentCacheManagerFactory(TestIndex.INDEX_IO, objectMapper));
+
.toInstance(SegmentCacheManagerFactory.createWithOwnedPool(TestIndex.INDEX_IO,
objectMapper));
binder.bind(AppenderatorsManager.class).toInstance(new
TestAppenderatorsManager());
binder.bind(ExprMacroTable.class).toInstance(TestExprMacroTable.INSTANCE);
}
@@ -377,7 +377,7 @@ public class CompactionTaskTest
testIndexIO,
SEGMENT_MAP
);
- segmentCacheManagerFactory = new
SegmentCacheManagerFactory(TestIndex.INDEX_IO, OBJECT_MAPPER);
+ segmentCacheManagerFactory =
SegmentCacheManagerFactory.createWithOwnedPool(TestIndex.INDEX_IO,
OBJECT_MAPPER);
}
@Test
diff --git
a/indexing-service/src/test/java/org/apache/druid/indexing/common/task/IngestionTestBase.java
b/indexing-service/src/test/java/org/apache/druid/indexing/common/task/IngestionTestBase.java
index 7ca28f7f3b7..7673350e802 100644
---
a/indexing-service/src/test/java/org/apache/druid/indexing/common/task/IngestionTestBase.java
+++
b/indexing-service/src/test/java/org/apache/druid/indexing/common/task/IngestionTestBase.java
@@ -187,7 +187,7 @@ public abstract class IngestionTestBase extends
InitializedNullHandlingTest
);
lockbox = new GlobalTaskLockbox(taskStorage, storageCoordinator);
lockbox.syncFromStorage();
- segmentCacheManagerFactory = new
SegmentCacheManagerFactory(TestIndex.INDEX_IO, getObjectMapper());
+ segmentCacheManagerFactory =
SegmentCacheManagerFactory.createWithOwnedPool(TestIndex.INDEX_IO,
getObjectMapper());
reportsFile = temporaryFolder.newFile();
dataSegmentKiller = new TestDataSegmentKiller();
taskActionToolbox = createTaskActionToolbox();
diff --git
a/indexing-service/src/test/java/org/apache/druid/indexing/common/task/TaskBuilder.java
b/indexing-service/src/test/java/org/apache/druid/indexing/common/task/TaskBuilder.java
index e5040904a90..c3bcdcab0b0 100644
---
a/indexing-service/src/test/java/org/apache/druid/indexing/common/task/TaskBuilder.java
+++
b/indexing-service/src/test/java/org/apache/druid/indexing/common/task/TaskBuilder.java
@@ -144,7 +144,7 @@ public abstract class TaskBuilder<
null,
TestHelper.getTestIndexIO(),
new NoopCoordinatorClient(),
- new SegmentCacheManagerFactory(TestHelper.getTestIndexIO(),
TestHelper.JSON_MAPPER),
+
SegmentCacheManagerFactory.createWithOwnedPool(TestHelper.getTestIndexIO(),
TestHelper.JSON_MAPPER),
new TaskConfigBuilder().build()
)
);
diff --git
a/indexing-service/src/test/java/org/apache/druid/indexing/common/task/batch/parallel/AbstractMultiPhaseParallelIndexingTest.java
b/indexing-service/src/test/java/org/apache/druid/indexing/common/task/batch/parallel/AbstractMultiPhaseParallelIndexingTest.java
index abd74439d25..ef0a730e932 100644
---
a/indexing-service/src/test/java/org/apache/druid/indexing/common/task/batch/parallel/AbstractMultiPhaseParallelIndexingTest.java
+++
b/indexing-service/src/test/java/org/apache/druid/indexing/common/task/batch/parallel/AbstractMultiPhaseParallelIndexingTest.java
@@ -257,7 +257,7 @@ abstract class AbstractMultiPhaseParallelIndexingTest
extends AbstractParallelIn
private Segment loadSegment(DataSegment dataSegment, File tempSegmentDir)
{
- final SegmentCacheManager cacheManager = new
SegmentCacheManagerFactory(TestIndex.INDEX_IO, getObjectMapper())
+ final SegmentCacheManager cacheManager =
SegmentCacheManagerFactory.createWithOwnedPool(TestIndex.INDEX_IO,
getObjectMapper())
.manufacturate(tempSegmentDir, null, false);
try {
cacheManager.load(dataSegment);
diff --git
a/indexing-service/src/test/java/org/apache/druid/indexing/common/task/batch/parallel/AbstractParallelIndexSupervisorTaskTest.java
b/indexing-service/src/test/java/org/apache/druid/indexing/common/task/batch/parallel/AbstractParallelIndexSupervisorTaskTest.java
index e147d032ad5..123003a7ad3 100644
---
a/indexing-service/src/test/java/org/apache/druid/indexing/common/task/batch/parallel/AbstractParallelIndexSupervisorTaskTest.java
+++
b/indexing-service/src/test/java/org/apache/druid/indexing/common/task/batch/parallel/AbstractParallelIndexSupervisorTaskTest.java
@@ -636,7 +636,7 @@ public class AbstractParallelIndexSupervisorTaskTest
extends IngestionTestBase
.addValue(AppenderatorsManager.class,
TestUtils.APPENDERATORS_MANAGER)
.addValue(LocalDataSegmentPuller.class, new
LocalDataSegmentPuller())
.addValue(CoordinatorClient.class, coordinatorClient)
- .addValue(SegmentCacheManagerFactory.class, new
SegmentCacheManagerFactory(TestIndex.INDEX_IO, objectMapper))
+ .addValue(SegmentCacheManagerFactory.class,
SegmentCacheManagerFactory.createWithOwnedPool(TestIndex.INDEX_IO,
objectMapper))
.addValue(RetryPolicyFactory.class, new RetryPolicyFactory(new
RetryPolicyConfig()))
.addValue(TaskConfig.class, taskConfig)
);
diff --git
a/indexing-service/src/test/java/org/apache/druid/indexing/compact/OverlordCompactionSchedulerTest.java
b/indexing-service/src/test/java/org/apache/druid/indexing/compact/OverlordCompactionSchedulerTest.java
index 3b9597cd79f..4073221223f 100644
---
a/indexing-service/src/test/java/org/apache/druid/indexing/compact/OverlordCompactionSchedulerTest.java
+++
b/indexing-service/src/test/java/org/apache/druid/indexing/compact/OverlordCompactionSchedulerTest.java
@@ -115,7 +115,7 @@ public class OverlordCompactionSchedulerTest
.Std()
.addValue(
SegmentCacheManagerFactory.class,
- new SegmentCacheManagerFactory(TestIndex.INDEX_IO,
OBJECT_MAPPER)
+
SegmentCacheManagerFactory.createWithOwnedPool(TestIndex.INDEX_IO,
OBJECT_MAPPER)
)
);
}
@@ -230,7 +230,7 @@ public class OverlordCompactionSchedulerTest
TestIndex.INDEX_IO,
Mockito.mock(TaskConfig.class),
new NoopCoordinatorClient(),
- new SegmentCacheManagerFactory(TestIndex.INDEX_IO, OBJECT_MAPPER)
+ SegmentCacheManagerFactory.createWithOwnedPool(TestIndex.INDEX_IO,
OBJECT_MAPPER)
),
(nameFormat, numThreads) -> new
WrappingScheduledExecutorService("test", executor, false),
brokerClient,
diff --git
a/indexing-service/src/test/java/org/apache/druid/indexing/overlord/SingleTaskBackgroundRunnerTest.java
b/indexing-service/src/test/java/org/apache/druid/indexing/overlord/SingleTaskBackgroundRunnerTest.java
index 37b2c5323b3..b628f8a253c 100644
---
a/indexing-service/src/test/java/org/apache/druid/indexing/overlord/SingleTaskBackgroundRunnerTest.java
+++
b/indexing-service/src/test/java/org/apache/druid/indexing/overlord/SingleTaskBackgroundRunnerTest.java
@@ -117,7 +117,7 @@ public class SingleTaskBackgroundRunnerTest
null,
NoopJoinableFactory.INSTANCE,
null,
- new SegmentCacheManagerFactory(TestIndex.INDEX_IO,
utils.getTestObjectMapper()),
+ SegmentCacheManagerFactory.createWithOwnedPool(TestIndex.INDEX_IO,
utils.getTestObjectMapper()),
utils.getTestObjectMapper(),
utils.getTestIndexIO(),
null,
diff --git
a/indexing-service/src/test/java/org/apache/druid/indexing/overlord/TaskLifecycleTest.java
b/indexing-service/src/test/java/org/apache/druid/indexing/overlord/TaskLifecycleTest.java
index ef723b94ee5..5da3d2ed754 100644
---
a/indexing-service/src/test/java/org/apache/druid/indexing/overlord/TaskLifecycleTest.java
+++
b/indexing-service/src/test/java/org/apache/druid/indexing/overlord/TaskLifecycleTest.java
@@ -581,7 +581,7 @@ public class TaskLifecycleTest extends
InitializedNullHandlingTest
DirectQueryProcessingPool.INSTANCE, // query executor service
NoopJoinableFactory.INSTANCE,
() -> monitorScheduler, // monitor scheduler
- new SegmentCacheManagerFactory(TestIndex.INDEX_IO, new
DefaultObjectMapper()),
+ SegmentCacheManagerFactory.createWithOwnedPool(TestIndex.INDEX_IO, new
DefaultObjectMapper()),
MAPPER,
INDEX_IO,
MapCache.create(0),
diff --git
a/indexing-service/src/test/java/org/apache/druid/indexing/overlord/TestTaskToolboxFactory.java
b/indexing-service/src/test/java/org/apache/druid/indexing/overlord/TestTaskToolboxFactory.java
index b566252f56d..de263efd871 100644
---
a/indexing-service/src/test/java/org/apache/druid/indexing/overlord/TestTaskToolboxFactory.java
+++
b/indexing-service/src/test/java/org/apache/druid/indexing/overlord/TestTaskToolboxFactory.java
@@ -148,7 +148,7 @@ public class TestTaskToolboxFactory extends
TaskToolboxFactory
private Provider<MonitorScheduler> monitorSchedulerProvider;
private ObjectMapper jsonMapper = TestHelper.JSON_MAPPER;
private IndexIO indexIO = TestHelper.getTestIndexIO();
- private SegmentCacheManagerFactory segmentCacheManagerFactory = new
SegmentCacheManagerFactory(TestIndex.INDEX_IO, jsonMapper);
+ private SegmentCacheManagerFactory segmentCacheManagerFactory =
SegmentCacheManagerFactory.createWithOwnedPool(TestIndex.INDEX_IO, jsonMapper);
private Cache cache;
private CacheConfig cacheConfig;
private CachePopulatorStats cachePopulatorStats;
diff --git
a/indexing-service/src/test/java/org/apache/druid/indexing/seekablestream/SeekableStreamIndexTaskTestBase.java
b/indexing-service/src/test/java/org/apache/druid/indexing/seekablestream/SeekableStreamIndexTaskTestBase.java
index ce48c0efda2..1821fc5b38c 100644
---
a/indexing-service/src/test/java/org/apache/druid/indexing/seekablestream/SeekableStreamIndexTaskTestBase.java
+++
b/indexing-service/src/test/java/org/apache/druid/indexing/seekablestream/SeekableStreamIndexTaskTestBase.java
@@ -681,7 +681,7 @@ public abstract class SeekableStreamIndexTaskTestBase
extends EasyMockSupport
DirectQueryProcessingPool.INSTANCE,
NoopJoinableFactory.INSTANCE,
() -> EasyMock.createMock(MonitorScheduler.class),
- new SegmentCacheManagerFactory(TestIndex.INDEX_IO, objectMapper),
+ SegmentCacheManagerFactory.createWithOwnedPool(TestIndex.INDEX_IO,
objectMapper),
objectMapper,
testUtils.getTestIndexIO(),
MapCache.create(1024),
diff --git
a/indexing-service/src/test/java/org/apache/druid/indexing/worker/WorkerTaskManagerTest.java
b/indexing-service/src/test/java/org/apache/druid/indexing/worker/WorkerTaskManagerTest.java
index d77b83a75c2..3e35287894a 100644
---
a/indexing-service/src/test/java/org/apache/druid/indexing/worker/WorkerTaskManagerTest.java
+++
b/indexing-service/src/test/java/org/apache/druid/indexing/worker/WorkerTaskManagerTest.java
@@ -159,7 +159,7 @@ public class WorkerTaskManagerTest
null,
NoopJoinableFactory.INSTANCE,
null,
- new SegmentCacheManagerFactory(TestIndex.INDEX_IO, jsonMapper),
+
SegmentCacheManagerFactory.createWithOwnedPool(TestIndex.INDEX_IO, jsonMapper),
jsonMapper,
indexIO,
null,
diff --git
a/multi-stage-query/src/test/java/org/apache/druid/msq/test/CalciteMSQTestsHelper.java
b/multi-stage-query/src/test/java/org/apache/druid/msq/test/CalciteMSQTestsHelper.java
index 837a545db1d..15d74d025c4 100644
---
a/multi-stage-query/src/test/java/org/apache/druid/msq/test/CalciteMSQTestsHelper.java
+++
b/multi-stage-query/src/test/java/org/apache/druid/msq/test/CalciteMSQTestsHelper.java
@@ -94,7 +94,7 @@ public class CalciteMSQTestsHelper
@LazySingleton
public SegmentCacheManager provideSegmentCacheManager(ObjectMapper
testMapper, TempDirProducer tempDirProducer)
{
- return new SegmentCacheManagerFactory(TestIndex.INDEX_IO, testMapper)
+ return
SegmentCacheManagerFactory.createWithOwnedPool(TestIndex.INDEX_IO, testMapper)
.manufacturate(tempDirProducer.newTempFolder("test"), null, true);
}
diff --git
a/multi-stage-query/src/test/java/org/apache/druid/msq/test/MSQTestBase.java
b/multi-stage-query/src/test/java/org/apache/druid/msq/test/MSQTestBase.java
index 54fc466c618..02cb12d7444 100644
--- a/multi-stage-query/src/test/java/org/apache/druid/msq/test/MSQTestBase.java
+++ b/multi-stage-query/src/test/java/org/apache/druid/msq/test/MSQTestBase.java
@@ -473,7 +473,7 @@ public class MSQTestBase extends BaseCalciteQueryTest
indexIO = new IndexIO(objectMapper, ColumnConfig.DEFAULT);
segmentCacheManager =
- new SegmentCacheManagerFactory(indexIO,
objectMapper).manufacturate(newTempFolder("cacheManager"), null, true);
+ SegmentCacheManagerFactory.createWithOwnedPool(indexIO,
objectMapper).manufacturate(newTempFolder("cacheManager"), null, true);
testSegmentManager = new TestSegmentManager();
diff --git
a/processing/src/main/java/org/apache/druid/guice/annotations/EphemeralStorageLoading.java
b/processing/src/main/java/org/apache/druid/guice/annotations/EphemeralStorageLoading.java
new file mode 100644
index 00000000000..f2874236ee9
--- /dev/null
+++
b/processing/src/main/java/org/apache/druid/guice/annotations/EphemeralStorageLoading.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.druid.guice.annotations;
+
+import com.google.inject.BindingAnnotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Binding annotation for the {@code StorageLoadingThreadPool} used by
ephemeral, per-task segment caches
+ */
+@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+@BindingAnnotation
+public @interface EphemeralStorageLoading
+{
+}
diff --git a/server/src/main/java/org/apache/druid/guice/StorageNodeModule.java
b/server/src/main/java/org/apache/druid/guice/StorageNodeModule.java
index 09b953204a4..24d4e360c1e 100644
--- a/server/src/main/java/org/apache/druid/guice/StorageNodeModule.java
+++ b/server/src/main/java/org/apache/druid/guice/StorageNodeModule.java
@@ -28,6 +28,7 @@ import com.google.inject.name.Named;
import com.google.inject.util.Providers;
import org.apache.druid.client.DruidServerConfig;
import org.apache.druid.discovery.DataNodeService;
+import org.apache.druid.guice.annotations.EphemeralStorageLoading;
import org.apache.druid.guice.annotations.Self;
import org.apache.druid.java.util.emitter.EmittingLogger;
import org.apache.druid.query.DruidProcessingConfig;
@@ -134,6 +135,20 @@ public class StorageNodeModule implements Module
return StorageLoadingThreadPool.createFromConfig(config);
}
+ /**
+ * Process-wide, always-virtual on-demand loading pool shared by the
ephemeral per-task segment caches built via
+ * {@code SegmentCacheManagerFactory} (tasks and MSQ workers).
+ */
+ @Provides
+ @LazySingleton
+ @EphemeralStorageLoading
+ public StorageLoadingThreadPool
getEphemeralStorageLoadingThreadPool(SegmentLoaderConfig config)
+ {
+ // Force virtual-storage mode: this pool serves per-task caches even when
the node itself is not in virtual-storage
+ // mode. Threads are created lazily, so an unused pool on a non-task
process is cheap.
+ return
StorageLoadingThreadPool.createFromConfig(config.withVirtualStorage(true));
+ }
+
@Provides
@LazySingleton
@Named(IS_SEGMENT_CACHE_CONFIGURED)
diff --git
a/server/src/main/java/org/apache/druid/segment/loading/SegmentLoaderConfig.java
b/server/src/main/java/org/apache/druid/segment/loading/SegmentLoaderConfig.java
index d90be578d3d..b9c9f0804fe 100644
---
a/server/src/main/java/org/apache/druid/segment/loading/SegmentLoaderConfig.java
+++
b/server/src/main/java/org/apache/druid/segment/loading/SegmentLoaderConfig.java
@@ -137,6 +137,34 @@ public class SegmentLoaderConfig
private long combinedMaxSize = 0;
+ public SegmentLoaderConfig()
+ {
+ }
+
+ private SegmentLoaderConfig(SegmentLoaderConfig other)
+ {
+ this.locations = other.locations;
+ this.lazyLoadOnStart = other.lazyLoadOnStart;
+ this.deleteOnRemove = other.deleteOnRemove;
+ this.dropSegmentDelayMillis = other.dropSegmentDelayMillis;
+ this.announceIntervalMillis = other.announceIntervalMillis;
+ this.numLoadingThreads = other.numLoadingThreads;
+ this.numBootstrapThreads = other.numBootstrapThreads;
+ this.numThreadsToLoadSegmentsIntoPageCacheOnDownload =
other.numThreadsToLoadSegmentsIntoPageCacheOnDownload;
+ this.numThreadsToLoadSegmentsIntoPageCacheOnBootstrap =
other.numThreadsToLoadSegmentsIntoPageCacheOnBootstrap;
+ this.infoDir = other.infoDir;
+ this.statusQueueMaxSize = other.statusQueueMaxSize;
+ this.virtualStorage = other.virtualStorage;
+ this.virtualStorageLoadThreads = other.virtualStorageLoadThreads;
+ this.virtualStorageUseVirtualThreads =
other.virtualStorageUseVirtualThreads;
+ this.virtualStorageIsEphemeral = other.virtualStorageIsEphemeral;
+ this.virtualStorageMetadataReservationEstimate =
other.virtualStorageMetadataReservationEstimate;
+ this.virtualStoragePartialDownloadsEnabled =
other.virtualStoragePartialDownloadsEnabled;
+ this.virtualStorageCoalesceGapBytes = other.virtualStorageCoalesceGapBytes;
+ this.virtualStorageMaxFetchRunBytes = other.virtualStorageMaxFetchRunBytes;
+ this.combinedMaxSize = other.combinedMaxSize;
+ }
+
public List<StorageLocationConfig> getLocations()
{
return locations;
@@ -263,6 +291,19 @@ public class SegmentLoaderConfig
return this;
}
+ /**
+ * Returns a copy of this config with {@link #virtualStorage} set to {@code
virtualStorage}. All other settings
+ * (notably {@link #getVirtualStorageLoadThreads()} and {@link
#isVirtualStorageUseVirtualThreads()}) are preserved.
+ * Used to derive an always-virtual config for the shared ephemeral
on-demand loading pool from a node config that
+ * may not itself run in virtual-storage mode.
+ */
+ public SegmentLoaderConfig withVirtualStorage(boolean virtualStorage)
+ {
+ final SegmentLoaderConfig copy = new SegmentLoaderConfig(this);
+ copy.virtualStorage = virtualStorage;
+ return copy;
+ }
+
/**
* Sets {@link #virtualStorageIsEphemeral}.
*/
diff --git
a/server/src/main/java/org/apache/druid/segment/loading/StorageLoadingThreadPool.java
b/server/src/main/java/org/apache/druid/segment/loading/StorageLoadingThreadPool.java
index 18668a51e2c..b76f347cb14 100644
---
a/server/src/main/java/org/apache/druid/segment/loading/StorageLoadingThreadPool.java
+++
b/server/src/main/java/org/apache/druid/segment/loading/StorageLoadingThreadPool.java
@@ -27,7 +27,6 @@ import com.google.common.util.concurrent.MoreExecutors;
import org.apache.druid.common.asyncresource.AsyncResource;
import org.apache.druid.common.asyncresource.AsyncResources;
import org.apache.druid.error.DruidException;
-import org.apache.druid.guice.StorageNodeModule;
import org.apache.druid.java.util.common.concurrent.Execs;
import org.apache.druid.java.util.common.lifecycle.LifecycleStop;
import org.apache.druid.java.util.common.logger.Logger;
@@ -50,7 +49,7 @@ import java.util.concurrent.TimeUnit;
*/
public class StorageLoadingThreadPool
{
- private static final Logger log = new Logger(StorageNodeModule.class);
+ private static final Logger log = new Logger(StorageLoadingThreadPool.class);
private final ListeningExecutorService exec;
diff --git
a/server/src/test/java/org/apache/druid/guice/StorageNodeModuleTest.java
b/server/src/test/java/org/apache/druid/guice/StorageNodeModuleTest.java
index 0f63572ed73..06c01e1ad5a 100644
--- a/server/src/test/java/org/apache/druid/guice/StorageNodeModuleTest.java
+++ b/server/src/test/java/org/apache/druid/guice/StorageNodeModuleTest.java
@@ -27,10 +27,12 @@ import com.google.inject.name.Names;
import com.google.inject.util.Modules;
import org.apache.druid.discovery.DataNodeService;
import org.apache.druid.error.ExceptionMatcher;
+import org.apache.druid.guice.annotations.EphemeralStorageLoading;
import org.apache.druid.guice.annotations.Self;
import org.apache.druid.initialization.Initialization;
import org.apache.druid.query.DruidProcessingConfig;
import org.apache.druid.segment.loading.SegmentLoaderConfig;
+import org.apache.druid.segment.loading.StorageLoadingThreadPool;
import org.apache.druid.segment.loading.StorageLocationConfig;
import org.apache.druid.server.DruidNode;
import org.apache.druid.server.coordination.DruidServerMetadata;
@@ -167,6 +169,26 @@ public class StorageNodeModuleTest
.assertThrowsAndMatches(() ->
injector.getInstance(DruidServerMetadata.class));
}
+ @Test
+ public void testEphemeralStorageLoadingThreadPoolIsInjectedAndAvailable()
+ {
+ // The qualified ephemeral loading pool must resolve from the core
injector (StorageNodeModule is universal via
+ // CoreInjectorBuilder), so SegmentCacheManagerFactory's @Inject
constructor can be satisfied on every process.
+ // The provider forces virtual storage via config.withVirtualStorage(true)
regardless of the node's flag.
+ Mockito.when(segmentLoaderConfig.withVirtualStorage(true))
+ .thenReturn(new SegmentLoaderConfig().setVirtualStorage(true));
+
+ final StorageLoadingThreadPool pool = injector().getInstance(
+ Key.get(StorageLoadingThreadPool.class, EphemeralStorageLoading.class)
+ );
+ try {
+ Assert.assertTrue(pool.isAvailable());
+ }
+ finally {
+ pool.stop();
+ }
+ }
+
private Injector injector()
{
return makeInjector(INJECT_SERVER_TYPE_CONFIG);
diff --git
a/server/src/test/java/org/apache/druid/segment/loading/SegmentLoaderConfigTest.java
b/server/src/test/java/org/apache/druid/segment/loading/SegmentLoaderConfigTest.java
index 9c61e00e62e..a3db293b6ad 100644
---
a/server/src/test/java/org/apache/druid/segment/loading/SegmentLoaderConfigTest.java
+++
b/server/src/test/java/org/apache/druid/segment/loading/SegmentLoaderConfigTest.java
@@ -80,4 +80,22 @@ class SegmentLoaderConfigTest
);
Assertions.assertEquals(8388608L,
configured.getVirtualStorageMaxFetchRunBytes());
}
+
+ @Test
+ public void testWithVirtualStorageReturnsCopyAndDoesNotMutateOriginal()
+ {
+ final SegmentLoaderConfig original = new SegmentLoaderConfig();
+ Assertions.assertFalse(original.isVirtualStorage());
+
+ final SegmentLoaderConfig copy = original.withVirtualStorage(true);
+
+ // The copy has the flag flipped, while other settings are preserved.
+ Assertions.assertTrue(copy.isVirtualStorage());
+ Assertions.assertEquals(original.getVirtualStorageLoadThreads(),
copy.getVirtualStorageLoadThreads());
+ Assertions.assertEquals(original.isVirtualStorageUseVirtualThreads(),
copy.isVirtualStorageUseVirtualThreads());
+
+ // The original is untouched and the copy is a distinct instance.
+ Assertions.assertNotSame(original, copy);
+ Assertions.assertFalse(original.isVirtualStorage());
+ }
}
diff --git
a/server/src/test/java/org/apache/druid/segment/loading/StorageLoadingThreadPoolTest.java
b/server/src/test/java/org/apache/druid/segment/loading/StorageLoadingThreadPoolTest.java
new file mode 100644
index 00000000000..78c5fde240d
--- /dev/null
+++
b/server/src/test/java/org/apache/druid/segment/loading/StorageLoadingThreadPoolTest.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.druid.segment.loading;
+
+import org.apache.druid.error.DruidException;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+class StorageLoadingThreadPoolTest
+{
+ @Test
+ void testCreateFromConfigIsUnavailableWhenNotVirtualStorage()
+ {
+ Assertions.assertFalse(StorageLoadingThreadPool.createFromConfig(new
SegmentLoaderConfig()).isAvailable());
+ }
+
+ @Test
+ void testCreateFromConfigIsAvailableWhenVirtualStorage()
+ {
+ // The shared ephemeral pool is built this way:
createFromConfig(config.withVirtualStorage(true)).
+ final StorageLoadingThreadPool pool =
+ StorageLoadingThreadPool.createFromConfig(new
SegmentLoaderConfig().withVirtualStorage(true));
+ try {
+ Assertions.assertTrue(pool.isAvailable());
+ Assertions.assertNotNull(pool.getExecutorService());
+ }
+ finally {
+ pool.stop();
+ }
+ }
+
+ @Test
+ void testCreateFromConfigRejectsNonPositiveThreadCountInVirtualStorage()
+ {
+ final SegmentLoaderConfig config = new SegmentLoaderConfig()
+ {
+ @Override
+ public int getVirtualStorageLoadThreads()
+ {
+ return 0;
+ }
+ }.setVirtualStorage(true);
+ Assertions.assertThrows(DruidException.class, () ->
StorageLoadingThreadPool.createFromConfig(config));
+ }
+}
diff --git a/services/src/test/java/org/apache/druid/cli/CliPeonTest.java
b/services/src/test/java/org/apache/druid/cli/CliPeonTest.java
index f7f6eb7f60a..8d49c6b3747 100644
--- a/services/src/test/java/org/apache/druid/cli/CliPeonTest.java
+++ b/services/src/test/java/org/apache/druid/cli/CliPeonTest.java
@@ -105,7 +105,7 @@ public class CliPeonTest
private final ObjectMapper mapper = TestHelper.makeJsonMapper();
private final CompactionTask.Builder compactBuilder =
- new CompactionTask.Builder("test_ds", new
SegmentCacheManagerFactory(TestIndex.INDEX_IO, mapper))
+ new CompactionTask.Builder("test_ds",
SegmentCacheManagerFactory.createWithOwnedPool(TestIndex.INDEX_IO, mapper))
.id("compact_test_taskid")
.inputSpec(new CompactionIntervalSpec(Intervals.of("2020/2021"), null));
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]