Browse Source

Bugfix: Disable eager loading BitSetFilterCache on Stateless Indexing Nodes (#105791)

The BitSetFilterCache is used for search traffic, which is not served on stateless indexing nodes.  Thus, we can disable it and save memory.
John Verwolf 1 year ago
parent
commit
d72665a207

+ 5 - 0
docs/changelog/105791.yaml

@@ -0,0 +1,5 @@
+pr: 105791
+summary: "Bugfix: Disable eager loading `BitSetFilterCache` on Indexing Nodes"
+area: Search
+type: bug
+issues: []

+ 17 - 1
server/src/main/java/org/elasticsearch/index/cache/bitset/BitsetFilterCache.java

@@ -24,6 +24,8 @@ import org.apache.lucene.util.Accountable;
 import org.apache.lucene.util.BitDocIdSet;
 import org.apache.lucene.util.BitSet;
 import org.elasticsearch.ExceptionsHelper;
+import org.elasticsearch.cluster.node.DiscoveryNode;
+import org.elasticsearch.cluster.node.DiscoveryNodeRole;
 import org.elasticsearch.common.cache.Cache;
 import org.elasticsearch.common.cache.CacheBuilder;
 import org.elasticsearch.common.cache.RemovalListener;
@@ -55,6 +57,8 @@ import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executor;
 
+import static org.elasticsearch.index.IndexSettings.INDEX_FAST_REFRESH_SETTING;
+
 /**
  * This is a cache for {@link BitDocIdSet} based filters and is unbounded by size or time.
  * <p>
@@ -92,10 +96,22 @@ public final class BitsetFilterCache
             throw new IllegalArgumentException("listener must not be null");
         }
         this.index = indexSettings.getIndex();
-        this.loadRandomAccessFiltersEagerly = indexSettings.getValue(INDEX_LOAD_RANDOM_ACCESS_FILTERS_EAGERLY_SETTING);
+        this.loadRandomAccessFiltersEagerly = shouldLoadRandomAccessFiltersEagerly(indexSettings);
         this.listener = listener;
     }
 
+    static boolean shouldLoadRandomAccessFiltersEagerly(IndexSettings settings) {
+        boolean loadFiltersEagerlySetting = settings.getValue(INDEX_LOAD_RANDOM_ACCESS_FILTERS_EAGERLY_SETTING);
+        boolean isStateless = DiscoveryNode.isStateless(settings.getNodeSettings());
+        if (isStateless) {
+            return DiscoveryNode.hasRole(settings.getNodeSettings(), DiscoveryNodeRole.INDEX_ROLE)
+                && loadFiltersEagerlySetting
+                && INDEX_FAST_REFRESH_SETTING.get(settings.getSettings());
+        } else {
+            return loadFiltersEagerlySetting;
+        }
+    }
+
     public static BitSet bitsetFromQuery(Query query, LeafReaderContext context) throws IOException {
         final IndexReaderContext topLevelContext = ReaderUtil.getTopLevelContext(context);
         final IndexSearcher searcher = new IndexSearcher(topLevelContext);

+ 57 - 0
server/src/test/java/org/elasticsearch/index/cache/bitset/BitSetFilterCacheTests.java

@@ -28,19 +28,27 @@ import org.apache.lucene.store.ByteBuffersDirectory;
 import org.apache.lucene.store.Directory;
 import org.apache.lucene.util.Accountable;
 import org.apache.lucene.util.BitSet;
+import org.elasticsearch.cluster.metadata.IndexMetadata;
+import org.elasticsearch.cluster.node.DiscoveryNodeRole;
 import org.elasticsearch.common.lucene.index.ElasticsearchDirectoryReader;
 import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.core.IOUtils;
+import org.elasticsearch.index.Index;
 import org.elasticsearch.index.IndexSettings;
 import org.elasticsearch.index.shard.ShardId;
 import org.elasticsearch.lucene.util.MatchAllBitSet;
+import org.elasticsearch.node.NodeRoleSettings;
 import org.elasticsearch.test.ESTestCase;
 import org.elasticsearch.test.IndexSettingsModule;
 
 import java.io.IOException;
+import java.util.List;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicLong;
 
+import static org.elasticsearch.cluster.node.DiscoveryNode.STATELESS_ENABLED_SETTING_NAME;
+import static org.elasticsearch.index.IndexSettings.INDEX_FAST_REFRESH_SETTING;
+import static org.elasticsearch.index.cache.bitset.BitsetFilterCache.INDEX_LOAD_RANDOM_ACCESS_FILTERS_EAGERLY_SETTING;
 import static org.hamcrest.Matchers.equalTo;
 import static org.hamcrest.Matchers.greaterThan;
 import static org.hamcrest.Matchers.hasSize;
@@ -259,4 +267,53 @@ public class BitSetFilterCacheTests extends ESTestCase {
         }
     }
 
+    public void testShouldLoadRandomAccessFiltersEagerly() {
+        var values = List.of(true, false);
+        for (var hasIndexRole : values) {
+            for (var indexFastRefresh : values) {
+                for (var loadFiltersEagerly : values) {
+                    for (var isStateless : values) {
+                        if (isStateless) {
+                            assertEquals(
+                                loadFiltersEagerly && indexFastRefresh && hasIndexRole,
+                                BitsetFilterCache.shouldLoadRandomAccessFiltersEagerly(
+                                    bitsetFilterCacheSettings(isStateless, hasIndexRole, loadFiltersEagerly, indexFastRefresh)
+                                )
+                            );
+                        } else {
+                            assertEquals(
+                                loadFiltersEagerly,
+                                BitsetFilterCache.shouldLoadRandomAccessFiltersEagerly(
+                                    bitsetFilterCacheSettings(isStateless, hasIndexRole, loadFiltersEagerly, indexFastRefresh)
+                                )
+                            );
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    private IndexSettings bitsetFilterCacheSettings(
+        boolean isStateless,
+        boolean hasIndexRole,
+        boolean loadFiltersEagerly,
+        boolean indexFastRefresh
+    ) {
+        var indexSettingsBuilder = Settings.builder().put(INDEX_LOAD_RANDOM_ACCESS_FILTERS_EAGERLY_SETTING.getKey(), loadFiltersEagerly);
+        if (isStateless) indexSettingsBuilder.put(INDEX_FAST_REFRESH_SETTING.getKey(), indexFastRefresh);
+
+        var nodeSettingsBuilder = Settings.builder()
+            .putList(
+                NodeRoleSettings.NODE_ROLES_SETTING.getKey(),
+                hasIndexRole ? DiscoveryNodeRole.INDEX_ROLE.roleName() : DiscoveryNodeRole.SEARCH_ROLE.roleName()
+            )
+            .put(STATELESS_ENABLED_SETTING_NAME, isStateless);
+
+        return IndexSettingsModule.newIndexSettings(
+            new Index("index", IndexMetadata.INDEX_UUID_NA_VALUE),
+            indexSettingsBuilder.build(),
+            nodeSettingsBuilder.build()
+        );
+    }
 }