Skip to content

Remove child level directory on refresh for CompositeIndexWriter#20326

Merged
Bukhtawar merged 1 commit intoopensearch-project:mainfrom
RS146BIJAY:directory-cleanup
Jan 25, 2026
Merged

Remove child level directory on refresh for CompositeIndexWriter#20326
Bukhtawar merged 1 commit intoopensearch-project:mainfrom
RS146BIJAY:directory-cleanup

Conversation

@RS146BIJAY
Copy link
Copy Markdown
Contributor

@RS146BIJAY RS146BIJAY commented Dec 28, 2025

Description

Remove child level directory during refresh once child level IndexWriter is synced with parent IndexWriter.

Related Issues

#19921

Summary by CodeRabbit

  • Bug Fixes

    • Child index directories are now removed during refresh, preventing accumulation of orphaned on-disk directories.
  • Tests

    • Added/updated tests to verify child directories are deleted after refresh and to validate concurrent indexing during refresh cycles.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Dec 28, 2025

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

📝 Walkthrough

Walkthrough

Removes child-level on-disk directories after CompositeIndexWriter refresh by unwrapping FSDirectory instances to obtain Paths, closing child directories, and deleting their on-disk directories; adds a test asserting no leftover child directories after refresh.

Changes

Cohort / File(s) Summary
Changelog
CHANGELOG.md
Added "Fixed" entry noting removal of child-level directories on refresh for CompositeIndexWriter.
Index writer implementation
server/src/main/java/org/opensearch/index/engine/CompositeIndexWriter.java
Added imports (FSDirectory, FilterDirectory, java.nio.file.Path); after collecting child Directory instances, added getLocalFSDirectory(Directory) to unwrap FSDirectory (handles FilterDirectory), derive Path[], close child directories, and remove their on-disk directories via IOUtils.rm(Path[]).
Tests
server/src/test/java/org/opensearch/index/engine/CompositeIndexWriterForAppendTests.java
Added testChildDirectoryDeletedPostRefresh() which creates docs, calls beforeRefresh()/afterRefresh(true), and asserts no child directories remain using java.nio.file.Files/Path; adjusted placement/structure of testConcurrentIndexingDuringRefresh() and added NIO imports.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐇 I hopped through shard paths, soft paws in the night,
I closed every writer and swept folders light.
Merged children gone, not a nest left in view,
The index dreams tidy — fresh, calm, and new. ✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Description check ❓ Inconclusive The description provides the core change explanation and links to the related issue, but lacks information about testing and omits the required checklist items. Complete the checklist section and confirm whether functionality includes testing and if any API documentation updates are needed.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely summarizes the main change: removing child-level directories on refresh for CompositeIndexWriter, matching the core objective.

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown
Contributor

❌ Gradle check result for d06276f: FAILURE

Please examine the workflow log, locate, and copy-paste the failure(s) below, then iterate to green. Is the failure a flaky test unrelated to your change?

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
server/src/test/java/org/opensearch/index/engine/CompositeIndexWriterForAppendTests.java (1)

192-228: Consider improving test clarity and robustness.

The test verifies that child directories are removed after refresh, but has some areas that could be strengthened:

  1. Line 219: The filter logic with path.endsWith("extra0") == false and the - 1 adjustment uses unexplained magic values. This makes the test fragile and harder to maintain.

  2. Line 219: Style - attributes.isDirectory() == true should be simplified to attributes.isDirectory().

  3. The test doesn't verify that child directories were actually created before refresh—it only checks they're gone after. The test could pass vacuously if directories were never created.

