Repository navigation
Add TaxonomyReader#getBulkOrdinals method (#12180) #12769
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -38,14 +38,19 @@ | |
| import org.apache.lucene.index.MultiTerms; | ||
| import org.apache.lucene.index.PostingsEnum; | ||
| import org.apache.lucene.index.ReaderUtil; | ||
| import org.apache.lucene.index.TermsEnum; | ||
| import org.apache.lucene.search.DocIdSetIterator; | ||
| import org.apache.lucene.store.Directory; | ||
| import org.apache.lucene.util.Accountable; | ||
| import org.apache.lucene.util.Accountables; | ||
| import org.apache.lucene.util.ArrayUtil; | ||
| import org.apache.lucene.util.BytesRef; | ||
| import org.apache.lucene.util.BytesRefBuilder; | ||
| import org.apache.lucene.util.BytesRefComparator; | ||
| import org.apache.lucene.util.IOUtils; | ||
| import org.apache.lucene.util.InPlaceMergeSorter; | ||
| import org.apache.lucene.util.RamUsageEstimator; | ||
| import org.apache.lucene.util.StringSorter; | ||
|
|
||
| /** | ||
| * A {@link TaxonomyReader} which retrieves stored taxonomy information from a {@link Directory}. | ||
|
|
@@ -71,6 +76,11 @@ public class DirectoryTaxonomyReader extends TaxonomyReader implements Accountab | |
| private final long taxoEpoch; // used in doOpenIfChanged | ||
| private final DirectoryReader indexReader; | ||
|
|
||
| // We only store the fact that a category exists, not otherwise. | ||
| // This is required because the caches are shared with new DTR instances | ||
| // that are allocated from doOpenIfChanged. Therefore, if we only store | ||
| // information about found categories, we cannot accidentally tell a new | ||
| // generation of DTR that a category does not exist. | ||
| // TODO: test DoubleBarrelLRUCache and consider using it instead | ||
| private LRUHashMap<FacetLabel, Integer> ordinalCache; | ||
| private LRUHashMap<Integer, FacetLabel> categoryCache; | ||
|
|
@@ -298,12 +308,6 @@ public int getOrdinal(FacetLabel cp) throws IOException { | |
| 0); | ||
| if (docs != null && docs.nextDoc() != DocIdSetIterator.NO_MORE_DOCS) { | ||
| ret = docs.docID(); | ||
|
|
||
| // We only store the fact that a category exists, not otherwise. | ||
| // This is required because the caches are shared with new DTR instances | ||
| // that are allocated from doOpenIfChanged. Therefore, if we only store | ||
| // information about found categories, we cannot accidentally tell a new | ||
| // generation of DTR that a category does not exist. | ||
| synchronized (ordinalCache) { | ||
| ordinalCache.put(cp, ret); | ||
| } | ||
|
|
@@ -312,6 +316,117 @@ public int getOrdinal(FacetLabel cp) throws IOException { | |
| return ret; | ||
| } | ||
|
|
||
| @Override | ||
| public int[] getBulkOrdinals(FacetLabel... categoryPaths) throws IOException { | ||
| ensureOpen(); | ||
| if (categoryPaths.length == 0) { | ||
| return new int[0]; | ||
| } | ||
| if (categoryPaths.length == 1) { | ||
| return new int[] {getOrdinal(categoryPaths[0])}; | ||
| } | ||
| // First try to find results in the cache: | ||
| int[] result = new int[categoryPaths.length]; | ||
| int[] indexesMissingFromCache = new int[10]; // initial size, will grow when required | ||
| int numberOfMissingFromCache = 0; | ||
| FacetLabel cp; | ||
| Integer res; | ||
| for (int i = 0; i < categoryPaths.length; i++) { | ||
| cp = categoryPaths[i]; | ||
| synchronized (ordinalCache) { | ||
| res = ordinalCache.get(cp); | ||
| } | ||
| if (res != null) { | ||
| if (res < indexReader.maxDoc()) { | ||
| // Since the cache is shared with DTR instances allocated from | ||
| // doOpenIfChanged, we need to ensure that the ordinal is one that | ||
| // this DTR instance recognizes. | ||
| result[i] = res; | ||
| } else { | ||
| // if we get here, it means that the category was found in the cache, | ||
| // but is not recognized by this TR instance. Therefore, there's no | ||
| // need to continue search for the path on disk, because we won't find | ||
| // it there too. | ||
| result[i] = TaxonomyReader.INVALID_ORDINAL; | ||
| } | ||
| } else { | ||
| indexesMissingFromCache = | ||
| ArrayUtil.grow(indexesMissingFromCache, numberOfMissingFromCache + 1); | ||
| indexesMissingFromCache[numberOfMissingFromCache++] = i; | ||
| } | ||
| } | ||
| // all ordinals found in cache | ||
| if (indexesMissingFromCache.length == 0) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. With AI pocking around the code, I found that this condition never passes. |
||
| return result; | ||
| } | ||
|
|
||
| // If we're still here, we have at least one cache miss. We need to fetch the | ||
| // value from disk, and then also put results in the cache | ||
|
|
||
| // Create array of missing terms, and sort them so that later we scan terms dictionary | ||
| // forward-only. | ||
| // Note: similar functionality exists within BytesRefHash and BytesRefArray, but they don't | ||
| // reuse BytesRefs and assign their own ords. It is cheaper to have custom implementation here. | ||
| BytesRef[] termsToGet = new BytesRef[numberOfMissingFromCache]; | ||
| for (int i = 0; i < termsToGet.length; i++) { | ||
| cp = categoryPaths[indexesMissingFromCache[i]]; | ||
| termsToGet[i] = new BytesRef(FacetsConfig.pathToString(cp.components, cp.length)); | ||
| } | ||
| // sort both terms and their indexes in the input parameter | ||
| int[] finalMissingFromCache = indexesMissingFromCache; | ||
|
|
||
| new StringSorter(BytesRefComparator.NATURAL) { | ||
|
|
||
| @Override | ||
| protected void swap(int i, int j) { | ||
| int tmp = finalMissingFromCache[i]; | ||
| finalMissingFromCache[i] = finalMissingFromCache[j]; | ||
| finalMissingFromCache[j] = tmp; | ||
| BytesRef tmpBytes = termsToGet[i]; | ||
| termsToGet[i] = termsToGet[j]; | ||
| termsToGet[j] = tmpBytes; | ||
| } | ||
|
|
||
| @Override | ||
| protected void get(BytesRefBuilder builder, BytesRef result, int i) { | ||
| BytesRef ref = termsToGet[i]; | ||
| result.offset = ref.offset; | ||
| result.length = ref.length; | ||
| result.bytes = ref.bytes; | ||
| } | ||
| }.sort(0, numberOfMissingFromCache); | ||
|
|
||
| TermsEnum te = MultiTerms.getTerms(indexReader, Consts.FULL).iterator(); | ||
| PostingsEnum postings = null; | ||
| int ord; | ||
| int resIndex; | ||
| for (int i = 0; i < numberOfMissingFromCache; i++) { | ||
| resIndex = indexesMissingFromCache[i]; | ||
| if (te.seekExact(termsToGet[i])) { | ||
| postings = te.postings(postings, 0); | ||
| if (postings != null && postings.nextDoc() != DocIdSetIterator.NO_MORE_DOCS) { | ||
| ord = postings.docID(); | ||
| result[resIndex] = ord; | ||
| } else { | ||
| result[resIndex] = INVALID_ORDINAL; | ||
| } | ||
| } else { | ||
| result[resIndex] = INVALID_ORDINAL; | ||
| } | ||
| } | ||
| // populate cache | ||
| synchronized (ordinalCache) { | ||
| for (int i = 0; i < numberOfMissingFromCache; i++) { | ||
| resIndex = indexesMissingFromCache[i]; | ||
| ord = result[resIndex]; | ||
| if (ord != INVALID_ORDINAL) { | ||
| ordinalCache.put(categoryPaths[resIndex], ord); | ||
| } | ||
| } | ||
| } | ||
| return result; | ||
| } | ||
|
|
||
| @Override | ||
| public FacetLabel getPath(int ordinal) throws IOException { | ||
| ensureOpen(); | ||
|
|
||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I love the above javadoc, but maybe additionally add comment here that this is the simple & slow base class / default implementation, and note that
DirectoryTaxonomyReaderhas an optimized and more complex version?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will do!