All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.elasticsearch.index.cache.bitset.BitsetFilterCache Maven / Gradle / Ivy

/*
 * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
 * or more contributor license agreements. Licensed under the Elastic License
 * 2.0 and the Server Side Public License, v 1; you may not use this file except
 * in compliance with, at your election, the Elastic License 2.0 or the Server
 * Side Public License, v 1.
 */

package org.elasticsearch.index.cache.bitset;

import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexReaderContext;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.index.ReaderUtil;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreMode;
import org.apache.lucene.search.Scorer;
import org.apache.lucene.search.Weight;
import org.apache.lucene.search.join.BitSetProducer;
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.common.cache.Cache;
import org.elasticsearch.common.cache.CacheBuilder;
import org.elasticsearch.common.cache.RemovalListener;
import org.elasticsearch.common.cache.RemovalNotification;
import org.elasticsearch.common.lucene.index.ElasticsearchDirectoryReader;
import org.elasticsearch.common.lucene.search.Queries;
import org.elasticsearch.common.settings.Setting;
import org.elasticsearch.common.settings.Setting.Property;
import org.elasticsearch.core.TimeValue;
import org.elasticsearch.index.AbstractIndexComponent;
import org.elasticsearch.index.IndexSettings;
import org.elasticsearch.index.IndexWarmer;
import org.elasticsearch.index.IndexWarmer.TerminationHandle;
import org.elasticsearch.index.mapper.MapperService;
import org.elasticsearch.index.mapper.MappingLookup;
import org.elasticsearch.index.mapper.NestedLookup;
import org.elasticsearch.index.shard.IndexShard;
import org.elasticsearch.index.shard.ShardId;
import org.elasticsearch.index.shard.ShardUtils;
import org.elasticsearch.threadpool.ThreadPool;

import java.io.Closeable;
import java.io.IOException;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;