🔎 Suggested improvements
 public void testChildDirectoryDeletedPostRefresh() throws IOException, InterruptedException {
     final EngineConfig engineConfig = config();
     Path dataPath = engineConfig.getStore().shardPath().resolveIndex();
     CompositeIndexWriter compositeIndexWriter = null;
     
     try {
         compositeIndexWriter = new CompositeIndexWriter(
             engineConfig,
             createWriter(),
             newSoftDeletesPolicy(),
             softDeletesField,
             indexWriterFactory
         );
         
         Engine.Index operation = indexForDoc(createParsedDoc("id", null, DEFAULT_CRITERIA));
         try (Releasable ignore1 = compositeIndexWriter.acquireLock(operation.uid().bytes())) {
             compositeIndexWriter.addDocuments(operation.docs(), operation.uid());
         }
         
         operation = indexForDoc(createParsedDoc("id2", null, "testingNewCriteria"));
         try (Releasable ignore1 = compositeIndexWriter.acquireLock(operation.uid().bytes())) {
             compositeIndexWriter.addDocuments(operation.docs(), operation.uid());
         }
         
+        // Verify child directories were created
+        long childDirCountBefore = Files.list(dataPath)
+            .filter(path -> Files.isDirectory(path) && path.getFileName().toString().startsWith(CHILD_DIRECTORY_PREFIX))
+            .count();
+        assertTrue("Expected child directories to be created before refresh", childDirCountBefore > 0);
+        
         compositeIndexWriter.beforeRefresh();
         compositeIndexWriter.afterRefresh(true);
         
-        long directoryCount = Files.find(dataPath, 1, (path, attributes) -> attributes.isDirectory() == true && path.endsWith("extra0") == false).count() - 1;
-        // Ensure no child directory is pending here.
-        assertEquals(0, directoryCount);
+        // Verify all child directories are removed after refresh
+        long childDirCountAfter = Files.list(dataPath)
+            .filter(path -> Files.isDirectory(path) && path.getFileName().toString().startsWith(CHILD_DIRECTORY_PREFIX))
+            .count();
+        assertEquals("Expected no child directories after refresh", 0, childDirCountAfter);
     }  finally {
         if (compositeIndexWriter != null) {
             IOUtils.closeWhileHandlingException(compositeIndexWriter);
         }
     }
 }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 696951d and d06276f.

📒 Files selected for processing (3)
  • CHANGELOG.md
  • server/src/main/java/org/opensearch/index/engine/CompositeIndexWriter.java
  • server/src/test/java/org/opensearch/index/engine/CompositeIndexWriterForAppendTests.java
🧰 Additional context used
🧬 Code graph analysis (2)
server/src/main/java/org/opensearch/index/engine/CompositeIndexWriter.java (1)
libs/common/src/main/java/org/opensearch/common/util/io/IOUtils.java (1)
  • IOUtils (58-317)
server/src/test/java/org/opensearch/index/engine/CompositeIndexWriterForAppendTests.java (1)
libs/common/src/main/java/org/opensearch/common/util/io/IOUtils.java (1)
  • IOUtils (58-317)
🔇 Additional comments (3)
CHANGELOG.md (1)

103-103: LGTM!

The changelog entry correctly documents the fix for removing child-level directories on refresh.

server/src/main/java/org/opensearch/index/engine/CompositeIndexWriter.java (1)

22-23: LGTM!

The imports are necessary for the new directory cleanup functionality.

Also applies to: 44-44

server/src/test/java/org/opensearch/index/engine/CompositeIndexWriterForAppendTests.java (1)

32-33: LGTM!

The imports are necessary for filesystem verification in the new test method.

@RS146BIJAY RS146BIJAY force-pushed the directory-cleanup branch 2 times, most recently from 8d4412f to 9fb528a Compare December 30, 2025 14:15
@github-actions
Copy link
Copy Markdown
Contributor

❌ Gradle check result for 9fb528a: FAILURE

Please examine the workflow log, locate, and copy-paste the failure(s) below, then iterate to green. Is the failure a flaky test unrelated to your change?

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (2)
server/src/main/java/org/opensearch/index/engine/CompositeIndexWriter.java (2)

546-550: Error handling needs improvement for directory cleanup.

