xiangfu0 commented on code in PR #19331:
URL: https://github.com/apache/pinot/pull/19331#discussion_r4215045834


##########
pinot-segment-local/src/main/java/org/apache/pinot/segment/local/startree/v2/builder/MultipleTreesBuilder.java:
##########
@@ -222,6 +222,19 @@ public void build()
         _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);

Review Comment:
   **[MAJOR] Cleanup omits `StarTreeV2Constants.STAR_TREE_TEMP_DIR` 
(`star_tree_tmp`)** — the same killed-build scenario this block targets also 
leaves `star_tree_tmp` populated (`StarTreeIndexCombiner.combine()` only runs 
`cleanDirectory` after a successful combine, and the later `forceMkdir` is a 
no-op on an existing dir).
   
   Consequences on the retry:
   - OFF_HEAP (the server default): `OffHeapSingleTreeBuilder`'s constructor 
throws `Preconditions.checkState(!_starTreeRecordFile.exists(), "Star-tree 
record file: %s already exists")` (OffHeapSingleTreeBuilder.java:78) — i.e. the 
retry still fails with the exact "already exists" failure class this PR set out 
to fix (it self-heals only on the attempt after, because the inner catch 
deletes `starTreeIndexDir`).
   - ON_HEAP: worse — no exists-check, and 
`StarTreeIndexCombiner.writeNullValueVectorIfExists` includes any stale 
`<column>.bitmap.nullvalue` file that merely exists in `star_tree_tmp`, so a 
leftover null-vector from the killed build can be silently combined into the 
new index (wrong query results, no error).
   
   Fix: also delete `new File(_segmentDirectory, 
StarTreeV2Constants.STAR_TREE_TEMP_DIR)` in this cleanup block.



##########
pinot-segment-local/src/main/java/org/apache/pinot/segment/local/startree/v2/builder/MultipleTreesBuilder.java:
##########
@@ -222,6 +222,19 @@ public void build()
         _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.

Review Comment:
   **[MAJOR] The advertised "killed prior build" state is only partially 
covered: a kill between the metadata save (line 278) and the index-map store 
(line 280) still bricks the segment.** In that window, metadata.properties has 
`STAR_TREE_COUNT`, `star_tree_index` is complete, but `star_tree_index_map` is 
missing. On the next build `_separator != null`, so this cleanup is skipped; 
`getSeparator()` moves `star_tree_index` into the temp dir, then 
`moveFileToDirectory` of the missing map file throws `FileNotFoundException`, 
and the catch block (line 206) force-deletes the temp dir — destroying the 
valid `star_tree_index` — while metadata still claims star-trees exist. The 
segment then fails to load entirely (`StarTreeIndexReader.load` does `new 
FileInputStream(star_tree_index_map)` with no fallback) until manual metadata 
surgery or re-download.
   
   Root-cause fix: swap lines 278-281 so `StarTreeIndexMapUtils.storeToFile` 
runs before the metadata save. That makes the metadata save the single commit 
point, and every pre-commit crash state then lands in the `_separator == null` 
path this cleanup already handles.



##########
pinot-segment-local/src/main/java/org/apache/pinot/segment/local/startree/v2/builder/MultipleTreesBuilder.java:
##########
@@ -222,6 +222,19 @@ public void build()
         _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);
+      }
+    }

Review Comment:
   **[MAJOR] (pre-existing, same function) `_starTreeCreationFailed` is only 
set in the inner loop catch (lines 262-274), so a failure in the persistence 
tail (lines 278-282) bypasses the restore path in `close()` and destroys the 
old star-tree without any process kill.** Example: incremental rebuild 
(`_separator != null`), metadata is saved with the new `STAR_TREE_COUNT` at 
line 278, then `StarTreeIndexMapUtils.storeToFile` at line 280 throws (disk 
full). `SegmentPreProcessor` swallows the `build()` exception expecting 
`close()` to roll back, but `close()` sees `_starTreeCreationFailed == false` 
and force-deletes `existing_star_tree_tmp` holding the only copy of the old 
index/map — leaving `STAR_TREE_COUNT` in metadata with no `star_tree_index_map` 
on disk (segment unloadable). The same flag-scope gap applies to a failure at 
`forceMkdir` (line 240) before the inner try. Setting the flag for any failure 
inside the try-with-resources (not just the loop) would close this.



