LUCENE-7237: LRUQueryCache now prefers returning an uncached Scorer than waiting on a lock.

This commit is contained in:
Adrien Grand 2016-04-22 14:09:44 +02:00
parent 927a44881c
commit bf232d7635
2 changed files with 154 additions and 83 deletions

View File

@ -76,6 +76,9 @@ Optimizations
* LUCENE-7238: Explicitly disable the query cache in MemoryIndex#createSearcher. * LUCENE-7238: Explicitly disable the query cache in MemoryIndex#createSearcher.
(Adrien Grand) (Adrien Grand)
* LUCENE-7237: LRUQueryCache now prefers returning an uncached Scorer than
waiting on a lock. (Adrien Grand)
Bug Fixes Bug Fixes
* LUCENE-7127: Fix corner case bugs in GeoPointDistanceQuery. (Robert Muir) * LUCENE-7127: Fix corner case bugs in GeoPointDistanceQuery. (Robert Muir)

View File

@ -29,6 +29,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Predicate; import java.util.function.Predicate;
import org.apache.lucene.index.LeafReader.CoreClosedListener; import org.apache.lucene.index.LeafReader.CoreClosedListener;
@ -112,6 +113,7 @@ public class LRUQueryCache implements QueryCache, Accountable {
// mostRecentlyUsedQueries. This is why write operations are performed under a lock // mostRecentlyUsedQueries. This is why write operations are performed under a lock
private final Set<Query> mostRecentlyUsedQueries; private final Set<Query> mostRecentlyUsedQueries;
private final Map<Object, LeafCache> cache; private final Map<Object, LeafCache> cache;
private final ReentrantLock lock;
// these variables are volatile so that we do not need to sync reads // these variables are volatile so that we do not need to sync reads
// but increments need to be performed under the lock // but increments need to be performed under the lock
@ -134,6 +136,7 @@ public class LRUQueryCache implements QueryCache, Accountable {
uniqueQueries = new LinkedHashMap<>(16, 0.75f, true); uniqueQueries = new LinkedHashMap<>(16, 0.75f, true);
mostRecentlyUsedQueries = uniqueQueries.keySet(); mostRecentlyUsedQueries = uniqueQueries.keySet();
cache = new IdentityHashMap<>(); cache = new IdentityHashMap<>();
lock = new ReentrantLock();
ramBytesUsed = 0; ramBytesUsed = 0;
} }
@ -182,6 +185,7 @@ public class LRUQueryCache implements QueryCache, Accountable {
* @lucene.experimental * @lucene.experimental
*/ */
protected void onHit(Object readerCoreKey, Query query) { protected void onHit(Object readerCoreKey, Query query) {
assert lock.isHeldByCurrentThread();
hitCount += 1; hitCount += 1;
} }
@ -191,6 +195,7 @@ public class LRUQueryCache implements QueryCache, Accountable {
* @lucene.experimental * @lucene.experimental
*/ */
protected void onMiss(Object readerCoreKey, Query query) { protected void onMiss(Object readerCoreKey, Query query) {
assert lock.isHeldByCurrentThread();
assert query != null; assert query != null;
missCount += 1; missCount += 1;
} }
@ -203,6 +208,7 @@ public class LRUQueryCache implements QueryCache, Accountable {
* @lucene.experimental * @lucene.experimental
*/ */
protected void onQueryCache(Query query, long ramBytesUsed) { protected void onQueryCache(Query query, long ramBytesUsed) {
assert lock.isHeldByCurrentThread();
this.ramBytesUsed += ramBytesUsed; this.ramBytesUsed += ramBytesUsed;
} }
@ -212,6 +218,7 @@ public class LRUQueryCache implements QueryCache, Accountable {
* @lucene.experimental * @lucene.experimental
*/ */
protected void onQueryEviction(Query query, long ramBytesUsed) { protected void onQueryEviction(Query query, long ramBytesUsed) {
assert lock.isHeldByCurrentThread();
this.ramBytesUsed -= ramBytesUsed; this.ramBytesUsed -= ramBytesUsed;
} }
@ -223,6 +230,7 @@ public class LRUQueryCache implements QueryCache, Accountable {
* @lucene.experimental * @lucene.experimental
*/ */
protected void onDocIdSetCache(Object readerCoreKey, long ramBytesUsed) { protected void onDocIdSetCache(Object readerCoreKey, long ramBytesUsed) {
assert lock.isHeldByCurrentThread();
cacheSize += 1; cacheSize += 1;
cacheCount += 1; cacheCount += 1;
this.ramBytesUsed += ramBytesUsed; this.ramBytesUsed += ramBytesUsed;
@ -235,6 +243,7 @@ public class LRUQueryCache implements QueryCache, Accountable {
* @lucene.experimental * @lucene.experimental
*/ */
protected void onDocIdSetEviction(Object readerCoreKey, int numEntries, long sumRamBytesUsed) { protected void onDocIdSetEviction(Object readerCoreKey, int numEntries, long sumRamBytesUsed) {
assert lock.isHeldByCurrentThread();
this.ramBytesUsed -= sumRamBytesUsed; this.ramBytesUsed -= sumRamBytesUsed;
cacheSize -= numEntries; cacheSize -= numEntries;
} }
@ -244,12 +253,14 @@ public class LRUQueryCache implements QueryCache, Accountable {
* @lucene.experimental * @lucene.experimental
*/ */
protected void onClear() { protected void onClear() {
assert lock.isHeldByCurrentThread();
ramBytesUsed = 0; ramBytesUsed = 0;
cacheSize = 0; cacheSize = 0;
} }
/** Whether evictions are required. */ /** Whether evictions are required. */
boolean requiresEviction() { boolean requiresEviction() {
assert lock.isHeldByCurrentThread();
final int size = mostRecentlyUsedQueries.size(); final int size = mostRecentlyUsedQueries.size();
if (size == 0) { if (size == 0) {
return false; return false;
@ -258,7 +269,8 @@ public class LRUQueryCache implements QueryCache, Accountable {
} }
} }
synchronized DocIdSet get(Query key, LeafReaderContext context) { DocIdSet get(Query key, LeafReaderContext context) {
assert lock.isHeldByCurrentThread();
assert key instanceof BoostQuery == false; assert key instanceof BoostQuery == false;
assert key instanceof ConstantScoreQuery == false; assert key instanceof ConstantScoreQuery == false;
final Object readerKey = context.reader().getCoreCacheKey(); final Object readerKey = context.reader().getCoreCacheKey();
@ -282,11 +294,12 @@ public class LRUQueryCache implements QueryCache, Accountable {
return cached; return cached;
} }
synchronized void putIfAbsent(Query query, LeafReaderContext context, DocIdSet set) { void putIfAbsent(Query query, LeafReaderContext context, DocIdSet set) {
// under a lock to make sure that mostRecentlyUsedQueries and cache remain sync'ed
// we don't want to have user-provided queries as keys in our cache since queries are mutable
assert query instanceof BoostQuery == false; assert query instanceof BoostQuery == false;
assert query instanceof ConstantScoreQuery == false; assert query instanceof ConstantScoreQuery == false;
// under a lock to make sure that mostRecentlyUsedQueries and cache remain sync'ed
lock.lock();
try {
Query singleton = uniqueQueries.putIfAbsent(query, query); Query singleton = uniqueQueries.putIfAbsent(query, query);
if (singleton == null) { if (singleton == null) {
onQueryCache(singleton, LINKED_HASHTABLE_RAM_BYTES_PER_ENTRY + ramBytesUsed(query)); onQueryCache(singleton, LINKED_HASHTABLE_RAM_BYTES_PER_ENTRY + ramBytesUsed(query));
@ -310,9 +323,13 @@ public class LRUQueryCache implements QueryCache, Accountable {
} }
leafCache.putIfAbsent(query, set); leafCache.putIfAbsent(query, set);
evictIfNecessary(); evictIfNecessary();
} finally {
lock.unlock();
}
} }
synchronized void evictIfNecessary() { void evictIfNecessary() {
assert lock.isHeldByCurrentThread();
// under a lock to make sure that mostRecentlyUsedQueries and cache keep sync'ed // under a lock to make sure that mostRecentlyUsedQueries and cache keep sync'ed
if (requiresEviction()) { if (requiresEviction()) {
@ -337,7 +354,9 @@ public class LRUQueryCache implements QueryCache, Accountable {
/** /**
* Remove all cache entries for the given core cache key. * Remove all cache entries for the given core cache key.
*/ */
public synchronized void clearCoreCacheKey(Object coreKey) { public void clearCoreCacheKey(Object coreKey) {
lock.lock();
try {
final LeafCache leafCache = cache.remove(coreKey); final LeafCache leafCache = cache.remove(coreKey);
if (leafCache != null) { if (leafCache != null) {
ramBytesUsed -= HASHTABLE_RAM_BYTES_PER_ENTRY; ramBytesUsed -= HASHTABLE_RAM_BYTES_PER_ENTRY;
@ -349,19 +368,28 @@ public class LRUQueryCache implements QueryCache, Accountable {
assert leafCache.ramBytesUsed == 0; assert leafCache.ramBytesUsed == 0;
} }
} }
} finally {
lock.unlock();
}
} }
/** /**
* Remove all cache entries for the given query. * Remove all cache entries for the given query.
*/ */
public synchronized void clearQuery(Query query) { public void clearQuery(Query query) {
lock.lock();
try {
final Query singleton = uniqueQueries.remove(query); final Query singleton = uniqueQueries.remove(query);
if (singleton != null) { if (singleton != null) {
onEviction(singleton); onEviction(singleton);
} }
} finally {
lock.unlock();
}
} }
private void onEviction(Query singleton) { private void onEviction(Query singleton) {
assert lock.isHeldByCurrentThread();
onQueryEviction(singleton, LINKED_HASHTABLE_RAM_BYTES_PER_ENTRY + ramBytesUsed(singleton)); onQueryEviction(singleton, LINKED_HASHTABLE_RAM_BYTES_PER_ENTRY + ramBytesUsed(singleton));
for (LeafCache leafCache : cache.values()) { for (LeafCache leafCache : cache.values()) {
leafCache.remove(singleton); leafCache.remove(singleton);
@ -371,14 +399,21 @@ public class LRUQueryCache implements QueryCache, Accountable {
/** /**
* Clear the content of this cache. * Clear the content of this cache.
*/ */
public synchronized void clear() { public void clear() {
lock.lock();
try {
cache.clear(); cache.clear();
mostRecentlyUsedQueries.clear(); mostRecentlyUsedQueries.clear();
onClear(); onClear();
} finally {
lock.unlock();
}
} }
// pkg-private for testing // pkg-private for testing
synchronized void assertConsistent() { void assertConsistent() {
lock.lock();
try {
if (requiresEviction()) { if (requiresEviction()) {
throw new AssertionError("requires evictions: size=" + mostRecentlyUsedQueries.size() throw new AssertionError("requires evictions: size=" + mostRecentlyUsedQueries.size()
+ ", maxSize=" + maxSize + ", ramBytesUsed=" + ramBytesUsed() + ", maxRamBytesUsed=" + maxRamBytesUsed); + ", maxSize=" + maxSize + ", ramBytesUsed=" + ramBytesUsed() + ", maxRamBytesUsed=" + maxRamBytesUsed);
@ -414,12 +449,20 @@ public class LRUQueryCache implements QueryCache, Accountable {
if (recomputedCacheSize != getCacheSize()) { if (recomputedCacheSize != getCacheSize()) {
throw new AssertionError("cacheSize mismatch : " + getCacheSize() + " != " + recomputedCacheSize); throw new AssertionError("cacheSize mismatch : " + getCacheSize() + " != " + recomputedCacheSize);
} }
} finally {
lock.unlock();
}
} }
// pkg-private for testing // pkg-private for testing
// return the list of cached queries in LRU order // return the list of cached queries in LRU order
synchronized List<Query> cachedQueries() { List<Query> cachedQueries() {
lock.lock();
try {
return new ArrayList<>(mostRecentlyUsedQueries); return new ArrayList<>(mostRecentlyUsedQueries);
} finally {
lock.unlock();
}
} }
@Override @Override
@ -438,8 +481,11 @@ public class LRUQueryCache implements QueryCache, Accountable {
@Override @Override
public Collection<Accountable> getChildResources() { public Collection<Accountable> getChildResources() {
synchronized (this) { lock.lock();
try {
return Accountables.namedAccountables("segment", cache); return Accountables.namedAccountables("segment", cache);
} finally {
lock.unlock();
} }
} }
@ -659,7 +705,18 @@ public class LRUQueryCache implements QueryCache, Accountable {
return in.scorer(context); return in.scorer(context);
} }
DocIdSet docIdSet = get(in.getQuery(), context); // If the lock is already busy, prefer using the uncached version than waiting
if (lock.tryLock() == false) {
return in.scorer(context);
}
DocIdSet docIdSet;
try {
docIdSet = get(in.getQuery(), context);
} finally {
lock.unlock();
}
if (docIdSet == null) { if (docIdSet == null) {
if (policy.shouldCache(in.getQuery())) { if (policy.shouldCache(in.getQuery())) {
docIdSet = cache(context); docIdSet = cache(context);
@ -692,7 +749,18 @@ public class LRUQueryCache implements QueryCache, Accountable {
return in.bulkScorer(context); return in.bulkScorer(context);
} }
DocIdSet docIdSet = get(in.getQuery(), context); // If the lock is already busy, prefer using the uncached version than waiting
if (lock.tryLock() == false) {
return in.bulkScorer(context);
}
DocIdSet docIdSet;
try {
docIdSet = get(in.getQuery(), context);
} finally {
lock.unlock();
}
if (docIdSet == null) { if (docIdSet == null) {
if (policy.shouldCache(in.getQuery())) { if (policy.shouldCache(in.getQuery())) {
docIdSet = cache(context); docIdSet = cache(context);