You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@solr.apache.org by ma...@apache.org on 2022/03/29 18:16:35 UTC
[solr] branch main updated: SOLR-16002: Avoid redundant `FilterQuery` caching, e.g. via `filter($query)` syntax (#624)
This is an automated email from the ASF dual-hosted git repository.
magibney pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/solr.git
The following commit(s) were added to refs/heads/main by this push:
new 534696c SOLR-16002: Avoid redundant `FilterQuery` caching, e.g. via `filter($query)` syntax (#624)
534696c is described below
commit 534696cf0e83c8671a1d3ef31e2ba2e6a93d7ec0
Author: Michael Gibney <mi...@michaelgibney.net>
AuthorDate: Tue Mar 29 14:16:30 2022 -0400
SOLR-16002: Avoid redundant `FilterQuery` caching, e.g. via `filter($query)` syntax (#624)
---
solr/CHANGES.txt | 2 +
.../java/org/apache/solr/query/FilterQuery.java | 28 +++
.../solr/search/TestFiltersQueryCaching.java | 197 +++++++++++++++++++++
3 files changed, 227 insertions(+)
diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index aae1da4..22c0497 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -39,6 +39,8 @@ Improvements
* SOLR-15652: Add Slack channel for community support to Admin UI footer, update links. (Eric Pugh)
+* SOLR-16002: Avoid redundant `FilterQuery` caching, e.g. via `filter($query)` syntax. (Michael Gibney, David Smiley)
+
Optimizations
---------------------
(No changes)
diff --git a/solr/core/src/java/org/apache/solr/query/FilterQuery.java b/solr/core/src/java/org/apache/solr/query/FilterQuery.java
index 28cae88..ce49583 100644
--- a/solr/core/src/java/org/apache/solr/query/FilterQuery.java
+++ b/solr/core/src/java/org/apache/solr/query/FilterQuery.java
@@ -46,6 +46,34 @@ public class FilterQuery extends ExtendedQueryBase {
this.q = q;
}
+ @Override
+ public final void setCache(boolean cache) {
+ /*
+ NOTE: at the implementation level, we silently ignore explicit `setCache` directives. But at a higher level
+ (i.e., from the client's perspective) by ignoring at the implementation level, we in fact respect the semantics
+ both of explicit `{!cache=false}filter(q)` and `{!cache=true}filter(q)`. Since the purpose of FilterQuery
+ is to handle caching _internal_ to the query, external `cache=false` _should_ have no effect. Slightly
+ less intuitive: the `cache=true` case should be interpreted as directing "ensure that we consult the
+ filterCache for this query" -- and indeed because caching is handled internally, the essence of the
+ top-level `cache=true` directive is most appropriately respected by having `getCache()` continue to return
+ `false` at the level of the FilterQuery _per se_.
+ */
+ }
+
+ @Override
+ public final boolean getCache() {
+ return false;
+ /*
+ Paradoxically, this _is_ what we want. The FilterQuery wrapper is designed to ensure that its
+ inner query always consults the filterCache, regardless of the context in which it's called.
+ FilterQuery internally calls SolrIndexSearcher.getDocSet with its _wrapped_ query, so the caching
+ happens at that level, and we want _not_ to consult the filterCache with the FilterQuery wrapper
+ per se. Allowing `getCache()=true` here can result in double-entry in the filterCache (e.g. when
+ using `fq=filter({!term f=field v=term})`, or caching separate clauses of a BooleanQuery via
+ `fq={!bool should='filter($q1)' should='filter($q2)'}`).
+ */
+ }
+
public Query getQuery() {
return q;
}
diff --git a/solr/core/src/test/org/apache/solr/search/TestFiltersQueryCaching.java b/solr/core/src/test/org/apache/solr/search/TestFiltersQueryCaching.java
new file mode 100644
index 0000000..cf797fe
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/search/TestFiltersQueryCaching.java
@@ -0,0 +1,197 @@
+/*
+ * 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.solr.search;
+
+import java.util.Map;
+import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.core.SolrCore;
+import org.apache.solr.metrics.MetricsMap;
+import org.apache.solr.metrics.SolrMetricManager;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/** Verify caching impacts of FiltersQParser and FilterQuery */
+public class TestFiltersQueryCaching extends SolrTestCaseJ4 {
+
+ private static final int NUM_DOCS = 20;
+
+ @BeforeClass
+ public static void beforeClass() throws Exception {
+ initCore("solrconfig.xml", "schema_latest.xml");
+ createIndex();
+ }
+
+ public static void createIndex() {
+ for (int i = 0; i < NUM_DOCS; i++) {
+ assertU(adoc("id", Integer.toString(i), "field_s", "d" + i));
+ if (random().nextInt(NUM_DOCS) == 0) {
+ assertU(commit()); // sometimes make multiple segments
+ }
+ }
+ assertU(commit());
+ }
+
+ private static Map<String, Object> lookupFilterCacheMetrics(SolrCore core) {
+ return ((MetricsMap)
+ ((SolrMetricManager.GaugeWrapper<?>)
+ core.getCoreMetricManager()
+ .getRegistry()
+ .getMetrics()
+ .get("CACHE.searcher.filterCache"))
+ .getGauge())
+ .getValue();
+ }
+
+ private static long lookupFilterCacheInserts(SolrCore core) {
+ return (long)
+ ((MetricsMap)
+ ((SolrMetricManager.GaugeWrapper<?>)
+ core.getCoreMetricManager()
+ .getRegistry()
+ .getMetrics()
+ .get("CACHE.searcher.filterCache"))
+ .getGauge())
+ .getValue()
+ .get("inserts");
+ }
+
+ @Test
+ public void testRecursiveFilter() throws Exception {
+ final String termQuery = "{!term f=field_s v='d0'}";
+ final String filterTermQuery = "filter(" + termQuery + ")";
+ final int expectNumFound = 1;
+ final String expectNumFoundXPath = "/response/numFound==" + expectNumFound;
+
+ h.reload();
+ assertJQ(req("q", termQuery, "indent", "true"), expectNumFoundXPath);
+ assertEquals(0, lookupFilterCacheInserts(h.getCore()));
+
+ h.reload();
+ assertJQ(req("q", filterTermQuery, "indent", "true"), expectNumFoundXPath);
+ assertEquals(1, lookupFilterCacheInserts(h.getCore()));
+
+ h.reload();
+ assertJQ(
+ req("q", "*:*", "indent", "true", "fq", "{!cache=false}field_s:d0"), expectNumFoundXPath);
+ assertEquals(0, lookupFilterCacheInserts(h.getCore()));
+
+ h.reload();
+ assertJQ(
+ req("q", "*:*", "indent", "true", "fq", "{!cache=true}field_s:d0"), expectNumFoundXPath);
+ assertEquals(1, lookupFilterCacheInserts(h.getCore()));
+
+ h.reload();
+ assertJQ(
+ req("q", "*:*", "indent", "true", "fq", "{!cache=false}filter(field_s:d0)"),
+ expectNumFoundXPath);
+ assertEquals(1, lookupFilterCacheInserts(h.getCore()));
+
+ h.reload();
+ assertJQ(
+ req("q", "*:*", "indent", "true", "fq", "{!cache=true}filter(field_s:d0)"),
+ expectNumFoundXPath);
+ assertEquals(1, lookupFilterCacheInserts(h.getCore()));
+
+ h.reload();
+ assertJQ(req("q", "*:*", "indent", "true", "fq", termQuery), expectNumFoundXPath);
+ assertEquals(1, lookupFilterCacheInserts(h.getCore()));
+
+ h.reload();
+ assertJQ(req("q", "*:*", "indent", "true", "fq", filterTermQuery), expectNumFoundXPath);
+ assertEquals(1, lookupFilterCacheInserts(h.getCore()));
+
+ h.reload();
+ SolrCore core = h.getCore();
+ Map<String, Object> filterCacheMetrics;
+ final String termQuery2 = "{!term f=field_s v='d1'}";
+ final String filterTermQuery2 = "filter(" + termQuery2 + ")";
+ assertJQ(
+ req(
+ "q",
+ "*:*",
+ "indent",
+ "true",
+ "fq",
+ "{!bool cache=false should=$ftq should=$ftq2}",
+ "ftq",
+ filterTermQuery,
+ "ftq2",
+ filterTermQuery2),
+ "/response/numFound==2");
+ assertEquals(2, lookupFilterCacheInserts(core));
+ JQ(
+ req(
+ "q",
+ "*:*",
+ "indent",
+ "true",
+ "fq",
+ random().nextBoolean() ? termQuery : filterTermQuery));
+ filterCacheMetrics = lookupFilterCacheMetrics(core);
+ assertEquals(2, (long) filterCacheMetrics.get("inserts")); // unchanged
+ assertEquals(1, (long) filterCacheMetrics.get("hits"));
+ JQ(
+ req(
+ "q",
+ "*:*",
+ "indent",
+ "true",
+ "fq",
+ random().nextBoolean() ? termQuery2 : filterTermQuery2));
+ filterCacheMetrics = lookupFilterCacheMetrics(core);
+ assertEquals(2, (long) filterCacheMetrics.get("inserts")); // unchanged
+ assertEquals(2, (long) filterCacheMetrics.get("hits"));
+ JQ(
+ req(
+ "q",
+ "*:*",
+ "indent",
+ "true",
+ "fq",
+ "{!bool cache=false should=$ftq should=$ftq2}",
+ "ftq",
+ filterTermQuery,
+ "ftq2",
+ filterTermQuery2,
+ "cursorMark",
+ "*",
+ "sort",
+ "id asc"));
+ filterCacheMetrics = lookupFilterCacheMetrics(core);
+ assertEquals(2, (long) filterCacheMetrics.get("inserts")); // unchanged
+ assertEquals(4, (long) filterCacheMetrics.get("hits"));
+ JQ(
+ req(
+ "q",
+ "*:*",
+ "indent",
+ "true",
+ "fq",
+ "{!bool should=$ftq should=$ftq2}",
+ "ftq",
+ filterTermQuery,
+ "ftq2",
+ filterTermQuery2,
+ "cursorMark",
+ "*",
+ "sort",
+ "id asc"));
+ filterCacheMetrics = lookupFilterCacheMetrics(core);
+ assertEquals(3, (long) filterCacheMetrics.get("inserts")); // added top-level
+ assertEquals(6, (long) filterCacheMetrics.get("hits"));
+ }
+}