|
@@ -93,7 +93,7 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|
|
import java.util.concurrent.atomic.AtomicLong;
|
|
|
import java.util.concurrent.locks.Lock;
|
|
|
import java.util.concurrent.locks.ReentrantLock;
|
|
|
-import java.util.function.Function;
|
|
|
+import java.util.function.BiFunction;
|
|
|
import java.util.function.LongSupplier;
|
|
|
|
|
|
public class InternalEngine extends Engine {
|
|
@@ -108,20 +108,18 @@ public class InternalEngine extends Engine {
|
|
|
|
|
|
private final IndexWriter indexWriter;
|
|
|
|
|
|
- private final SearcherFactory searcherFactory;
|
|
|
- private final SearcherManager searcherManager;
|
|
|
+ private final SearcherManager externalSearcherManager;
|
|
|
+ private final SearcherManager internalSearcherManager;
|
|
|
|
|
|
private final Lock flushLock = new ReentrantLock();
|
|
|
private final ReentrantLock optimizeLock = new ReentrantLock();
|
|
|
|
|
|
// A uid (in the form of BytesRef) to the version map
|
|
|
// we use the hashed variant since we iterate over it and check removal and additions on existing keys
|
|
|
- private final LiveVersionMap versionMap;
|
|
|
+ private final LiveVersionMap versionMap = new LiveVersionMap();
|
|
|
|
|
|
private final KeyedLock<BytesRef> keyedLock = new KeyedLock<>();
|
|
|
|
|
|
- private final AtomicBoolean versionMapRefreshPending = new AtomicBoolean();
|
|
|
-
|
|
|
private volatile SegmentInfos lastCommittedSegmentInfos;
|
|
|
|
|
|
private final IndexThrottle throttle;
|
|
@@ -153,7 +151,6 @@ public class InternalEngine extends Engine {
|
|
|
maxUnsafeAutoIdTimestamp.set(Long.MAX_VALUE);
|
|
|
}
|
|
|
this.uidField = engineConfig.getIndexSettings().isSingleType() ? IdFieldMapper.NAME : UidFieldMapper.NAME;
|
|
|
- this.versionMap = new LiveVersionMap();
|
|
|
final TranslogDeletionPolicy translogDeletionPolicy = new TranslogDeletionPolicy(
|
|
|
engineConfig.getIndexSettings().getTranslogRetentionSize().getBytes(),
|
|
|
engineConfig.getIndexSettings().getTranslogRetentionAge().getMillis()
|
|
@@ -163,7 +160,8 @@ public class InternalEngine extends Engine {
|
|
|
store.incRef();
|
|
|
IndexWriter writer = null;
|
|
|
Translog translog = null;
|
|
|
- SearcherManager manager = null;
|
|
|
+ SearcherManager externalSearcherManager = null;
|
|
|
+ SearcherManager internalSearcherManager = null;
|
|
|
EngineMergeScheduler scheduler = null;
|
|
|
boolean success = false;
|
|
|
try {
|
|
@@ -171,7 +169,6 @@ public class InternalEngine extends Engine {
|
|
|
|
|
|
mergeScheduler = scheduler = new EngineMergeScheduler(engineConfig.getShardId(), engineConfig.getIndexSettings());
|
|
|
throttle = new IndexThrottle();
|
|
|
- this.searcherFactory = new SearchFactory(logger, isClosed, engineConfig);
|
|
|
try {
|
|
|
final SeqNoStats seqNoStats;
|
|
|
switch (openMode) {
|
|
@@ -215,20 +212,21 @@ public class InternalEngine extends Engine {
|
|
|
throw e;
|
|
|
}
|
|
|
}
|
|
|
- manager = createSearcherManager();
|
|
|
- this.searcherManager = manager;
|
|
|
- this.versionMap.setManager(searcherManager);
|
|
|
+ internalSearcherManager = createSearcherManager(new SearcherFactory(), false);
|
|
|
+ externalSearcherManager = createSearcherManager(new SearchFactory(logger, isClosed, engineConfig), true);
|
|
|
+ this.internalSearcherManager = internalSearcherManager;
|
|
|
+ this.externalSearcherManager = externalSearcherManager;
|
|
|
+ internalSearcherManager.addListener(versionMap);
|
|
|
assert pendingTranslogRecovery.get() == false : "translog recovery can't be pending before we set it";
|
|
|
// don't allow commits until we are done with recovering
|
|
|
pendingTranslogRecovery.set(openMode == EngineConfig.OpenMode.OPEN_INDEX_AND_TRANSLOG);
|
|
|
for (ReferenceManager.RefreshListener listener: engineConfig.getRefreshListeners()) {
|
|
|
- searcherManager.addListener(listener);
|
|
|
+ this.externalSearcherManager.addListener(listener);
|
|
|
}
|
|
|
success = true;
|
|
|
} finally {
|
|
|
if (success == false) {
|
|
|
- IOUtils.closeWhileHandlingException(writer, translog, manager, scheduler);
|
|
|
- versionMap.clear();
|
|
|
+ IOUtils.closeWhileHandlingException(writer, translog, externalSearcherManager, internalSearcherManager, scheduler);
|
|
|
if (isClosed.get() == false) {
|
|
|
// failure we need to dec the store reference
|
|
|
store.decRef();
|
|
@@ -345,6 +343,7 @@ public class InternalEngine extends Engine {
|
|
|
logger.trace("flushing post recovery from translog. ops recovered [{}]. committed translog id [{}]. current id [{}]",
|
|
|
opsRecovered, translogGeneration == null ? null : translogGeneration.translogFileGeneration, translog.currentFileGeneration());
|
|
|
flush(true, true);
|
|
|
+ refresh("translog_recovery");
|
|
|
} else if (translog.isCurrent(translogGeneration) == false) {
|
|
|
commitIndexWriter(indexWriter, translog, lastCommittedSegmentInfos.getUserData().get(Engine.SYNC_COMMIT_ID));
|
|
|
refreshLastCommittedSegmentInfos();
|
|
@@ -441,14 +440,16 @@ public class InternalEngine extends Engine {
|
|
|
return uuid;
|
|
|
}
|
|
|
|
|
|
- private SearcherManager createSearcherManager() throws EngineException {
|
|
|
+ private SearcherManager createSearcherManager(SearcherFactory searcherFactory, boolean readSegmentsInfo) throws EngineException {
|
|
|
boolean success = false;
|
|
|
SearcherManager searcherManager = null;
|
|
|
try {
|
|
|
try {
|
|
|
final DirectoryReader directoryReader = ElasticsearchDirectoryReader.wrap(DirectoryReader.open(indexWriter), shardId);
|
|
|
searcherManager = new SearcherManager(directoryReader, searcherFactory);
|
|
|
- lastCommittedSegmentInfos = readLastCommittedSegmentInfos(searcherManager, store);
|
|
|
+ if (readSegmentsInfo) {
|
|
|
+ lastCommittedSegmentInfos = readLastCommittedSegmentInfos(searcherManager, store);
|
|
|
+ }
|
|
|
success = true;
|
|
|
return searcherManager;
|
|
|
} catch (IOException e) {
|
|
@@ -468,10 +469,11 @@ public class InternalEngine extends Engine {
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
- public GetResult get(Get get, Function<String, Searcher> searcherFactory) throws EngineException {
|
|
|
+ public GetResult get(Get get, BiFunction<String, SearcherScope, Searcher> searcherFactory) throws EngineException {
|
|
|
assert Objects.equals(get.uid().field(), uidField) : get.uid().field();
|
|
|
try (ReleasableLock ignored = readLock.acquire()) {
|
|
|
ensureOpen();
|
|
|
+ SearcherScope scope;
|
|
|
if (get.realtime()) {
|
|
|
VersionValue versionValue = versionMap.getUnderLock(get.uid());
|
|
|
if (versionValue != null) {
|
|
@@ -482,12 +484,16 @@ public class InternalEngine extends Engine {
|
|
|
throw new VersionConflictEngineException(shardId, get.type(), get.id(),
|
|
|
get.versionType().explainConflictForReads(versionValue.version, get.version()));
|
|
|
}
|
|
|
- refresh("realtime_get");
|
|
|
+ refresh("realtime_get", SearcherScope.INTERNAL);
|
|
|
}
|
|
|
+ scope = SearcherScope.INTERNAL;
|
|
|
+ } else {
|
|
|
+ // we expose what has been externally expose in a point in time snapshot via an explicit refresh
|
|
|
+ scope = SearcherScope.EXTERNAL;
|
|
|
}
|
|
|
|
|
|
// no version, get the version from the index, we know that we refresh on flush
|
|
|
- return getFromSearcher(get, searcherFactory);
|
|
|
+ return getFromSearcher(get, searcherFactory, scope);
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -1187,17 +1193,34 @@ public class InternalEngine extends Engine {
|
|
|
|
|
|
@Override
|
|
|
public void refresh(String source) throws EngineException {
|
|
|
+ refresh(source, SearcherScope.EXTERNAL);
|
|
|
+ }
|
|
|
+
|
|
|
+ final void refresh(String source, SearcherScope scope) throws EngineException {
|
|
|
// we obtain a read lock here, since we don't want a flush to happen while we are refreshing
|
|
|
// since it flushes the index as well (though, in terms of concurrency, we are allowed to do it)
|
|
|
try (ReleasableLock lock = readLock.acquire()) {
|
|
|
ensureOpen();
|
|
|
- searcherManager.maybeRefreshBlocking();
|
|
|
+ switch (scope) {
|
|
|
+ case EXTERNAL:
|
|
|
+ // even though we maintain 2 managers we really do the heavy-lifting only once.
|
|
|
+ // the second refresh will only do the extra work we have to do for warming caches etc.
|
|
|
+ externalSearcherManager.maybeRefreshBlocking();
|
|
|
+ // the break here is intentional we never refresh both internal / external together
|
|
|
+ break;
|
|
|
+ case INTERNAL:
|
|
|
+ internalSearcherManager.maybeRefreshBlocking();
|
|
|
+ break;
|
|
|
+
|
|
|
+ default:
|
|
|
+ throw new IllegalArgumentException("unknown scope: " + scope);
|
|
|
+ }
|
|
|
} catch (AlreadyClosedException e) {
|
|
|
failOnTragicEvent(e);
|
|
|
throw e;
|
|
|
} catch (Exception e) {
|
|
|
try {
|
|
|
- failEngine("refresh failed", e);
|
|
|
+ failEngine("refresh failed source[" + source + "]", e);
|
|
|
} catch (Exception inner) {
|
|
|
e.addSuppressed(inner);
|
|
|
}
|
|
@@ -1208,36 +1231,20 @@ public class InternalEngine extends Engine {
|
|
|
// We check for pruning in each delete request, but we also prune here e.g. in case a delete burst comes in and then no more deletes
|
|
|
// for a long time:
|
|
|
maybePruneDeletedTombstones();
|
|
|
- versionMapRefreshPending.set(false);
|
|
|
mergeScheduler.refreshConfig();
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
public void writeIndexingBuffer() throws EngineException {
|
|
|
-
|
|
|
// we obtain a read lock here, since we don't want a flush to happen while we are writing
|
|
|
// since it flushes the index as well (though, in terms of concurrency, we are allowed to do it)
|
|
|
try (ReleasableLock lock = readLock.acquire()) {
|
|
|
ensureOpen();
|
|
|
-
|
|
|
- // TODO: it's not great that we secretly tie searcher visibility to "freeing up heap" here... really we should keep two
|
|
|
- // searcher managers, one for searching which is only refreshed by the schedule the user requested (refresh_interval, or invoking
|
|
|
- // refresh API), and another for version map interactions. See #15768.
|
|
|
final long versionMapBytes = versionMap.ramBytesUsedForRefresh();
|
|
|
final long indexingBufferBytes = indexWriter.ramBytesUsed();
|
|
|
-
|
|
|
- final boolean useRefresh = versionMapRefreshPending.get() || (indexingBufferBytes / 4 < versionMapBytes);
|
|
|
- if (useRefresh) {
|
|
|
- // The version map is using > 25% of the indexing buffer, so we do a refresh so the version map also clears
|
|
|
- logger.debug("use refresh to write indexing buffer (heap size=[{}]), to also clear version map (heap size=[{}])",
|
|
|
- new ByteSizeValue(indexingBufferBytes), new ByteSizeValue(versionMapBytes));
|
|
|
- refresh("write indexing buffer");
|
|
|
- } else {
|
|
|
- // Most of our heap is used by the indexing buffer, so we do a cheaper (just writes segments, doesn't open a new searcher) IW.flush:
|
|
|
- logger.debug("use IndexWriter.flush to write indexing buffer (heap size=[{}]) since version map is small (heap size=[{}])",
|
|
|
- new ByteSizeValue(indexingBufferBytes), new ByteSizeValue(versionMapBytes));
|
|
|
- indexWriter.flush();
|
|
|
- }
|
|
|
+ logger.debug("use refresh to write indexing buffer (heap size=[{}]), to also clear version map (heap size=[{}])",
|
|
|
+ new ByteSizeValue(indexingBufferBytes), new ByteSizeValue(versionMapBytes));
|
|
|
+ refresh("write indexing buffer", SearcherScope.INTERNAL);
|
|
|
} catch (AlreadyClosedException e) {
|
|
|
failOnTragicEvent(e);
|
|
|
throw e;
|
|
@@ -1302,10 +1309,11 @@ public class InternalEngine extends Engine {
|
|
|
maybeFailEngine("renew sync commit", ex);
|
|
|
throw new EngineException(shardId, "failed to renew sync commit", ex);
|
|
|
}
|
|
|
- if (renewed) { // refresh outside of the write lock
|
|
|
- refresh("renew sync commit");
|
|
|
+ if (renewed) {
|
|
|
+ // refresh outside of the write lock
|
|
|
+ // we have to refresh internal searcher here to ensure we release unreferenced segments.
|
|
|
+ refresh("renew sync commit", SearcherScope.INTERNAL);
|
|
|
}
|
|
|
-
|
|
|
return renewed;
|
|
|
}
|
|
|
|
|
@@ -1347,7 +1355,7 @@ public class InternalEngine extends Engine {
|
|
|
commitIndexWriter(indexWriter, translog, null);
|
|
|
logger.trace("finished commit for flush");
|
|
|
// we need to refresh in order to clear older version values
|
|
|
- refresh("version_table_flush");
|
|
|
+ refresh("version_table_flush", SearcherScope.INTERNAL);
|
|
|
translog.trimUnreferencedReaders();
|
|
|
} catch (Exception e) {
|
|
|
throw new FlushFailedEngineException(shardId, e);
|
|
@@ -1651,8 +1659,9 @@ public class InternalEngine extends Engine {
|
|
|
assert rwl.isWriteLockedByCurrentThread() || failEngineLock.isHeldByCurrentThread() : "Either the write lock must be held or the engine must be currently be failing itself";
|
|
|
try {
|
|
|
this.versionMap.clear();
|
|
|
+ internalSearcherManager.removeListener(versionMap);
|
|
|
try {
|
|
|
- IOUtils.close(searcherManager);
|
|
|
+ IOUtils.close(externalSearcherManager, internalSearcherManager);
|
|
|
} catch (Exception e) {
|
|
|
logger.warn("Failed to close SearcherManager", e);
|
|
|
}
|
|
@@ -1684,8 +1693,15 @@ public class InternalEngine extends Engine {
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
- protected SearcherManager getSearcherManager() {
|
|
|
- return searcherManager;
|
|
|
+ protected SearcherManager getSearcherManager(String source, SearcherScope scope) {
|
|
|
+ switch (scope) {
|
|
|
+ case INTERNAL:
|
|
|
+ return internalSearcherManager;
|
|
|
+ case EXTERNAL:
|
|
|
+ return externalSearcherManager;
|
|
|
+ default:
|
|
|
+ throw new IllegalStateException("unknown scope: " + scope);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
private Releasable acquireLock(BytesRef uid) {
|
|
@@ -1698,7 +1714,7 @@ public class InternalEngine extends Engine {
|
|
|
|
|
|
private long loadCurrentVersionFromIndex(Term uid) throws IOException {
|
|
|
assert incrementIndexVersionLookup();
|
|
|
- try (Searcher searcher = acquireSearcher("load_version")) {
|
|
|
+ try (Searcher searcher = acquireSearcher("load_version", SearcherScope.INTERNAL)) {
|
|
|
return VersionsAndSeqNoResolver.loadVersion(searcher.reader(), uid);
|
|
|
}
|
|
|
}
|