CavalliumDBEngine/src/main/java/it/cavallium/dbengine/database/disk/CachedIndexSearcherManager.java

201 lines
6.7 KiB
Java
Raw Normal View History

2021-09-06 15:06:51 +02:00
package it.cavallium.dbengine.database.disk;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
2021-11-08 12:06:32 +01:00
import io.net5.buffer.api.Resource;
2021-09-18 18:34:21 +02:00
import io.net5.buffer.api.Send;
2021-09-06 15:06:51 +02:00
import it.cavallium.dbengine.database.LLSnapshot;
import java.io.IOException;
import java.time.Duration;
2021-09-25 13:06:24 +02:00
import java.util.concurrent.Executor;
import java.util.concurrent.ForkJoinPool;
2021-09-06 18:29:10 +02:00
import java.util.concurrent.Phaser;
2021-09-07 02:36:11 +02:00
import java.util.concurrent.TimeUnit;
2021-09-07 11:39:13 +02:00
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
2021-09-06 15:06:51 +02:00
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.SearcherFactory;
import org.apache.lucene.search.SearcherManager;
import org.apache.lucene.search.similarities.Similarity;
2021-09-06 17:35:02 +02:00
import org.apache.lucene.store.AlreadyClosedException;
2021-09-06 15:06:51 +02:00
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
2021-11-07 14:46:03 +01:00
import org.warp.commonutils.log.Logger;
import org.warp.commonutils.log.LoggerFactory;
2021-09-06 15:06:51 +02:00
import reactor.core.publisher.Mono;
import reactor.core.publisher.Sinks;
import reactor.core.publisher.Sinks.Empty;
import reactor.core.scheduler.Schedulers;
2021-09-18 18:34:21 +02:00
public class CachedIndexSearcherManager implements IndexSearcherManager {
2021-09-06 15:06:51 +02:00
2021-09-06 18:52:21 +02:00
private static final Logger logger = LoggerFactory.getLogger(CachedIndexSearcherManager.class);
2021-09-25 13:06:24 +02:00
private static final Executor SEARCH_EXECUTOR = ForkJoinPool.commonPool();
private static final SearcherFactory SEARCHER_FACTORY = new ExecutorSearcherFactory(SEARCH_EXECUTOR);
2021-09-06 18:52:21 +02:00
2021-09-06 15:06:51 +02:00
private final SnapshotsManager snapshotsManager;
private final Similarity similarity;
private final SearcherManager searcherManager;
private final Duration queryRefreshDebounceTime;
2021-09-06 18:29:10 +02:00
private final Phaser activeSearchers = new Phaser(1);
2021-09-06 18:52:21 +02:00
private final Phaser activeRefreshes = new Phaser(1);
2021-09-06 15:06:51 +02:00
2021-09-18 18:34:21 +02:00
private final LoadingCache<LLSnapshot, Mono<Send<LLIndexSearcher>>> cachedSnapshotSearchers;
private final Mono<Send<LLIndexSearcher>> cachedMainSearcher;
2021-09-06 15:06:51 +02:00
private final AtomicBoolean closeRequested = new AtomicBoolean();
private final Empty<Void> closeRequestedMono = Sinks.empty();
2021-09-08 21:34:52 +02:00
private final Mono<Void> closeMono;
2021-09-06 15:06:51 +02:00
2021-09-06 18:24:36 +02:00
public CachedIndexSearcherManager(IndexWriter indexWriter,
2021-09-06 15:06:51 +02:00
SnapshotsManager snapshotsManager,
Similarity similarity,
boolean applyAllDeletes,
boolean writeAllDeletes,
Duration queryRefreshDebounceTime) throws IOException {
this.snapshotsManager = snapshotsManager;
this.similarity = similarity;
this.queryRefreshDebounceTime = queryRefreshDebounceTime;
2021-09-25 13:06:24 +02:00
this.searcherManager = new SearcherManager(indexWriter, applyAllDeletes, writeAllDeletes, SEARCHER_FACTORY);
2021-09-06 15:06:51 +02:00
Empty<Void> refresherClosed = Sinks.empty();
2021-09-06 15:06:51 +02:00
Mono
2021-09-06 18:52:21 +02:00
.fromRunnable(() -> {
try {
2021-09-08 21:34:52 +02:00
maybeRefresh();
2021-09-06 18:52:21 +02:00
} catch (Exception ex) {
logger.error("Failed to refresh the searcher manager", ex);
}
})
2021-09-06 15:06:51 +02:00
.subscribeOn(Schedulers.boundedElastic())
2021-09-09 11:43:37 +02:00
.repeatWhen(s -> s.delayElements(queryRefreshDebounceTime))
.takeUntilOther(closeRequestedMono.asMono())
2021-09-06 15:06:51 +02:00
.doAfterTerminate(refresherClosed::tryEmitEmpty)
.subscribe();
this.cachedSnapshotSearchers = CacheBuilder.newBuilder()
.expireAfterWrite(queryRefreshDebounceTime)
2021-09-06 15:08:07 +02:00
// Max 3 cached non-main index writers
.maximumSize(3)
2021-09-06 15:06:51 +02:00
.build(new CacheLoader<>() {
@Override
2021-09-18 18:34:21 +02:00
public Mono<Send<LLIndexSearcher>> load(@NotNull LLSnapshot snapshot) {
2021-09-06 18:24:36 +02:00
return CachedIndexSearcherManager.this.generateCachedSearcher(snapshot);
2021-09-06 15:06:51 +02:00
}
});
this.cachedMainSearcher = this.generateCachedSearcher(null);
2021-09-08 21:34:52 +02:00
this.closeMono = Mono
.fromRunnable(() -> {
logger.info("Closing IndexSearcherManager...");
this.closeRequested.set(true);
this.closeRequestedMono.tryEmitEmpty();
2021-09-08 21:34:52 +02:00
})
.then(refresherClosed.asMono())
.then(Mono.<Void>fromRunnable(() -> {
logger.info("Closed IndexSearcherManager");
logger.info("Closing refreshes...");
if (!activeRefreshes.isTerminated()) {
try {
activeRefreshes.awaitAdvanceInterruptibly(activeRefreshes.arrive(), 15, TimeUnit.SECONDS);
} catch (Exception ex) {
if (ex instanceof TimeoutException) {
logger.error("Failed to terminate active refreshes: timeout");
} else {
logger.error("Failed to terminate active refreshes", ex);
}
}
}
logger.info("Closed refreshes...");
logger.info("Closing active searchers...");
if (!activeSearchers.isTerminated()) {
try {
activeSearchers.awaitAdvanceInterruptibly(activeSearchers.arrive(), 15, TimeUnit.SECONDS);
} catch (Exception ex) {
if (ex instanceof TimeoutException) {
logger.error("Failed to terminate active searchers: timeout");
} else {
logger.error("Failed to terminate active searchers", ex);
}
}
}
logger.info("Closed active searchers");
cachedSnapshotSearchers.invalidateAll();
cachedSnapshotSearchers.cleanUp();
})).cache();
2021-09-06 15:06:51 +02:00
}
2021-09-18 18:34:21 +02:00
private Mono<Send<LLIndexSearcher>> generateCachedSearcher(@Nullable LLSnapshot snapshot) {
2021-09-22 11:03:39 +02:00
// todo: check if defer is really needed
return Mono.defer(() -> {
if (closeRequested.get()) {
return Mono.empty();
}
2021-09-22 11:03:39 +02:00
return Mono.fromCallable(() -> {
activeSearchers.register();
IndexSearcher indexSearcher;
2021-09-24 02:44:12 +02:00
boolean decRef;
2021-09-22 11:03:39 +02:00
if (snapshot == null) {
indexSearcher = searcherManager.acquire();
2021-09-24 02:44:12 +02:00
decRef = true;
2021-09-22 11:03:39 +02:00
} else {
2021-09-25 13:06:24 +02:00
indexSearcher = snapshotsManager.resolveSnapshot(snapshot).getIndexSearcher(SEARCH_EXECUTOR);
2021-09-24 02:44:12 +02:00
decRef = false;
2021-09-22 11:03:39 +02:00
}
2021-09-06 15:06:51 +02:00
indexSearcher.setSimilarity(similarity);
2021-09-22 11:03:39 +02:00
assert indexSearcher.getIndexReader().getRefCount() > 0;
2021-09-24 02:44:12 +02:00
return new LLIndexSearcher(indexSearcher, decRef, this::dropCachedIndexSearcher).send();
2021-09-22 11:03:39 +02:00
})
2021-11-08 12:06:32 +01:00
.doOnDiscard(Send.class, Send::close)
.doOnDiscard(Resource.class, Resource::close);
2021-09-22 11:03:39 +02:00
});
2021-09-18 18:34:21 +02:00
}
2021-10-01 19:17:33 +02:00
private void dropCachedIndexSearcher() {
2021-09-18 18:34:21 +02:00
// This shouldn't happen more than once per searcher.
activeSearchers.arriveAndDeregister();
2021-09-06 15:06:51 +02:00
}
2021-09-18 18:34:21 +02:00
@Override
2021-09-06 15:06:51 +02:00
public void maybeRefreshBlocking() throws IOException {
2021-09-06 17:35:02 +02:00
try {
2021-09-06 18:52:21 +02:00
activeRefreshes.register();
2021-09-06 17:35:02 +02:00
searcherManager.maybeRefreshBlocking();
} catch (AlreadyClosedException ignored) {
2021-09-06 18:52:21 +02:00
} finally {
activeRefreshes.arriveAndDeregister();
2021-09-06 17:35:02 +02:00
}
2021-09-06 15:06:51 +02:00
}
2021-09-18 18:34:21 +02:00
@Override
2021-09-06 15:06:51 +02:00
public void maybeRefresh() throws IOException {
2021-09-06 17:35:02 +02:00
try {
2021-09-06 18:52:21 +02:00
activeRefreshes.register();
2021-09-06 17:35:02 +02:00
searcherManager.maybeRefresh();
} catch (AlreadyClosedException ignored) {
2021-09-06 18:52:21 +02:00
} finally {
activeRefreshes.arriveAndDeregister();
2021-09-06 17:35:02 +02:00
}
2021-09-06 15:06:51 +02:00
}
2021-09-18 18:34:21 +02:00
@Override
public Mono<Send<LLIndexSearcher>> retrieveSearcher(@Nullable LLSnapshot snapshot) {
2021-09-06 15:06:51 +02:00
if (snapshot == null) {
return this.cachedMainSearcher;
} else {
return this.cachedSnapshotSearchers.getUnchecked(snapshot);
}
}
2021-09-18 18:34:21 +02:00
@Override
2021-09-06 15:06:51 +02:00
public Mono<Void> close() {
2021-09-08 21:34:52 +02:00
return closeMono;
2021-09-06 15:06:51 +02:00
}
2021-09-25 13:06:24 +02:00
2021-09-06 15:06:51 +02:00
}