Skip to content

Add tiered-storage module with stored fields prefetch support - #20962

Closed
GeekGlider wants to merge 2 commits into
opensearch-project:mainfrom
GeekGlider:feature/tiered-storage-module
Closed

Add tiered-storage module with stored fields prefetch support#20962
GeekGlider wants to merge 2 commits into
opensearch-project:mainfrom
GeekGlider:feature/tiered-storage-module

Conversation

@GeekGlider

@GeekGlider GeekGlider commented Mar 23, 2026

Copy link
Copy Markdown
Contributor

Description

Adds the tiered-storage module to support writable warm index features. This module introduces stored fields prefetching for tiered storage indices, which prefetches stored fields during the fetch phase of search to improve read performance on warm indices.

The module includes:

  • TieredStoragePlugin — the main plugin class that registers cluster settings and a search operation listener
  • StoredFieldsPrefetch — a SearchOperationListener that prefetches stored fields when the feature flag is enabled
  • TieredStoragePrefetchSettings — cluster-level settings to control prefetch behavior (read-ahead block count and enable/disable toggle)
  • Unit tests for prefetch settings and the stored fields prefetch listener

Check List

  • Functionality includes testing.
  • API changes companion pull request created, if applicable.
  • Public documentation issue/PR created, if applicable.

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
For more information on following Developer Certificate of Origin and signing off your commits, please check here.

@GeekGlider
GeekGlider requested a review from a team as a code owner March 23, 2026 09:23
@github-actions

github-actions Bot commented Mar 23, 2026

Copy link
Copy Markdown
Contributor

PR Reviewer Guide 🔍

(Review updated until commit 32a94e8)

Here are some key observations to aid the review process:

🧪 PR contains tests
🔒 No security concerns identified
✅ No TODO sections
🔀 Multiple PR themes

Sub-PR theme: Add TieredStoragePrefetchSettings with cluster settings support

Relevant files:

  • modules/tiered-storage/src/main/java/org/opensearch/storage/prefetch/TieredStoragePrefetchSettings.java
  • modules/tiered-storage/src/test/java/org/opensearch/storage/prefetch/TieredStoragePrefetchSettingsTests.java

Sub-PR theme: Add TieredStoragePlugin and StoredFieldsPrefetch listener

Relevant files:

  • modules/tiered-storage/src/main/java/org/opensearch/storage/TieredStoragePlugin.java
  • modules/tiered-storage/src/main/java/org/opensearch/storage/prefetch/StoredFieldsPrefetch.java
  • modules/tiered-storage/src/main/java/org/opensearch/storage/package-info.java
  • modules/tiered-storage/src/main/java/org/opensearch/storage/prefetch/package-info.java
  • modules/tiered-storage/src/test/java/org/opensearch/storage/prefetch/StoredFieldsPrefetchTests.java
  • CHANGELOG.md

⚡ Recommended focus areas for review

Possible NPE

In executePrefetch, when currentReaderIndex != readerIndex is true and the reader is not a SegmentReader, currentReader is set to null and the loop continues. However, currentReaderContext is also updated to the new context before the check. On the next iteration, if the same readerIndex is encountered again (i.e., currentReaderIndex == readerIndex), the code skips the reader-unwrapping block and proceeds with currentReader == null, which is handled. But currentReaderContext could be stale from a previous segment if the non-SegmentReader segment is revisited — this is a minor inconsistency. More critically, the assert currentReaderContext != null on line 82 will only fire in assertions-enabled JVMs and does not protect production code.

int readerIndex = ReaderUtil.subIndex(docId, context.searcher().getIndexReader().leaves());
if (currentReaderIndex != readerIndex) {
    currentReaderContext = context.searcher().getIndexReader().leaves().get(readerIndex);
    currentReaderIndex = readerIndex;

    // Unwrap the reader here
    LeafReader innerLeafReader = currentReaderContext.reader();
    while (innerLeafReader instanceof FilterLeafReader) {
        innerLeafReader = ((FilterLeafReader) innerLeafReader).getDelegate();
    }
    // never be the case, just sanity check
    if (!(innerLeafReader instanceof SegmentReader)) {
        // disable prefetch on stored fields for this segment
        log.warn("Unexpected reader type [{}], skipping stored fields prefetch", innerLeafReader.getClass().getName());
        currentReader = null;
        continue;
    }
    currentReader = innerLeafReader.storedFields();
}
assert currentReaderContext != null;
if (currentReader == null) {
    continue;
}
log.debug(
    "Prefetching stored fields for index shard: {}, docId: {}, readerIndex: {}",
    context.indexShard().shardId(),
    docId,
    readerIndex
);

