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

Jackie-Jiang 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 b18c96d4956 Clean up stale star-tree index leftover from killed prior 
build (#19331)
b18c96d4956 is described below

commit b18c96d4956c4d1a5a76c5548c39c8841164bfa7
Author: Chaitanya Deepthi <[email protected]>
AuthorDate: Sat Aug 22 17:25:54 2026 -0700

    Clean up stale star-tree index leftover from killed prior build (#19331)
---
 .../startree/v2/builder/MultipleTreesBuilder.java  |  13 ++
 .../MultipleTreesBuilderStaleCleanupTest.java      | 163 +++++++++++++++++++++
 2 files changed, 176 insertions(+)

diff --git 
a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/startree/v2/builder/MultipleTreesBuilder.java
 
b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/startree/v2/builder/MultipleTreesBuilder.java
index f971128e13f..bb94e4f47e9 100644
--- 
a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/startree/v2/builder/MultipleTreesBuilder.java
+++ 
b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/startree/v2/builder/MultipleTreesBuilder.java
@@ -222,6 +222,19 @@ public class MultipleTreesBuilder implements Closeable {
         _buildMode);
 
     File starTreeV2IndexFile = new File(_segmentDirectory, 
StarTreeV2Constants.INDEX_FILE_NAME);
+    // When _separator is null, metadata has no star-tree, so any leftover 
files on disk are orphaned
+    // (e.g. from a previous build killed before the metadata save). Delete 
them so the combiner opens.
+    if (_separator == null) {
+      File starTreeV2IndexMapFile = new File(_segmentDirectory, 
StarTreeV2Constants.INDEX_MAP_FILE_NAME);
+      File existingSeparatorDir = new File(_segmentDirectory, 
StarTreeV2Constants.EXISTING_STAR_TREE_TEMP_DIR);
+      if (starTreeV2IndexFile.exists() || starTreeV2IndexMapFile.exists() || 
existingSeparatorDir.exists()) {
+        LOGGER.warn("Cleaning up stale star-tree artifacts in {} from a prior 
incomplete build",
+            _segmentDirectory);
+        FileUtils.deleteQuietly(starTreeV2IndexFile);
+        FileUtils.deleteQuietly(starTreeV2IndexMapFile);
+        FileUtils.deleteQuietly(existingSeparatorDir);
+      }
+    }
     try (StarTreeIndexCombiner indexCombiner = new 
StarTreeIndexCombiner(starTreeV2IndexFile)) {
       File starTreeIndexDir = new File(_segmentDirectory, 
StarTreeV2Constants.STAR_TREE_TEMP_DIR);
       FileUtils.forceMkdir(starTreeIndexDir);
diff --git 
a/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/startree/v2/builder/MultipleTreesBuilderStaleCleanupTest.java
 
b/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/startree/v2/builder/MultipleTreesBuilderStaleCleanupTest.java
new file mode 100644
index 00000000000..877807fe26d
--- /dev/null
+++ 
b/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/startree/v2/builder/MultipleTreesBuilderStaleCleanupTest.java
@@ -0,0 +1,163 @@
+/**
+ * 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.pinot.segment.local.startree.v2.builder;
+
+import java.io.File;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import org.apache.commons.io.FileUtils;
+import 
org.apache.pinot.segment.local.indexsegment.immutable.ImmutableSegmentLoader;
+import 
org.apache.pinot.segment.local.segment.creator.impl.SegmentIndexCreationDriverImpl;
+import org.apache.pinot.segment.local.segment.readers.GenericRowRecordReader;
+import org.apache.pinot.segment.local.startree.StarTreeBuilderUtils;
+import org.apache.pinot.segment.spi.ImmutableSegment;
+import org.apache.pinot.segment.spi.creator.SegmentGeneratorConfig;
+import org.apache.pinot.segment.spi.index.startree.StarTreeV2Constants;
+import org.apache.pinot.spi.config.table.StarTreeIndexConfig;
+import org.apache.pinot.spi.config.table.TableConfig;
+import org.apache.pinot.spi.config.table.TableType;
+import org.apache.pinot.spi.data.FieldSpec;
+import org.apache.pinot.spi.data.Schema;
+import org.apache.pinot.spi.data.readers.GenericRow;
+import org.apache.pinot.spi.utils.ReadMode;
+import org.apache.pinot.spi.utils.builder.TableConfigBuilder;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+
+
+/// Unit test for {@link MultipleTreesBuilder#build()} verifying that a stale 
on-disk `star_tree_index`
+/// (leftover from a previous build attempt that was killed before completing) 
is cleaned up on the next
+/// build instead of triggering the `Star-tree index file already exists` 
IllegalStateException from
+/// {@link StarTreeIndexCombiner}.
+public class MultipleTreesBuilderStaleCleanupTest {
+  private static final File TEMP_DIR =
+      new File(FileUtils.getTempDirectory(), 
"MultipleTreesBuilderStaleCleanupTest");
+  private static final File INDEX_DIR = new File(TEMP_DIR, "testSegment");
+
+  @BeforeMethod
+  public void setUp()
+      throws Exception {
+    FileUtils.deleteQuietly(TEMP_DIR);
+    FileUtils.forceMkdir(TEMP_DIR);
+    buildBaseSegment();
+  }
+
+  @AfterMethod
+  public void tearDown() {
+    FileUtils.deleteQuietly(TEMP_DIR);
+  }
+
+  /// A previous build that was hard-killed (JVM crash / SIGKILL / OOM) leaves 
behind a partial
+  /// `star_tree_index` at the segment root without a matching 
`STAR_TREE_COUNT` in segment metadata.
+  /// The next build must clean it up and succeed rather than fail with 
`Star-tree index file already exists`.
+  @Test
+  public void staleIndexFileFromKilledBuildIsCleanedUp()
+      throws Exception {
+    File segmentDir = INDEX_DIR.listFiles()[0];
+    File v3Dir = findV3Dir(segmentDir);
+    File staleIndex = new File(v3Dir, StarTreeV2Constants.INDEX_FILE_NAME);
+    File staleIndexMap = new File(v3Dir, 
StarTreeV2Constants.INDEX_MAP_FILE_NAME);
+    // Simulate the killed-build leftover: a file at the segment root that 
segment metadata does not know about.
+    FileUtils.writeStringToFile(staleIndex, "stale bytes from a prior killed 
build", StandardCharsets.UTF_8);
+    assertTrue(staleIndex.exists());
+
+    // Kick off a fresh build. Without the fix this throws 
`IllegalStateException: Star-tree index file already exists`.
+    List<StarTreeV2BuilderConfig> builderConfigs = 
createBuilderConfigs(segmentDir);
+    try (MultipleTreesBuilder builder = new 
MultipleTreesBuilder(builderConfigs, segmentDir,
+        MultipleTreesBuilder.BuildMode.OFF_HEAP)) {
+      builder.build();
+    }
+
+    // Post-build: the stale bytes are gone, a real index file and map file 
exist.
+    assertTrue(staleIndex.isFile(), "star_tree_index should exist after a 
successful build");
+    assertTrue(staleIndexMap.isFile(), "star_tree_index_map should exist after 
a successful build");
+    assertTrue(staleIndex.length() > "stale bytes from a prior killed 
build".length(),
+        "star_tree_index should be a real serialized tree, not the stale 
contents");
+  }
+
+  /// A lingering EXISTING_STAR_TREE_TEMP_DIR from a killed incremental build 
(previous run set files aside
+  /// but never restored them) is also cleaned up on the next fresh build.
+  @Test
+  public void staleSeparatorTempDirFromKilledIncrementalIsCleanedUp()
+      throws Exception {
+    File segmentDir = INDEX_DIR.listFiles()[0];
+    File v3Dir = findV3Dir(segmentDir);
+    File staleSeparatorDir = new File(v3Dir, 
StarTreeV2Constants.EXISTING_STAR_TREE_TEMP_DIR);
+    FileUtils.forceMkdir(staleSeparatorDir);
+    FileUtils.writeStringToFile(new File(staleSeparatorDir, "leftover.bin"), 
"old", StandardCharsets.UTF_8);
+    assertTrue(staleSeparatorDir.isDirectory());
+
+    List<StarTreeV2BuilderConfig> builderConfigs = 
createBuilderConfigs(segmentDir);
+    try (MultipleTreesBuilder builder = new 
MultipleTreesBuilder(builderConfigs, segmentDir,
+        MultipleTreesBuilder.BuildMode.OFF_HEAP)) {
+      builder.build();
+    }
+
+    // The temp dir has been cleaned up (either by our pre-build cleanup, or 
by the normal build flow).
+    assertFalse(staleSeparatorDir.exists(),
+        "stale EXISTING_STAR_TREE_TEMP_DIR should have been removed by the 
fresh build");
+  }
+
+  private void buildBaseSegment()
+      throws Exception {
+    Schema schema = new Schema.SchemaBuilder()
+        .addSingleValueDimension("stringCol", FieldSpec.DataType.STRING)
+        .addMetric("longCol", FieldSpec.DataType.LONG)
+        .build();
+    TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE)
+        .setTableName("testTable")
+        .build();
+    SegmentGeneratorConfig config = new SegmentGeneratorConfig(tableConfig, 
schema);
+    config.setOutDir(TEMP_DIR.getAbsolutePath());
+    config.setSegmentName("testSegment");
+    List<GenericRow> rows = List.of(makeRow("A", 1L), makeRow("B", 2L), 
makeRow("C", 3L));
+    SegmentIndexCreationDriverImpl driver = new 
SegmentIndexCreationDriverImpl();
+    driver.init(config, new GenericRowRecordReader(rows));
+    driver.build();
+  }
+
+  private GenericRow makeRow(String s, long l) {
+    GenericRow row = new GenericRow();
+    row.putValue("stringCol", s);
+    row.putValue("longCol", l);
+    return row;
+  }
+
+  private List<StarTreeV2BuilderConfig> createBuilderConfigs(File segmentDir)
+      throws Exception {
+    StarTreeIndexConfig starTreeConfig =
+        new StarTreeIndexConfig(List.of("stringCol"), null, 
List.of("SUM__longCol"), null, 1000);
+    ImmutableSegment segment = ImmutableSegmentLoader.load(segmentDir, 
ReadMode.mmap);
+    try {
+      return 
StarTreeBuilderUtils.generateBuilderConfigs(List.of(starTreeConfig), false,
+          segment.getSegmentMetadata());
+    } finally {
+      segment.destroy();
+    }
+  }
+
+  private static File findV3Dir(File segmentDir) {
+    File v3 = new File(segmentDir, "v3");
+    return v3.isDirectory() ? v3 : segmentDir;
+  }
+}


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

Reply via email to