##########
pinot-segment-local/src/main/java/org/apache/pinot/segment/local/startree/v2/builder/MultipleTreesBuilder.java:
##########
@@ -222,6 +222,19 @@ public void build()
         _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);

Review Comment:
   **[MAJOR] (pre-existing, adjacent) Symmetric kill window inside 
`getSeparator()` remains uncovered and data-destroying.** A kill after the file 
moves (lines 189-192) but before the metadata save (lines 201-202) leaves 
metadata still listing star-trees while `star_tree_index` lives only in 
`existing_star_tree_tmp`. On retry, `_separator != null` so this cleanup never 
runs; `getSeparator()` throws `FileNotFoundException` moving the now-missing 
`star_tree_index` from the segment root, and its catch (line 206) force-deletes 
`existing_star_tree_tmp` — the only surviving copy — while metadata still 
references star-trees, so subsequent loads fail. Consider reconciling in 
`getSeparator()`: if the segment-root files are missing but 
`existing_star_tree_tmp` holds them, restore from the temp dir instead of 
deleting it.



##########
pinot-segment-local/src/main/java/org/apache/pinot/segment/local/startree/v2/builder/MultipleTreesBuilder.java:
##########
@@ -222,6 +222,19 @@ public void build()
         _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) {

Review Comment:
   **[MINOR] The healing path is unreachable once star-tree configs are removed 
from (or absent in) the table config.** 
`SegmentPreProcessor.needProcessStarTree` (SegmentPreProcessor.java:485) 
returns false when `starTreeBuilderConfigs` is empty and `starTreeMetadataList` 
is null, and `StarTreeBuilderUtils.removeStarTrees` is likewise gated on 
metadata. So orphan `star_tree_index` / `star_tree_index_map` / 
`existing_star_tree_tmp` from a killed build persist indefinitely for tables 
that no longer request star-trees, inflating segment size and tar uploads. If 
full coverage is desired, the orphan check belongs in the preprocessor path 
(run regardless of whether a build is requested), not only inside `build()`.



##########
pinot-segment-local/src/main/java/org/apache/pinot/segment/local/startree/v2/builder/MultipleTreesBuilder.java:
##########
@@ -222,6 +222,19 @@ public void build()
         _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);

Review Comment:
   **[MINOR] `FileUtils.deleteQuietly` return values are ignored, so a failed 
deletion is invisible.** If the stale `star_tree_index` cannot be deleted 
(permissions, Windows file lock, shared-storage IO error), the WARN at line 231 
still claims cleanup happened, and `StarTreeIndexCombiner`'s 
`checkState(!indexFile.exists())` then throws the very "Star-tree index file 
already exists" error this PR fixes — with logs misleadingly saying the stale 
files were cleaned (and possibly a partial state: map deleted, index not). 
Checking the boolean (`if (!FileUtils.deleteQuietly(f)) LOGGER.error(...)`) or 
using `forceDelete` inside a try/catch would surface the real cause.



##########
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];

Review Comment:
   **[MINOR] `INDEX_DIR.listFiles()[0]` grabs an arbitrary child of the segment 
directory and only works because a default v3 segment root currently contains 
exactly the `v3` subdirectory.** `File.listFiles()` order is unspecified and 
the call is NPE-prone; if the driver ever leaves another entry at the root, 
`[0]` may be a regular file and setup fails spuriously. It also means 
`findV3Dir` is a constant no-op (`v3/v3` never exists) and the builder is 
exercised with the inner v3 dir as `indexDir`, which is not the shape 
production callers use. Prefer passing `INDEX_DIR` to the builder and resolving 
the segment dir via `SegmentDirectoryPaths.findSegmentDirectory(INDEX_DIR)` — 
the same helper `MultipleTreesBuilder` uses.



##########
pinot-segment-local/src/main/java/org/apache/pinot/segment/local/startree/v2/builder/MultipleTreesBuilder.java:
##########
@@ -222,6 +222,19 @@ public void build()
         _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()) {

Review Comment:
   **[NIT] The triple `exists()` guard exists only to gate the WARN log** 
(`deleteQuietly` already no-ops on missing paths), costing up to six stat calls 
on a per-segment-build path, and when the WARN fires it does not say which 
artifact was stale. Simpler and more informative: delete unconditionally and 
log per artifact based on `deleteQuietly`'s boolean return.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


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

Reply via email to