// nested docs logic
final int subDocId = docId - currentReaderContext.docBase;
final int rootDocId = findRootDocumentIfNested(context, currentReaderContext, subDocId);
if (rootDocId != -1) {
    currentReader.prefetch(rootDocId);
}
currentReader.prefetch(subDocId);
Double Prefetch

When a document is a root document (not nested), findRootDocumentIfNested returns -1 and only currentReader.prefetch(subDocId) is called — correct. But when a document IS a nested child, both currentReader.prefetch(rootDocId) and currentReader.prefetch(subDocId) are called. If the same root document appears as the root of multiple nested children in the same fetch batch, prefetch(rootDocId) will be called redundantly for each child, potentially causing unnecessary I/O or overhead.

// nested docs logic
final int subDocId = docId - currentReaderContext.docBase;
final int rootDocId = findRootDocumentIfNested(context, currentReaderContext, subDocId);
if (rootDocId != -1) {
    currentReader.prefetch(rootDocId);
}
currentReader.prefetch(subDocId);
Null Supplier Risk

getPrefetchSettingsSupplier() returns a supplier that reads this.tieredStoragePrefetchSettings, which is only initialized in createComponents(). If onIndexModule() is called before createComponents() (e.g., during plugin initialization ordering), the supplier will return null. While StoredFieldsPrefetch.checkIfStoredFieldsPrefetchEnabled() handles a null settings object gracefully, this silent null-return could mask initialization ordering bugs.

public Supplier<TieredStoragePrefetchSettings> getPrefetchSettingsSupplier() {
    return () -> this.tieredStoragePrefetchSettings;
}

@GeekGlider
GeekGlider force-pushed the feature/tiered-storage-module branch from eb42398 to a3c9bdd Compare March 23, 2026 09:25
@github-actions

github-actions Bot commented Mar 23, 2026

Copy link
Copy Markdown
Contributor

PR Code Suggestions ✨

Latest suggestions up to 32a94e8

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Possible issue
Avoid double-prefetching nested child documents

When a nested document is found, subDocId is a nested child doc and should not be
prefetched independently — only the root document should be prefetched. Prefetching
the child subDocId after the root may cause redundant or incorrect I/O. Consider
skipping the currentReader.prefetch(subDocId) call when rootDocId != -1.

modules/tiered-storage/src/main/java/org/opensearch/storage/prefetch/StoredFieldsPrefetch.java [95-99]

 final int rootDocId = findRootDocumentIfNested(context, currentReaderContext, subDocId);
 if (rootDocId != -1) {
     currentReader.prefetch(rootDocId);
+} else {
+    currentReader.prefetch(subDocId);
 }
-currentReader.prefetch(subDocId);
Suggestion importance[1-10]: 6

__

Why: The suggestion raises a valid concern: when a nested child doc is found, prefetching both the root and the child subDocId may be redundant or incorrect. However, it's also possible that prefetching the child doc is intentional for certain use cases, so this is a moderate-impact suggestion worth considering.

Low
General
Reset reader index on unsupported reader type

Setting currentReader = null and using continue skips the current iteration but does
not reset currentReaderIndex, so subsequent docs in the same segment will also skip
the currentReaderIndex != readerIndex branch and use the stale null currentReader
which is actually the intended behavior. However, the currentReaderIndex should also
be reset to -1 to force re-evaluation if the same segment is encountered again after
a different segment, preventing incorrect skipping.

modules/tiered-storage/src/main/java/org/opensearch/storage/prefetch/StoredFieldsPrefetch.java [70-79]

 while (innerLeafReader instanceof FilterLeafReader) {
     innerLeafReader = ((FilterLeafReader) innerLeafReader).getDelegate();
 }
 // never be the case, just sanity check
 if (!(innerLeafReader instanceof SegmentReader)) {
     // disable prefetch on stored fields for this segment
     log.warn("Unexpected reader type [{}], skipping stored fields prefetch", innerLeafReader.getClass().getName());
     currentReader = null;
+    currentReaderIndex = -1;
     continue;
 }
Suggestion importance[1-10]: 4

__

Why: The suggestion about resetting currentReaderIndex to -1 is logically sound in theory, but the comment in the code says this case should "never be the case" — it's a sanity check. In practice, doc IDs are typically sorted by segment, so the scenario described (same segment encountered again after a different one) is unlikely. The improvement is minor.

Low
Document initialization ordering dependency in plugin lifecycle