/**
 * This is a cache for {@link BitDocIdSet} based filters and is unbounded by size or time.
 * 

* Use this cache with care, only components that require that a filter is to be materialized as a {@link BitDocIdSet} * and require that it should always be around should use this cache, otherwise the * {@link org.elasticsearch.index.cache.query.QueryCache} should be used instead. */ public final class BitsetFilterCache extends AbstractIndexComponent implements IndexReader.ClosedListener, RemovalListener>, Closeable { public static final Setting INDEX_LOAD_RANDOM_ACCESS_FILTERS_EAGERLY_SETTING = Setting.boolSetting( "index.load_fixed_bitset_filters_eagerly", true, Property.IndexScope ); private final boolean loadRandomAccessFiltersEagerly; /** * Lazy initialized by {@link #buildFiltersCache()} to save heap for indices not using this cache as even empty {@link Cache} are * quite heavy-weight. */ private volatile Cache> loadedFilters; private final Listener listener; public BitsetFilterCache(IndexSettings indexSettings, Listener listener) { super(indexSettings); if (listener == null) { throw new IllegalArgumentException("listener must not be null"); } this.loadRandomAccessFiltersEagerly = this.indexSettings.getValue(INDEX_LOAD_RANDOM_ACCESS_FILTERS_EAGERLY_SETTING); this.listener = listener; } public static BitSet bitsetFromQuery(Query query, LeafReaderContext context) throws IOException { final IndexReaderContext topLevelContext = ReaderUtil.getTopLevelContext(context); final IndexSearcher searcher = new IndexSearcher(topLevelContext); searcher.setQueryCache(null); final Weight weight = searcher.createWeight(searcher.rewrite(query), ScoreMode.COMPLETE_NO_SCORES, 1f); Scorer s = weight.scorer(context); if (s == null) { return null; } else { return BitSet.of(s.iterator(), context.reader().maxDoc()); } } public IndexWarmer.Listener createListener(ThreadPool threadPool) { return new BitSetProducerWarmer(threadPool); } public BitSetProducer getBitSetProducer(Query query) { return new QueryWrapperBitSetProducer(query); } @Override public void onClose(IndexReader.CacheKey ownerCoreCacheKey) { var filters = loadedFilters; if (filters != null) { filters.invalidate(ownerCoreCacheKey); } } @Override public void close() { clear("close"); } public void clear(String reason) { logger.debug("clearing all bitsets because [{}]", reason); var filters = loadedFilters; if (filters != null) { filters.invalidateAll(); } } private BitSet getAndLoadIfNotPresent(final Query query, final LeafReaderContext context) throws ExecutionException { final IndexReader.CacheHelper cacheHelper = context.reader().getCoreCacheHelper(); if (cacheHelper == null) { throw new IllegalArgumentException("Reader " + context.reader() + " does not support caching"); } final IndexReader.CacheKey coreCacheReader = cacheHelper.getKey(); final ShardId shardId = ShardUtils.extractShardId(context.reader()); if (shardId == null) { throw new IllegalStateException( "Null shardId. If you got here from a test, you need to wrap the directory reader. " + "see for example AggregatorTestCase#wrapInMockESDirectoryReader. If you got here in production, please file a bug." ); } if (indexSettings.getIndex().equals(shardId.getIndex()) == false) { // insanity throw new IllegalStateException( "Trying to load bit set for index " + shardId.getIndex() + " with cache of index " + indexSettings.getIndex() ); } var filters = loadedFilters; if (filters == null) { filters = buildFiltersCache(); } Cache filterToFbs = filters.computeIfAbsent(coreCacheReader, key -> { cacheHelper.addClosedListener(BitsetFilterCache.this); return CacheBuilder.builder().build(); }); return filterToFbs.computeIfAbsent(query, key -> { final BitSet bitSet = bitsetFromQuery(query, context); Value value = new Value(bitSet, shardId); listener.onCache(shardId, value.bitset); return value; }).bitset; } private synchronized Cache> buildFiltersCache() { var existing = loadedFilters; if (existing != null) { return existing; } existing = CacheBuilder.>builder().removalListener(this).build(); loadedFilters = existing; return existing; } @Override public void onRemoval(RemovalNotification> notification) { if (notification.getKey() == null) { return; } Cache valueCache = notification.getValue(); if (valueCache == null) { return; } for (Value value : valueCache.values()) { listener.onRemoval(value.shardId, value.bitset); // if null then this means the shard has already been removed and the stats are 0 anyway for the shard this key belongs to } } public static final class Value { final BitSet bitset; final ShardId shardId; public Value(BitSet bitset, ShardId shardId) { this.bitset = bitset; this.shardId = shardId; } } final class QueryWrapperBitSetProducer implements BitSetProducer { final Query query; QueryWrapperBitSetProducer(Query query) { this.query = Objects.requireNonNull(query); } @Override public BitSet getBitSet(LeafReaderContext context) { try { return getAndLoadIfNotPresent(query, context); } catch (ExecutionException e) { throw ExceptionsHelper.convertToElastic(e); } } @Override public String toString() { return "random_access(" + query + ")"; } @Override public boolean equals(Object o) { if ((o instanceof QueryWrapperBitSetProducer) == false) return false; return this.query.equals(((QueryWrapperBitSetProducer) o).query); } @Override public int hashCode() { return 31 * getClass().hashCode() + query.hashCode(); } } final class BitSetProducerWarmer implements IndexWarmer.Listener { private final Executor executor; BitSetProducerWarmer(ThreadPool threadPool) { this.executor = threadPool.executor(ThreadPool.Names.WARMER); } @Override public IndexWarmer.TerminationHandle warmReader(final IndexShard indexShard, final ElasticsearchDirectoryReader reader) { if (indexSettings.getIndex().equals(indexShard.indexSettings().getIndex()) == false) { // this is from a different index return TerminationHandle.NO_WAIT; } if (loadRandomAccessFiltersEagerly == false) { return TerminationHandle.NO_WAIT; } final Set warmUp = new HashSet<>(); final MapperService mapperService = indexShard.mapperService(); MappingLookup lookup = mapperService.mappingLookup(); NestedLookup nestedLookup = lookup.nestedLookup(); if (nestedLookup != NestedLookup.EMPTY) { warmUp.add(Queries.newNonNestedFilter()); warmUp.addAll(nestedLookup.getNestedParentFilters().values()); } final CountDownLatch latch = new CountDownLatch(reader.leaves().size() * warmUp.size()); for (final LeafReaderContext ctx : reader.leaves()) { for (final Query filterToWarm : warmUp) { executor.execute(() -> { try { final long start = System.nanoTime(); getAndLoadIfNotPresent(filterToWarm, ctx); if (indexShard.warmerService().logger().isTraceEnabled()) { indexShard.warmerService() .logger() .trace( "warmed bitset for [{}], took [{}]", filterToWarm, TimeValue.timeValueNanos(System.nanoTime() - start) ); } } catch (Exception e) { indexShard.warmerService().logger().warn(() -> "failed to load " + "bitset for [" + filterToWarm + "]", e); } finally { latch.countDown(); } }); } } return () -> latch.await(); } } // for testing Cache> getLoadedFilters() { return loadedFilters; } /** * A listener interface that is executed for each onCache / onRemoval event */ public interface Listener { /** * Called for each cached bitset on the cache event. * @param shardId the shard id the bitset was cached for. This can be null * @param accountable the bitsets ram representation */ void onCache(ShardId shardId, Accountable accountable); /** * Called for each cached bitset on the removal event. * @param shardId the shard id the bitset was cached for. This can be null * @param accountable the bitsets ram representation */ void onRemoval(ShardId shardId, Accountable accountable); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy