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

xiangfu0 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 3ac4b6e6c7b Add option to require an existing server data directory 
(#20115)
3ac4b6e6c7b is described below

commit 3ac4b6e6c7b83e07d09a646e0fedc218eb03fb53
Author: Xiang Fu <[email protected]>
AuthorDate: Mon Nov 2 14:52:51 2026 -0800

    Add option to require an existing server data directory (#20115)
    
    * Add option to require an existing server data directory
    
    * Address server data directory startup review feedback
---
 .../starter/helix/HelixInstanceDataManager.java    | 17 +++---
 .../helix/HelixInstanceDataManagerConfig.java      |  4 ++
 .../helix/HelixInstanceDataManagerConfigTest.java  | 17 ++++++
 .../helix/HelixInstanceDataManagerTest.java        | 68 +++++++++++++++++-----
 .../apache/pinot/spi/utils/CommonConstants.java    |  5 ++
 .../src/main/resources/conf/pinot-server.conf      |  4 ++
 6 files changed, 93 insertions(+), 22 deletions(-)

diff --git 
a/pinot-server/src/main/java/org/apache/pinot/server/starter/helix/HelixInstanceDataManager.java
 
b/pinot-server/src/main/java/org/apache/pinot/server/starter/helix/HelixInstanceDataManager.java
index 3b3e4fbfe29..df673ce8655 100644
--- 
a/pinot-server/src/main/java/org/apache/pinot/server/starter/helix/HelixInstanceDataManager.java
+++ 
b/pinot-server/src/main/java/org/apache/pinot/server/starter/helix/HelixInstanceDataManager.java
@@ -74,6 +74,7 @@ import org.apache.pinot.spi.data.LogicalTableConfig;
 import org.apache.pinot.spi.data.Schema;
 import org.apache.pinot.spi.env.PinotConfiguration;
 import org.apache.pinot.spi.plugin.PluginManager;
+import org.apache.pinot.spi.utils.CommonConstants.Server;
 import org.apache.pinot.spi.utils.ConsumingSegmentConsistencyModeListener;
 import org.apache.pinot.spi.utils.TimestampIndexUtils;
 import org.apache.pinot.spi.utils.builder.TableNameBuilder;
@@ -165,11 +166,8 @@ public class HelixInstanceDataManager implements 
InstanceDataManager {
     _segmentUploader = new 
PinotFSSegmentUploader(_instanceDataManagerConfig.getSegmentStoreUri(),
         
ServerSegmentCompletionProtocolHandler.getSegmentUploadRequestTimeoutMs(), 
serverMetrics);
 
-    File instanceDataDir = new 
File(_instanceDataManagerConfig.getInstanceDataDir());
-    initInstanceDataDir(instanceDataDir);
-
-    File instanceSegmentTarDir = new 
File(_instanceDataManagerConfig.getInstanceSegmentTarDir());
-    initInstanceSegmentTarDir(instanceSegmentTarDir);
+    initInstanceDataDir();
+    initInstanceSegmentTarDir();
 
     // Initialize segment build time lease extender executor
     SegmentBuildTimeLeaseExtender.initExecutor();
@@ -204,8 +202,12 @@ public class HelixInstanceDataManager implements 
InstanceDataManager {
   }
 
   @VisibleForTesting
-  void initInstanceDataDir(File instanceDataDir) {
+  void initInstanceDataDir() {
+    File instanceDataDir = new 
File(_instanceDataManagerConfig.getInstanceDataDir());
     if (!instanceDataDir.exists()) {
+      
Preconditions.checkState(!_instanceDataManagerConfig.shouldFailOnMissingInstanceDataDir(),
+          "Instance data dir does not exist: %s (%s=true)", instanceDataDir,
+          Server.CONFIG_OF_INSTANCE_DATA_DIR_FAIL_ON_MISSING);
       Preconditions.checkState(instanceDataDir.mkdirs(), "Failed to create 
instance data dir: %s", instanceDataDir);
     } else {
       // Clean up the _tmp (consuming segments) dir and empty table data dir
@@ -232,7 +234,8 @@ public class HelixInstanceDataManager implements 
InstanceDataManager {
   }
 
   @VisibleForTesting
-  void initInstanceSegmentTarDir(File instanceSegmentTarDir) {
+  void initInstanceSegmentTarDir() {
+    File instanceSegmentTarDir = new 
File(_instanceDataManagerConfig.getInstanceSegmentTarDir());
     if (!instanceSegmentTarDir.exists()) {
       Preconditions.checkState(instanceSegmentTarDir.mkdirs(), "Failed to 
create instance segment tar dir: %s",
           instanceSegmentTarDir);
diff --git 
a/pinot-server/src/main/java/org/apache/pinot/server/starter/helix/HelixInstanceDataManagerConfig.java
 
b/pinot-server/src/main/java/org/apache/pinot/server/starter/helix/HelixInstanceDataManagerConfig.java
index d31a99d7945..c240ea22dec 100644
--- 
a/pinot-server/src/main/java/org/apache/pinot/server/starter/helix/HelixInstanceDataManagerConfig.java
+++ 
b/pinot-server/src/main/java/org/apache/pinot/server/starter/helix/HelixInstanceDataManagerConfig.java
@@ -199,6 +199,10 @@ public class HelixInstanceDataManagerConfig implements 
InstanceDataManagerConfig
     return _serverConfig.getProperty(INSTANCE_DATA_DIR, 
DEFAULT_INSTANCE_DATA_DIR);
   }
 
+  public boolean shouldFailOnMissingInstanceDataDir() {
+    return _serverConfig.getProperty(INSTANCE_DATA_DIR_FAIL_ON_MISSING, 
DEFAULT_INSTANCE_DATA_DIR_FAIL_ON_MISSING);
+  }
+
   @Override
   public String getConsumerDir() {
     return _serverConfig.getProperty(CONSUMER_DIR);
diff --git 
a/pinot-server/src/test/java/org/apache/pinot/server/starter/helix/HelixInstanceDataManagerConfigTest.java
 
b/pinot-server/src/test/java/org/apache/pinot/server/starter/helix/HelixInstanceDataManagerConfigTest.java
index 9c70581fa24..46180044913 100644
--- 
a/pinot-server/src/test/java/org/apache/pinot/server/starter/helix/HelixInstanceDataManagerConfigTest.java
+++ 
b/pinot-server/src/test/java/org/apache/pinot/server/starter/helix/HelixInstanceDataManagerConfigTest.java
@@ -24,12 +24,29 @@ import 
org.apache.commons.configuration2.ex.ConfigurationException;
 import org.apache.pinot.spi.env.PinotConfiguration;
 import org.testng.annotations.Test;
 
+import static 
org.apache.pinot.spi.utils.CommonConstants.Server.CONFIG_OF_INSTANCE_DATA_DIR_FAIL_ON_MISSING;
+import static 
org.apache.pinot.spi.utils.CommonConstants.Server.CONFIG_OF_INSTANCE_ID;
+import static 
org.apache.pinot.spi.utils.CommonConstants.Server.INSTANCE_DATA_MANAGER_CONFIG_PREFIX;
 import static org.apache.pinot.spi.utils.CommonConstants.Server.INSTANCE_ID;
 import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
 
 
 public class HelixInstanceDataManagerConfigTest {
 
+  @Test
+  public void testInstanceDataDirFailOnMissingConfig()
+      throws ConfigurationException {
+    PinotConfiguration serverConfig = new 
PinotConfiguration(Map.of(CONFIG_OF_INSTANCE_ID, "testInstance"));
+    assertFalse(new 
HelixInstanceDataManagerConfig(serverConfig.subset(INSTANCE_DATA_MANAGER_CONFIG_PREFIX))
+        .shouldFailOnMissingInstanceDataDir());
+
+    serverConfig.setProperty(CONFIG_OF_INSTANCE_DATA_DIR_FAIL_ON_MISSING, 
"true");
+    assertTrue(new 
HelixInstanceDataManagerConfig(serverConfig.subset(INSTANCE_DATA_MANAGER_CONFIG_PREFIX))
+        .shouldFailOnMissingInstanceDataDir());
+  }
+
   @Test
   public void testTierConfigsWithMultipleTiers() throws ConfigurationException 
{
     Map<String, Object> props = new HashMap<>();
diff --git 
a/pinot-server/src/test/java/org/apache/pinot/server/starter/helix/HelixInstanceDataManagerTest.java
 
b/pinot-server/src/test/java/org/apache/pinot/server/starter/helix/HelixInstanceDataManagerTest.java
index b50aca813fb..1cedf716222 100644
--- 
a/pinot-server/src/test/java/org/apache/pinot/server/starter/helix/HelixInstanceDataManagerTest.java
+++ 
b/pinot-server/src/test/java/org/apache/pinot/server/starter/helix/HelixInstanceDataManagerTest.java
@@ -20,38 +20,55 @@ package org.apache.pinot.server.starter.helix;
 
 import java.io.File;
 import java.io.IOException;
+import java.nio.file.Files;
+import java.util.Map;
 import org.apache.commons.io.FileUtils;
-import org.mockito.Mockito;
+import org.apache.commons.lang3.reflect.FieldUtils;
+import org.apache.pinot.spi.env.PinotConfiguration;
 import org.testng.annotations.AfterClass;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
 
+import static 
org.apache.pinot.spi.utils.CommonConstants.Server.INSTANCE_DATA_DIR;
+import static 
org.apache.pinot.spi.utils.CommonConstants.Server.INSTANCE_DATA_DIR_FAIL_ON_MISSING;
+import static org.apache.pinot.spi.utils.CommonConstants.Server.INSTANCE_ID;
+import static 
org.apache.pinot.spi.utils.CommonConstants.Server.INSTANCE_SEGMENT_TAR_DIR;
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertFalse;
 import static org.testng.Assert.assertNotNull;
 import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.expectThrows;
 import static org.testng.Assert.fail;
 
 
 public class HelixInstanceDataManagerTest {
 
   private HelixInstanceDataManager _helixInstanceDataManager;
+  private File _testDir;
   private File _testInstanceDataDir;
   private File _testInstanceSegmentTarDir;
 
   @BeforeClass
-  public void setUp() {
-    _helixInstanceDataManager = Mockito.mock(HelixInstanceDataManager.class);
-    _testInstanceDataDir = new File("testInstanceDataDir");
-    _testInstanceSegmentTarDir = new File("testInstanceSegmentTarDir");
-    
Mockito.doCallRealMethod().when(_helixInstanceDataManager).initInstanceDataDir(_testInstanceDataDir);
-    
Mockito.doCallRealMethod().when(_helixInstanceDataManager).initInstanceSegmentTarDir(_testInstanceSegmentTarDir);
+  public void setUp()
+      throws Exception {
+    _testDir = 
Files.createTempDirectory("pinot-instance-data-manager-test").toFile();
+    _testInstanceDataDir = new File(_testDir, "data");
+    _testInstanceSegmentTarDir = new File(_testDir, "segmentTar");
+    _helixInstanceDataManager = createInstanceDataManager(new 
PinotConfiguration(Map.of(INSTANCE_ID, "testInstance",
+        INSTANCE_DATA_DIR, _testInstanceDataDir.toString(), 
INSTANCE_SEGMENT_TAR_DIR,
+        _testInstanceSegmentTarDir.toString())));
   }
 
-  @AfterClass
+  @AfterClass(alwaysRun = true)
   public void tearDown() {
-    FileUtils.deleteQuietly(_testInstanceDataDir);
-    FileUtils.deleteQuietly(_testInstanceSegmentTarDir);
+    FileUtils.deleteQuietly(_testDir);
+  }
+
+  private static HelixInstanceDataManager 
createInstanceDataManager(PinotConfiguration config)
+      throws Exception {
+    HelixInstanceDataManager manager = new HelixInstanceDataManager();
+    FieldUtils.writeField(manager, "_instanceDataManagerConfig", new 
HelixInstanceDataManagerConfig(config), true);
+    return manager;
   }
 
   @Test
@@ -60,17 +77,38 @@ public class HelixInstanceDataManagerTest {
       FileUtils.deleteQuietly(_testInstanceDataDir);
     }
 
-    _helixInstanceDataManager.initInstanceDataDir(_testInstanceDataDir);
+    _helixInstanceDataManager.initInstanceDataDir();
 
     assertTrue(_testInstanceDataDir.exists(), "Instance data directory should 
be created.");
   }
 
+  @Test
+  public void testInitInstanceDataDirFailsOnMissingDirectory()
+      throws Exception {
+    File tempDir = Files.createTempDirectory("pinot-data-dir-test").toFile();
+    File dataDir = new File(tempDir, "disk/data");
+    try {
+      HelixInstanceDataManager manager = createInstanceDataManager(new 
PinotConfiguration(Map.of(INSTANCE_ID,
+          "testInstance", INSTANCE_DATA_DIR, dataDir.toString(), 
INSTANCE_DATA_DIR_FAIL_ON_MISSING, true)));
+      IllegalStateException exception = 
expectThrows(IllegalStateException.class, manager::initInstanceDataDir);
+      assertTrue(exception.getMessage().contains(dataDir.toString()));
+      
assertTrue(exception.getMessage().contains("pinot.server.instance.dataDir.failOnMissing=true"));
+      assertFalse(dataDir.getParentFile().exists(), "Missing data directory 
must not be created.");
+
+      assertTrue(dataDir.mkdirs(), "Failed to prepare data directory.");
+      manager.initInstanceDataDir();
+      assertTrue(dataDir.isDirectory());
+    } finally {
+      FileUtils.deleteDirectory(tempDir);
+    }
+  }
+
   @Test
   public void testInitInstanceDataDirCleansUpTempDirs() {
     File tableDir = new File(_testInstanceDataDir, "tableA_OFFLINE");
     assertTrue(tableDir.mkdirs(), "Failed to create temp directory.");
 
-    _helixInstanceDataManager.initInstanceDataDir(_testInstanceDataDir);
+    _helixInstanceDataManager.initInstanceDataDir();
 
     assertFalse(tableDir.exists(), "Temporary directory should be deleted.");
   }
@@ -83,7 +121,7 @@ public class HelixInstanceDataManagerTest {
     _testInstanceDataDir.setWritable(false);
 
     try {
-      _helixInstanceDataManager.initInstanceDataDir(_testInstanceDataDir);
+      _helixInstanceDataManager.initInstanceDataDir();
       fail("Expected IllegalStateException due to non-writable directory.");
     } catch (IllegalStateException e) {
       // Expected exception
@@ -114,7 +152,7 @@ public class HelixInstanceDataManagerTest {
       FileUtils.deleteQuietly(_testInstanceSegmentTarDir);
     }
 
-    
_helixInstanceDataManager.initInstanceSegmentTarDir(_testInstanceSegmentTarDir);
+    _helixInstanceDataManager.initInstanceSegmentTarDir();
 
     assertTrue(_testInstanceSegmentTarDir.exists(), "Instance segment tar 
directory should be created.");
   }
@@ -127,7 +165,7 @@ public class HelixInstanceDataManagerTest {
     _testInstanceSegmentTarDir.setWritable(false);
 
     try {
-      
_helixInstanceDataManager.initInstanceSegmentTarDir(_testInstanceSegmentTarDir);
+      _helixInstanceDataManager.initInstanceSegmentTarDir();
       fail("Expected IllegalStateException due to non-writable directory.");
     } catch (IllegalStateException e) {
       // Expected exception
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 0b494ba02b5..c979a58ee6e 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
@@ -1546,6 +1546,11 @@ public class CommonConstants {
     public static final String DEFAULT_INSTANCE_BASE_DIR =
         FileUtils.getTempDirectoryPath() + File.separator + "PinotServer";
     public static final String DEFAULT_INSTANCE_DATA_DIR = 
DEFAULT_INSTANCE_BASE_DIR + File.separator + "index";
+    // Require a pre-existing data directory at server startup when enabled.
+    public static final String INSTANCE_DATA_DIR_FAIL_ON_MISSING = 
"dataDir.failOnMissing";
+    public static final String CONFIG_OF_INSTANCE_DATA_DIR_FAIL_ON_MISSING =
+        INSTANCE_DATA_MANAGER_CONFIG_PREFIX + "." + 
INSTANCE_DATA_DIR_FAIL_ON_MISSING;
+    public static final boolean DEFAULT_INSTANCE_DATA_DIR_FAIL_ON_MISSING = 
false;
     public static final String CONSUMER_DIR = "consumerDir";
     public static final String CONFIG_OF_CONSUMER_DIR = 
INSTANCE_DATA_MANAGER_CONFIG_PREFIX + "." + CONSUMER_DIR;
     public static final String INSTANCE_SEGMENT_TAR_DIR = "segmentTarDir";
diff --git a/pinot-tools/src/main/resources/conf/pinot-server.conf 
b/pinot-tools/src/main/resources/conf/pinot-server.conf
index 066f46651c7..26eae833b8d 100644
--- a/pinot-tools/src/main/resources/conf/pinot-server.conf
+++ b/pinot-tools/src/main/resources/conf/pinot-server.conf
@@ -37,6 +37,10 @@ pinot.server.adminapi.port=8097
 
 # Pinot Server Data Directory
 pinot.server.instance.dataDir=/tmp/pinot/data/server/index
+# Set to true to fail startup if dataDir is missing, for deployments that 
prepare disks separately.
+# Applies only to the instance dataDir; consumerDir and tier dataDirs are 
still created on demand.
+# This checks directory existence, not whether the intended disk is mounted.
+# pinot.server.instance.dataDir.failOnMissing=false
 
 # Pinot Server Temporary Segment Tar Directory
 pinot.server.instance.segmentTarDir=/tmp/pinot/data/server/segmentTar


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

Reply via email to