onIndexModule is called once per index, so a new StoredFieldsPrefetch instance is
created for every index. Since StoredFieldsPrefetch is stateless and only holds a
supplier reference, a single shared instance could be reused. More importantly, if
tieredStoragePrefetchSettings has not yet been initialized (i.e., createComponents
has not been called), the supplier will return null, which is handled gracefully —
but this ordering dependency should be documented or guarded.

modules/tiered-storage/src/main/java/org/opensearch/storage/TieredStoragePlugin.java [167-172]

 public void onIndexModule(IndexModule indexModule) {
     if (FeatureFlags.isEnabled(FeatureFlags.WRITABLE_WARM_INDEX_EXPERIMENTAL_FLAG)) {
         // indexModule.addSearchOperationListener(new TieredStorageSearchSlowLog(indexModule.getIndexSettings()));
+        // Note: tieredStoragePrefetchSettings is initialized in createComponents; supplier returns null until then.
         indexModule.addSearchOperationListener(new StoredFieldsPrefetch(getPrefetchSettingsSupplier()));
     }
 }
Suggestion importance[1-10]: 1

__

Why: The improved_code only adds a comment and doesn't change any logic. The suggestion asks to document a behavior rather than fix it, and the existing code already handles the null case gracefully in checkIfStoredFieldsPrefetchEnabled.

Low

Previous suggestions

Suggestions up to commit 27724de
CategorySuggestion                                                                                                                                    Impact
Possible issue
Fix stale reader index on unsupported reader type

When a non-SegmentReader is encountered and currentReader is set to null, the
continue skips only the current iteration but currentReader remains null for
subsequent docs in the same segment (since currentReaderIndex won't change). This is
actually handled by the if (currentReader == null) continue; check below, but the
currentReaderIndex is still updated to the new segment, so the reader won't be
re-evaluated. Consider resetting currentReaderIndex to -1 as well so the reader is
re-evaluated on the next doc.

modules/tiered-storage/src/main/java/org/opensearch/storage/prefetch/StoredFieldsPrefetch.java [70-79]

 while (innerLeafReader instanceof FilterLeafReader) {
     innerLeafReader = ((FilterLeafReader) innerLeafReader).getDelegate();
 }
 // never be the case, just sanity check
 if (!(innerLeafReader instanceof SegmentReader)) {
     // disable prefetch on stored fields for this segment
     log.warn("Unexpected reader type [{}], skipping stored fields prefetch", innerLeafReader.getClass().getName());
     currentReader = null;
+    currentReaderIndex = -1;
     continue;
 }
Suggestion importance[1-10]: 7

__

Why: When a non-SegmentReader is encountered, currentReaderIndex is updated to the new segment index but currentReader is set to null. For subsequent docs in the same segment, the currentReaderIndex != readerIndex check won't trigger, so the reader won't be re-evaluated. Resetting currentReaderIndex to -1 ensures the reader is properly re-evaluated, preventing silent skipping of all docs in that segment.

Medium
Avoid double-prefetching nested and root docs

When a nested document is found, subDocId is a nested child doc and should not be
prefetched independently — only the root document should be prefetched. Prefetching
both the nested child and the root may result in redundant or incorrect I/O.
Consider only prefetching subDocId when rootDocId == -1 (i.e., the doc is itself a
root doc).

modules/tiered-storage/src/main/java/org/opensearch/storage/prefetch/StoredFieldsPrefetch.java [95-99]

 final int rootDocId = findRootDocumentIfNested(context, currentReaderContext, subDocId);
 if (rootDocId != -1) {
     currentReader.prefetch(rootDocId);
+} else {
+    currentReader.prefetch(subDocId);
 }
-currentReader.prefetch(subDocId);
Suggestion importance[1-10]: 6

__

Why: The current code prefetches both the root doc and the nested child doc when a nested document is found. Since the nested child's stored fields are typically accessed via the root document, prefetching subDocId separately when it's a nested child may be redundant or incorrect. The suggested fix to only prefetch subDocId when rootDocId == -1 is logically sound.

Low
General
Document implicit initialization ordering dependency

onIndexModule is called once per index, and each call creates a new
StoredFieldsPrefetch instance wrapping the same settings supplier. This is fine
functionally, but if tieredStoragePrefetchSettings is null at the time onIndexModule
is called (before createComponents completes), the supplier will return null. The
checkIfStoredFieldsPrefetchEnabled method handles this gracefully, but it's worth
noting that the ordering dependency between createComponents and onIndexModule is
implicit and fragile. Consider adding a null-guard or documentation to make this
dependency explicit.

modules/tiered-storage/src/main/java/org/opensearch/storage/TieredStoragePlugin.java [166-172]

 @Override
 public void onIndexModule(IndexModule indexModule) {
     if (FeatureFlags.isEnabled(FeatureFlags.WRITABLE_WARM_INDEX_EXPERIMENTAL_FLAG)) {
-        // indexModule.addSearchOperationListener(new TieredStorageSearchSlowLog(indexModule.getIndexSettings()));
+        // StoredFieldsPrefetch uses a supplier, so it will safely handle the case where
+        // tieredStoragePrefetchSettings is null (before createComponents is called).
         indexModule.addSearchOperationListener(new StoredFieldsPrefetch(getPrefetchSettingsSupplier()));
     }
 }
Suggestion importance[1-10]: 1

__

Why: This suggestion only adds a comment to document an existing behavior that is already handled gracefully by the null-check in checkIfStoredFieldsPrefetchEnabled. The improved_code is functionally identical to the existing_code, making this a documentation-only change with minimal impact.

Low
Suggestions up to commit 019b06c
CategorySuggestion                                                                                                                                    Impact
Possible issue
Handle IOException from storedFields() call

innerLeafReader.storedFields() can throw an IOException, but it is called outside
the try/catch block that wraps the rest of the per-document logic. This means an
IOException from storedFields() would propagate uncaught and abort the entire
prefetch loop. Move the storedFields() call inside the try block or handle the
exception explicitly.

modules/tiered-storage/src/main/java/org/opensearch/storage/prefetch/StoredFieldsPrefetch.java [63-81]

 int readerIndex = ReaderUtil.subIndex(docId, context.searcher().getIndexReader().leaves());
 if (currentReaderIndex != readerIndex) {
     currentReaderContext = context.searcher().getIndexReader().leaves().get(readerIndex);
     currentReaderIndex = readerIndex;
 
     // Unwrap the reader here
     LeafReader innerLeafReader = currentReaderContext.reader();
     while (innerLeafReader instanceof FilterLeafReader) {
         innerLeafReader = ((FilterLeafReader) innerLeafReader).getDelegate();
     }
     // never be the case, just sanity check
     if (!(innerLeafReader instanceof SegmentReader)) {
         // disable prefetch on stored fields for this segment
         log.warn("Unexpected reader type [{}], skipping stored fields prefetch", innerLeafReader.getClass().getName());
         currentReader = null;
         continue;
     }
-    currentReader = innerLeafReader.storedFields();
+    try {
+        currentReader = innerLeafReader.storedFields();
+    } catch (IOException e) {
+        log.warn("Failed to get storedFields for segment, skipping prefetch", e);
+        currentReader = null;
+        continue;
+    }
 }
Suggestion importance[1-10]: 7

__

Why: The innerLeafReader.storedFields() call at line 80 can throw IOException but is inside the try/catch(Exception e) block that wraps the entire per-document logic (lines 62-102), so it is already caught. However, the suggestion to handle it separately with a continue is still valid for better error isolation and to avoid skipping subsequent documents in the same segment.

Medium
General
Avoid redundant prefetch of nested child documents

When a document is nested, subDocId is a child document and rootDocId is the parent.
Prefetching subDocId when it is a nested (non-root) document may be unnecessary or
incorrect, since the stored fields of interest are on the root document. Consider
only prefetching subDocId when it is not a nested child (i.e., when rootDocId ==
-1).

modules/tiered-storage/src/main/java/org/opensearch/storage/prefetch/StoredFieldsPrefetch.java [95-99]

 final int rootDocId = findRootDocumentIfNested(context, currentReaderContext, subDocId);
 if (rootDocId != -1) {
     currentReader.prefetch(rootDocId);
+} else {
+    currentReader.prefetch(subDocId);
 }
-currentReader.prefetch(subDocId);
Suggestion importance[1-10]: 6

__

Why: When a document is nested, prefetching both the root and the child subDocId may be redundant or incorrect since stored fields of interest are on the root document. The suggested change to only prefetch subDocId when it's not a nested child is a reasonable optimization, though the impact depends on the actual stored fields structure.

Low
Guard against uninitialized settings in index module hook

onIndexModule is called once per index, so a new StoredFieldsPrefetch instance is
created for every index. Since StoredFieldsPrefetch is stateless (it only holds the
settings supplier), a single shared instance could be reused. More importantly, if
tieredStoragePrefetchSettings has not yet been initialized (i.e., createComponents
has not been called), the supplier will return null, which is handled, but this
ordering dependency is fragile. Consider documenting or asserting this dependency.

modules/tiered-storage/src/main/java/org/opensearch/storage/TieredStoragePlugin.java [167-172]

+@Override
 public void onIndexModule(IndexModule indexModule) {
     if (FeatureFlags.isEnabled(FeatureFlags.WRITABLE_WARM_INDEX_EXPERIMENTAL_FLAG)) {
-        // indexModule.addSearchOperationListener(new TieredStorageSearchSlowLog(indexModule.getIndexSettings()));
+        assert tieredStoragePrefetchSettings != null : "createComponents must be called before onIndexModule";
         indexModule.addSearchOperationListener(new StoredFieldsPrefetch(getPrefetchSettingsSupplier()));
     }
 }
Suggestion importance[1-10]: 3

__

Why: Adding an assert for the initialization order dependency is a minor defensive improvement, but the null case is already gracefully handled in checkIfStoredFieldsPrefetchEnabled(). The assert would only fire in assertion-enabled JVMs and doesn't change production behavior.

Low
Suggestions up to commit 380ff14
CategorySuggestion                                                                                                                                    Impact
Possible issue
Add null check for reader context safety

The assertion assert currentReaderContext != null; earlier in the code is
insufficient for null safety. If currentReaderContext is null when reaching the
prefetch calls, a NullPointerException will occur. Add an explicit null check before
using currentReaderContext.docBase to ensure robustness.

modules/tiered-storage/src/main/java/org/opensearch/storage/prefetch/StoredFieldsPrefetch.java [83-99]

-if (currentReader == null) {
+if (currentReader == null || currentReaderContext == null) {
     continue;
 }
 log.debug(
     "Prefetching stored fields for index shard: {}, docId: {}, readerIndex: {}",
     context.indexShard().shardId(),
     docId,
     readerIndex
 );
 
 // nested docs logic
 final int subDocId = docId - currentReaderContext.docBase;
 final int rootDocId = findRootDocumentIfNested(context, currentReaderContext, subDocId);
 if (rootDocId != -1) {
     currentReader.prefetch(rootDocId);
 }
 currentReader.prefetch(subDocId);
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies a potential null pointer risk with currentReaderContext. While an assertion exists at line 82, assertions can be disabled at runtime. Adding an explicit null check alongside the currentReader == null check at line 83 improves robustness. However, the assertion immediately preceding this code makes the risk lower in practice, and the improvement is defensive rather than addressing an actual bug, limiting the score to 7.

Medium
Validate root document ID before returning

The findRootDocumentIfNested method may return an invalid document ID if
bits.nextSetBit(subDocId) returns -1 (indicating no set bit found). This could cause
issues in the prefetch logic. Add a validation check to ensure the returned root
document ID is valid before using it.

modules/tiered-storage/src/main/java/org/opensearch/storage/prefetch/StoredFieldsPrefetch.java [106-114]

 private int findRootDocumentIfNested(SearchContext context, LeafReaderContext subReaderContext, int subDocId) throws IOException {
     if (context.mapperService().hasNested()) {
         BitSet bits = context.bitsetFilterCache().getBitSetProducer(Queries.newNonNestedFilter()).getBitSet(subReaderContext);
         if (bits != null && !bits.get(subDocId)) {
-            return bits.nextSetBit(subDocId);
+            int rootDocId = bits.nextSetBit(subDocId);
+            if (rootDocId != -1) {
+                return rootDocId;
+            }
         }
     }
     return -1;
 }
Suggestion importance[1-10]: 6

__

Why: The suggestion correctly identifies that bits.nextSetBit(subDocId) can return -1, and the improved code adds a validation check. However, examining the existing code at line 96-98, the caller already checks if (rootDocId != -1) before using the returned value, making this defensive check redundant. The suggestion is valid but offers only marginal improvement since the caller properly validates the return value.

Low
Suggestions up to commit d5b58e4
CategorySuggestion                                                                                                                                    Impact
Possible issue
Avoid double-prefetching root and child documents

When a nested document is found, subDocId is a child document and rootDocId is the
parent. Prefetching subDocId after rootDocId may be redundant or incorrect —
typically you only want to prefetch the root document for nested docs. Additionally,
if rootDocId == subDocId (the doc is already a root), both calls would prefetch the
same document unnecessarily.

modules/tiered-storage/src/main/java/org/opensearch/storage/prefetch/StoredFieldsPrefetch.java [95-99]

 final int rootDocId = findRootDocumentIfNested(context, currentReaderContext, subDocId);
 if (rootDocId != -1) {
     currentReader.prefetch(rootDocId);
+} else {
+    currentReader.prefetch(subDocId);
 }
-currentReader.prefetch(subDocId);
Suggestion importance[1-10]: 6

__

Why: The current code prefetches both the rootDocId and subDocId when a nested document is found, which may be redundant since the root document's stored fields typically contain the nested child data. The suggested fix to use else avoids unnecessary double-prefetching, though the actual impact depends on the Lucene stored fields implementation.

Low
General
Fix reader index update on invalid segment type

When the reader changes to a new segment that is not a SegmentReader, currentReader
is set to null and the loop continues to the next doc. However, currentReaderIndex
is updated to readerIndex before the check, so subsequent docs in the same invalid
segment will skip the unwrapping logic entirely and fall through to currentReader ==
null silently. The currentReaderIndex should only be updated when a valid reader is
successfully obtained.

modules/tiered-storage/src/main/java/org/opensearch/storage/prefetch/StoredFieldsPrefetch.java [56-81]

-int currentReaderIndex = -1;
-LeafReaderContext currentReaderContext = null;
-StoredFields currentReader = null;
-...
-for (int index = 0; index < context.docIdsToLoadSize(); index++) {
-    int docId = context.docIdsToLoad()[context.docIdsToLoadFrom() + index];
-    try {
-        int readerIndex = ReaderUtil.subIndex(docId, context.searcher().getIndexReader().leaves());
-        if (currentReaderIndex != readerIndex) {
-            ...
-            if (!(innerLeafReader instanceof SegmentReader)) {
-                ...
-                currentReader = null;
-                continue;
-            }
-            currentReader = innerLeafReader.storedFields();
-        }
+if (currentReaderIndex != readerIndex) {
+    currentReaderContext = context.searcher().getIndexReader().leaves().get(readerIndex);
 
+    LeafReader innerLeafReader = currentReaderContext.reader();
+    while (innerLeafReader instanceof FilterLeafReader) {
+        innerLeafReader = ((FilterLeafReader) innerLeafReader).getDelegate();
+    }
+    if (!(innerLeafReader instanceof SegmentReader)) {
+        log.warn("Unexpected reader type [{}], skipping stored fields prefetch", innerLeafReader.getClass().getName());
+        currentReader = null;
+        currentReaderIndex = readerIndex; // still update so we don't re-warn for every doc
+        continue;
+    }
+    currentReaderIndex = readerIndex;
+    currentReader = innerLeafReader.storedFields();
+}
+
Suggestion importance[1-10]: 3

__

Why: The suggestion points out that currentReaderIndex is updated before the SegmentReader check, but looking at the actual code, currentReaderIndex = readerIndex is set at line 66 before the validity check. However, the improved code in the suggestion also updates currentReaderIndex in the invalid case (with a comment), making the behavior essentially the same as the original. The suggestion's improved code doesn't meaningfully differ from the existing behavior.

Low
Suggestions up to commit 164717b
CategorySuggestion                                                                                                                                    Impact
Possible issue
Close StoredFields resources to prevent leaks

StoredFields obtained via innerLeafReader.storedFields() may hold resources and
should be closed after use to avoid resource leaks. Since a new StoredFields
instance is obtained per segment, the previous one should be closed before
reassigning currentReader, and the final one should be closed after the loop.

modules/tiered-storage/src/main/java/org/opensearch/storage/prefetch/StoredFieldsPrefetch.java [56-81]

 int currentReaderIndex = -1;
 LeafReaderContext currentReaderContext = null;
 StoredFields currentReader = null;
 ...
 for (int index = 0; index < context.docIdsToLoadSize(); index++) {
     int docId = context.docIdsToLoad()[context.docIdsToLoadFrom() + index];
     try {
         int readerIndex = ReaderUtil.subIndex(docId, context.searcher().getIndexReader().leaves());
         if (currentReaderIndex != readerIndex) {
+            if (currentReader != null) {
+                currentReader.close();
+                currentReader = null;
+            }
             ...
             currentReader = innerLeafReader.storedFields();
         }
+    } catch (Exception e) {
+        log.warn("Failed to prefetch stored fields for docId: " + docId, e);
+    }
+}
+if (currentReader != null) {
+    try { currentReader.close(); } catch (Exception e) { log.warn("Failed to close StoredFields", e); }
+}
Suggestion importance[1-10]: 6

__

Why: Resource leak prevention is important, but StoredFields obtained from a SegmentReader typically delegates to the reader's own resources and may not require explicit closing. The improved_code uses ellipsis (...) which doesn't accurately reflect the full code change, making it harder to evaluate precisely.

Low
Guard against invalid root document lookup result

bits.nextSetBit(subDocId) may return -1 if no set bit exists at or after subDocId
(e.g., at the end of the segment), which would be indistinguishable from the "not
nested" return value of -1. This could cause a call to currentReader.prefetch(-1) to
be skipped silently, but if the sentinel value changes or the logic is reused, it
could cause incorrect behavior. Add a guard to handle the case where nextSetBit
returns -1.

modules/tiered-storage/src/main/java/org/opensearch/storage/prefetch/StoredFieldsPrefetch.java [106-114]

 private int findRootDocumentIfNested(SearchContext context, LeafReaderContext subReaderContext, int subDocId) throws IOException {
     if (context.mapperService().hasNested()) {
         BitSet bits = context.bitsetFilterCache().getBitSetProducer(Queries.newNonNestedFilter()).getBitSet(subReaderContext);
         if (bits != null && !bits.get(subDocId)) {
-            return bits.nextSetBit(subDocId);
+            int rootDocId = bits.nextSetBit(subDocId);
+            if (rootDocId == DocIdSetIterator.NO_MORE_DOCS) {
+                return -1;
+            }
+            return rootDocId;
         }
     }
     return -1;
 }
Suggestion importance[1-10]: 5

__

Why: The concern about nextSetBit returning -1 (or DocIdSetIterator.NO_MORE_DOCS) is valid as a defensive check, but in practice Lucene's BitSet.nextSetBit returns -1 when no bit is found, which already matches the sentinel value used. The improved_code introduces DocIdSetIterator.NO_MORE_DOCS which is Integer.MAX_VALUE, not -1, making the guard condition potentially incorrect.

Low
General
Avoid redundant prefetch for nested child documents

When a nested document is found, the root document is prefetched first, but then
subDocId is also prefetched unconditionally. For nested documents, the stored fields
are stored at the root document level, so prefetching subDocId (the nested child) is
redundant and wasteful. Consider only prefetching subDocId when it is not a nested
document (i.e., rootDocId == -1).

modules/tiered-storage/src/main/java/org/opensearch/storage/prefetch/StoredFieldsPrefetch.java [95-99]

 final int rootDocId = findRootDocumentIfNested(context, currentReaderContext, subDocId);
 if (rootDocId != -1) {
     currentReader.prefetch(rootDocId);
+} else {
+    currentReader.prefetch(subDocId);
 }
-currentReader.prefetch(subDocId);
Suggestion importance[1-10]: 5

__

Why: The suggestion has merit in avoiding potentially redundant prefetch calls for nested child documents, but it's not entirely clear that prefetching subDocId is always wasteful - it depends on how Lucene stores nested documents. The logic change is reasonable but may alter behavior in edge cases.

Low

@github-actions

Copy link
Copy Markdown
Contributor

Persistent review updated to latest commit a3c9bdd

@GeekGlider
GeekGlider force-pushed the feature/tiered-storage-module branch from a3c9bdd to 799eb95 Compare March 23, 2026 10:02
@github-actions

Copy link
Copy Markdown
Contributor

Persistent review updated to latest commit 799eb95

@GeekGlider
GeekGlider force-pushed the feature/tiered-storage-module branch from 799eb95 to a6eb66a Compare March 23, 2026 10:20
@github-actions

Copy link
Copy Markdown
Contributor

Persistent review updated to latest commit a6eb66a

@github-actions

Copy link
Copy Markdown
Contributor

❌ Gradle check result for a6eb66a: 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?

@GeekGlider
GeekGlider force-pushed the feature/tiered-storage-module branch from a6eb66a to b00a2bc Compare March 23, 2026 11:25
@github-actions

Copy link
Copy Markdown
Contributor

Persistent review updated to latest commit b00a2bc

@github-actions

Copy link
Copy Markdown
Contributor

❌ Gradle check result for b00a2bc: 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?

@GeekGlider
GeekGlider force-pushed the feature/tiered-storage-module branch from b00a2bc to 4e87c8d Compare March 23, 2026 12:12
@github-actions

Copy link
Copy Markdown
Contributor

Persistent review updated to latest commit 4e87c8d

@github-actions

Copy link
Copy Markdown
Contributor

❌ Gradle check result for 4e87c8d: 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?

@GeekGlider
GeekGlider force-pushed the feature/tiered-storage-module branch from 4e87c8d to f0959d8 Compare March 23, 2026 13:05
@github-actions

Copy link
Copy Markdown
Contributor

Persistent review updated to latest commit f0959d8

@github-actions

Copy link
Copy Markdown
Contributor

❌ Gradle check result for f0959d8: 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?

@kkewwei

kkewwei commented Mar 23, 2026

Copy link
Copy Markdown
Contributor

@GeekGlider Same with PR #20176.

Could you explain why a new module is required to implement this functionality? If the requirement is simply to add prefetching for stored fields, creating a separate module may not be appropriate.

@github-actions

Copy link
Copy Markdown
Contributor

Persistent review updated to latest commit afb0654

@github-actions

Copy link
Copy Markdown
Contributor

❌ Gradle check result for afb0654: 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?

@GeekGlider
GeekGlider force-pushed the feature/tiered-storage-module branch from afb0654 to d91f0af Compare March 24, 2026 05:42
@github-actions

Copy link
Copy Markdown
Contributor

Persistent review updated to latest commit 164717b

@GeekGlider
GeekGlider force-pushed the feature/tiered-storage-module branch from 164717b to d5b58e4 Compare March 24, 2026 05:49
@github-actions

Copy link
Copy Markdown
Contributor

Persistent review updated to latest commit d5b58e4

@GeekGlider
GeekGlider force-pushed the feature/tiered-storage-module branch from d5b58e4 to 380ff14 Compare March 24, 2026 06:34
@github-actions

Copy link
Copy Markdown
Contributor

Persistent review updated to latest commit 380ff14

@github-actions

Copy link
Copy Markdown
Contributor

✅ Gradle check result for 380ff14: SUCCESS

@codecov

codecov Bot commented Mar 24, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 90.41096% with 7 lines in your changes missing coverage. Please review.
✅ Project coverage is 73.11%. Comparing base (14faf38) to head (f804575).
⚠️ Report is 17 commits behind head on main.

Files with missing lines Patch % Lines
...nsearch/storage/prefetch/StoredFieldsPrefetch.java 87.50% 4 Missing and 2 partials ⚠️
...va/org/opensearch/storage/TieredStoragePlugin.java 87.50% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##               main   #20962      +/-   ##
============================================
- Coverage     73.16%   73.11%   -0.05%     
+ Complexity    72545    72540       -5     
============================================
  Files          5848     5851       +3     
  Lines        331982   332055      +73     
  Branches      47949    47958       +9     
============================================
- Hits         242892   242792     -100     
- Misses        69561    69763     +202     
+ Partials      19529    19500      -29     

☔ 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.

@github-actions

Copy link
Copy Markdown
Contributor

Persistent review updated to latest commit 019b06c

@GeekGlider
GeekGlider force-pushed the feature/tiered-storage-module branch from 019b06c to 27724de Compare March 24, 2026 13:31
@github-actions

Copy link
Copy Markdown
Contributor

Persistent review updated to latest commit 27724de

@GeekGlider
GeekGlider force-pushed the feature/tiered-storage-module branch from 27724de to 32a94e8 Compare March 24, 2026 13:34
@github-actions

Copy link
Copy Markdown
Contributor

Persistent review updated to latest commit 32a94e8

@github-actions

Copy link
Copy Markdown
Contributor

✅ Gradle check result for 32a94e8: SUCCESS

@GeekGlider
GeekGlider force-pushed the feature/tiered-storage-module branch 2 times, most recently from f6fc12e to 7930987 Compare March 25, 2026 06:50
@github-actions

Copy link
Copy Markdown
Contributor

Failed to generate code suggestions for PR

@GeekGlider
GeekGlider force-pushed the feature/tiered-storage-module branch from 7930987 to 63c1b09 Compare March 25, 2026 08:52
@github-actions

Copy link
Copy Markdown
Contributor

Failed to generate code suggestions for PR

@github-actions

Copy link
Copy Markdown
Contributor

❌ Gradle check result for 63c1b09: null

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: Kavya Aggarwal <kavyaagg@amazon.com>
@GeekGlider
GeekGlider force-pushed the feature/tiered-storage-module branch from 63c1b09 to 2d106db Compare March 26, 2026 04:18
@github-actions

Copy link
Copy Markdown
Contributor

Failed to generate code suggestions for PR

@github-actions

Copy link
Copy Markdown
Contributor

✅ Gradle check result for 2d106db: SUCCESS

Signed-off-by: Kavya Aggarwal <kavyaagg@amazon.com>
@GeekGlider
GeekGlider force-pushed the feature/tiered-storage-module branch from 2d106db to f804575 Compare March 26, 2026 07:41
@github-actions

Copy link
Copy Markdown
Contributor

Failed to generate code suggestions for PR

@github-actions

Copy link
Copy Markdown
Contributor

✅ Gradle check result for f804575: SUCCESS

@GeekGlider GeekGlider closed this Apr 1, 2026
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