You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by jp...@apache.org on 2025/03/30 08:49:20 UTC

(lucene) branch branch_10x updated: Allow skip cache factor to be updated dynamically (#14412)

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

jpountz pushed a commit to branch branch_10x
in repository https://gitbox.apache.org/repos/asf/lucene.git


The following commit(s) were added to refs/heads/branch_10x by this push:
     new f277b5e9198 Allow skip cache factor to be updated dynamically (#14412)
f277b5e9198 is described below

commit f277b5e919861366bc6ac0e44cbc3e6b6c8876fb
Author: Sagar <99...@users.noreply.github.com>
AuthorDate: Sun Mar 30 01:32:13 2025 -0700

    Allow skip cache factor to be updated dynamically (#14412)
---
 lucene/CHANGES.txt                                 |  2 +
 .../org/apache/lucene/search/LRUQueryCache.java    | 24 +++++++--
 .../apache/lucene/search/TestLRUQueryCache.java    | 63 ++++++++++++++++++++++
 3 files changed, 86 insertions(+), 3 deletions(-)

diff --git a/lucene/CHANGES.txt b/lucene/CHANGES.txt
index 2f48be27de2..daac7329b9e 100644
--- a/lucene/CHANGES.txt
+++ b/lucene/CHANGES.txt
@@ -63,6 +63,8 @@ New Features
 * GITHUB#13470: Added `TopDocs#rrf` to combine multiple TopDocs instances using
   reciprocal rank fusion. (Haren Lin, Adrien Grand)
 
+* GITHUB#14412: Allow skip cache factor to be updated dynamically. (Sagar Upadhyaya)
+
 Improvements
 ---------------------
 
diff --git a/lucene/core/src/java/org/apache/lucene/search/LRUQueryCache.java b/lucene/core/src/java/org/apache/lucene/search/LRUQueryCache.java
index 0976f60220d..088a47bf0a9 100644
--- a/lucene/core/src/java/org/apache/lucene/search/LRUQueryCache.java
+++ b/lucene/core/src/java/org/apache/lucene/search/LRUQueryCache.java
@@ -99,7 +99,7 @@ public class LRUQueryCache implements QueryCache, Accountable {
   private final Map<IndexReader.CacheKey, LeafCache> cache;
   private final ReentrantReadWriteLock.ReadLock readLock;
   private final ReentrantReadWriteLock.WriteLock writeLock;
-  private final float skipCacheFactor;
+  private volatile float skipCacheFactor;
   private final LongAdder hitCount;
   private final LongAdder missCount;
 
@@ -142,6 +142,25 @@ public class LRUQueryCache implements QueryCache, Accountable {
     missCount = new LongAdder();
   }
 
+  /**
+   * Get the skip cache factor
+   *
+   * @return #setSkipCacheFactor
+   */
+  public float getSkipCacheFactor() {
+    return skipCacheFactor;
+  }
+
+  /**
+   * This setter enables the skipCacheFactor to be updated dynamically.
+   *
+   * @param skipCacheFactor clauses whose cost is {@code skipCacheFactor} times more than the cost
+   *     of the top-level query will not be cached in order to not slow down queries too much.
+   */
+  public void setSkipCacheFactor(float skipCacheFactor) {
+    this.skipCacheFactor = skipCacheFactor;
+  }
+
   /**
    * Create a new instance that will cache at most <code>maxSize</code> queries with at most <code>
    * maxRamBytesUsed</code> bytes of memory. Queries will only be cached on leaves that have more
@@ -706,13 +725,12 @@ public class LRUQueryCache implements QueryCache, Accountable {
     private boolean cacheEntryHasReasonableWorstCaseSize(int maxDoc) {
       // The worst-case (dense) is a bit set which needs one bit per document
       final long worstCaseRamUsage = maxDoc / 8;
-      final long totalRamAvailable = maxRamBytesUsed;
       // Imagine the worst-case that a cache entry is large than the size of
       // the cache: not only will this entry be trashed immediately but it
       // will also evict all current entries from the cache. For this reason
       // we only cache on an IndexReader if we have available room for
       // 5 different filters on this reader to avoid excessive trashing
-      return worstCaseRamUsage * 5 < totalRamAvailable;
+      return worstCaseRamUsage * 5 < maxRamBytesUsed;
     }
 
     /** Check whether this segment is eligible for caching, regardless of the query. */
diff --git a/lucene/core/src/test/org/apache/lucene/search/TestLRUQueryCache.java b/lucene/core/src/test/org/apache/lucene/search/TestLRUQueryCache.java
index 6f8b4fdb8fb..b81f05d3f92 100644
--- a/lucene/core/src/test/org/apache/lucene/search/TestLRUQueryCache.java
+++ b/lucene/core/src/test/org/apache/lucene/search/TestLRUQueryCache.java
@@ -2134,4 +2134,67 @@ public class TestLRUQueryCache extends LuceneTestCase {
     w.close();
     dir.close();
   }
+
+  public void testSkipCacheFactorWithDynamicValues() throws IOException {
+    Directory dir = newDirectory();
+    final RandomIndexWriter w = new RandomIndexWriter(random(), dir);
+    Document doc1 = new Document();
+    doc1.add(new StringField("name", "tom", Store.YES));
+    doc1.add(new StringField("hobby", "movie", Store.YES));
+    Document doc2 = new Document();
+    doc2.add(new StringField("name", "alice", Store.YES));
+    doc2.add(new StringField("hobby", "book", Store.YES));
+    Document doc3 = new Document();
+    doc3.add(new StringField("name", "alice", Store.YES));
+    doc3.add(new StringField("hobby", "movie", Store.YES));
+    w.addDocuments(Arrays.asList(doc1, doc2, doc3));
+    final IndexReader reader = w.getReader();
+    final IndexSearcher searcher = newSearcher(reader);
+    final QueryCachingPolicy policy =
+        new QueryCachingPolicy() {
+
+          @Override
+          public boolean shouldCache(Query query) throws IOException {
+            return query.getClass() != TermQuery.class;
+          }
+
+          @Override
+          public void onUse(Query query) {
+            // no-op
+          }
+        };
+    searcher.setQueryCachingPolicy(policy);
+    w.close();
+
+    // lead cost is 2, cost of subQuery1 is 3, cost of subQuery2 is 2
+    BooleanQuery.Builder inner = new BooleanQuery.Builder();
+    TermQuery innerSubQuery1 = new TermQuery(new Term("hobby", "book"));
+    TermQuery innerSubQuery2 = new TermQuery(new Term("hobby", "movie"));
+    BooleanQuery subQuery1 =
+        inner.add(innerSubQuery1, Occur.SHOULD).add(innerSubQuery2, Occur.SHOULD).build();
+
+    BooleanQuery.Builder bq = new BooleanQuery.Builder();
+    TermQuery subQuery2 = new TermQuery(new Term("name", "alice"));
+    BooleanQuery query =
+        bq.add(new ConstantScoreQuery(subQuery1), Occur.FILTER)
+            .add(subQuery2, Occur.FILTER)
+            .build();
+    Set<Query> cacheSet = new HashSet<>();
+
+    // Define skip cache factor to be 1, so that query is not cached.
+    final LRUQueryCache cache = new LRUQueryCache(1000000, 10000000, ctx -> true, 1.0f);
+    searcher.setQueryCache(cache);
+    searcher.search(query, 1);
+    assertEquals(cacheSet, new HashSet<>(cache.cachedQueries()));
+    assertEquals(1.0, cache.getSkipCacheFactor(), 0);
+
+    // Now change the skipCacheFactor to 3.0f, now same query should get cached.
+    cache.setSkipCacheFactor(3.0f);
+    searcher.search(query, 1);
+    assertEquals(new HashSet<>(List.of(subQuery1)), new HashSet<>(cache.cachedQueries()));
+    assertEquals(3.0, cache.getSkipCacheFactor(), 0);
+
+    reader.close();
+    dir.close();
+  }
 }