The current implementation has the issues identified in previous reviews:

  1. If getLocalFSDirectory throws during the stream mapping (line 546-547), the directories in directoryToCombine remain open and won't be closed
  2. If IOUtils.rm fails (line 550), it triggers a full rollback via the catch block at line 529, causing shard failure even though the merge succeeded and directories are already closed

This can lead to:

  • Orphaned closed directories accumulating on disk
  • Unnecessary shard failures from transient filesystem issues (permissions, disk space)

Consider wrapping the path extraction in a try-finally block to ensure directories are always closed, and handle removal failures more gracefully (either using IOUtils.deleteFilesIgnoringExceptions or logging failures without triggering rollback).

Based on previous review feedback that flagged these same concerns.


556-566: Add defensive checks to prevent ClassCastException.

The method lacks validation and can fail with ClassCastException in several scenarios:

  1. No null check on localDirectory
  2. No validation before casting to FilterDirectory (line 562)
  3. No validation that the delegate is actually FSDirectory (line 562)
  4. Only unwraps one level of FilterDirectory (multiple layers not handled)
  5. Comment mentions "as per above validation" but no such validation exists

Consider adding:

  • Null check at the start
  • Validation that localDirectory is either FSDirectory or FilterDirectory, with a clear exception message if not
  • Recursive unwrapping to handle multiple FilterDirectory layers
  • Validation that the final unwrapped delegate is FSDirectory

Based on previous review feedback that identified these missing defensive checks.

🧹 Nitpick comments (1)
server/src/test/java/org/opensearch/index/engine/CompositeIndexWriterForAppendTests.java (1)

192-232: Consider strengthening the test verification.

The test validates the core functionality, but could be more robust:

  1. Line 222: The filter excluding "extra0" relies on hardcoded internal directory naming, which is brittle
  2. Line 223: Subtracting 1 from the count assumes a specific directory structure that may change
  3. Missing verification: The test doesn't confirm child directories existed before refresh (only checks they're gone after)
  4. No document validation: Doesn't verify the documents were successfully indexed/merged

Consider:

  • Counting child directories before and after refresh to confirm cleanup actually occurred
  • Using a more robust filter (e.g., checking for the CHILD_DIRECTORY_PREFIX constant from line 56 of CompositeIndexWriter)
  • Verifying the documents are accessible after refresh via a DirectoryReader
🔎 Example improvement
+// Count child directories before refresh
+long preRefreshCount = Files.find(
+    dataPath,
+    1, 
+    (path, attrs) -> attrs.isDirectory() && path.getFileName().toString().startsWith(BucketedCompositeDirectory.CHILD_DIRECTORY_PREFIX)
+).count();
+assertTrue("Expected child directories before refresh", preRefreshCount > 0);
+
 compositeIndexWriter.beforeRefresh();
 compositeIndexWriter.afterRefresh(true);
 
-long directoryCount = Files.find(
+long postRefreshCount = Files.find(
     dataPath,
     1,
-    (path, attributes) -> attributes.isDirectory() == true && path.endsWith("extra0") == false
-).count() - 1;
-// Ensure no child directory is pending here.
-assertEquals(0, directoryCount);
+    (path, attrs) -> attrs.isDirectory() && path.getFileName().toString().startsWith(BucketedCompositeDirectory.CHILD_DIRECTORY_PREFIX)
+).count();
+assertEquals("Child directories should be cleaned up after refresh", 0, postRefreshCount);
+
+// Verify documents are still accessible
+try (DirectoryReader reader = DirectoryReader.open(compositeIndexWriter.getAccumulatingIndexWriter())) {
+    assertEquals("Both documents should be in parent writer", 2, reader.numDocs());
+}
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9fb528a and f871f27.

📒 Files selected for processing (3)
  • CHANGELOG.md
  • server/src/main/java/org/opensearch/index/engine/CompositeIndexWriter.java
  • server/src/test/java/org/opensearch/index/engine/CompositeIndexWriterForAppendTests.java
🚧 Files skipped from review as they are similar to previous changes (1)
  • CHANGELOG.md
🧰 Additional context used
🧬 Code graph analysis (1)
server/src/main/java/org/opensearch/index/engine/CompositeIndexWriter.java (1)
libs/common/src/main/java/org/opensearch/common/util/io/IOUtils.java (1)
  • IOUtils (58-317)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
  • GitHub Check: gradle-check
  • GitHub Check: assemble (25, ubuntu-24.04-arm)
  • GitHub Check: assemble (21, windows-latest)
  • GitHub Check: assemble (21, ubuntu-24.04-arm)
  • GitHub Check: assemble (21, ubuntu-latest)
  • GitHub Check: assemble (25, ubuntu-latest)
  • GitHub Check: assemble (25, windows-latest)
  • GitHub Check: precommit (21, windows-2025, true)
  • GitHub Check: precommit (25, ubuntu-24.04-arm)
  • GitHub Check: precommit (25, macos-15)
  • GitHub Check: precommit (21, macos-15)
  • GitHub Check: precommit (25, windows-latest)
  • GitHub Check: precommit (21, ubuntu-24.04-arm)
  • GitHub Check: precommit (25, macos-15-intel)
  • GitHub Check: precommit (21, macos-15-intel)
  • GitHub Check: precommit (25, ubuntu-latest)
  • GitHub Check: precommit (21, windows-latest)
  • GitHub Check: precommit (21, ubuntu-latest)
  • GitHub Check: detect-breaking-change
  • GitHub Check: Analyze (java)
🔇 Additional comments (3)
server/src/main/java/org/opensearch/index/engine/CompositeIndexWriter.java (1)

22-23: LGTM!

The new imports are necessary for the directory cleanup functionality introduced in this PR.

Also applies to: 44-44

server/src/test/java/org/opensearch/index/engine/CompositeIndexWriterForAppendTests.java (2)

32-33: LGTM!

The imports are required for filesystem verification in the new test.


234-275: LGTM!

The concurrency test appropriately validates that the refresh mechanism (including the new directory cleanup logic) handles concurrent indexing operations without deadlocks or crashes. The test structure is sound with proper thread coordination and cleanup.

@github-actions
Copy link
Copy Markdown
Contributor

❌ Gradle check result for f871f27: FAILURE

Please examine the workflow log, locate, and copy-paste the failure(s) below, then iterate to green. Is the failure a flaky test unrelated to your change?

Copy link
Copy Markdown
Contributor

@Bukhtawar Bukhtawar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@github-actions
Copy link
Copy Markdown
Contributor

❌ Gradle check result for 660a498: FAILURE

Please examine the workflow log, locate, and copy-paste the failure(s) below, then iterate to green. Is the failure a flaky test unrelated to your change?

@github-actions
Copy link
Copy Markdown
Contributor

❌ Gradle check result for 04b9946: FAILURE

Please examine the workflow log, locate, and copy-paste the failure(s) below, then iterate to green. Is the failure a flaky test unrelated to your change?

Signed-off-by: RS146BIJAY <rishavsagar4b1@gmail.com>
@github-actions
Copy link
Copy Markdown
Contributor

✅ Gradle check result for 289dd9f: SUCCESS

@codecov
Copy link
Copy Markdown

codecov bot commented Jan 24, 2026

Codecov Report

❌ Patch coverage is 75.00000% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 73.25%. Comparing base (967c809) to head (289dd9f).
⚠️ Report is 6 commits behind head on main.

Files with missing lines Patch % Lines
.../opensearch/index/engine/CompositeIndexWriter.java 75.00% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##               main   #20326      +/-   ##
============================================
+ Coverage     73.23%   73.25%   +0.02%     
- Complexity    71953    72015      +62     
============================================
  Files          5795     5796       +1     
  Lines        329248   329274      +26     
  Branches      47410    47415       +5     
============================================
+ Hits         241122   241212      +90     
+ Misses        68841    68779      -62     
+ Partials      19285    19283       -2     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants