4848import org .elasticsearch .Version ;
4949import org .elasticsearch .action .index .IndexRequest ;
5050import org .elasticsearch .common .Nullable ;
51+ import org .elasticsearch .common .SuppressForbidden ;
5152import org .elasticsearch .common .UUIDs ;
5253import org .elasticsearch .common .lease .Releasable ;
5354import org .elasticsearch .common .lucene .LoggerInfoStream ;
5758import org .elasticsearch .common .lucene .uid .VersionsAndSeqNoResolver ;
5859import org .elasticsearch .common .lucene .uid .VersionsAndSeqNoResolver .DocIdAndSeqNo ;
5960import org .elasticsearch .common .metrics .CounterMetric ;
60- import org .elasticsearch .common .unit .ByteSizeValue ;
6161import org .elasticsearch .common .util .concurrent .AbstractRunnable ;
6262import org .elasticsearch .common .util .concurrent .KeyedLock ;
6363import org .elasticsearch .common .util .concurrent .ReleasableLock ;
@@ -108,7 +108,7 @@ public class InternalEngine extends Engine {
108108
109109 private final IndexWriter indexWriter ;
110110
111- private final SearcherManager externalSearcherManager ;
111+ private final ExternalSearcherManager externalSearcherManager ;
112112 private final SearcherManager internalSearcherManager ;
113113
114114 private final Lock flushLock = new ReentrantLock ();
@@ -172,7 +172,7 @@ public InternalEngine(EngineConfig engineConfig) {
172172 store .incRef ();
173173 IndexWriter writer = null ;
174174 Translog translog = null ;
175- SearcherManager externalSearcherManager = null ;
175+ ExternalSearcherManager externalSearcherManager = null ;
176176 SearcherManager internalSearcherManager = null ;
177177 EngineMergeScheduler scheduler = null ;
178178 boolean success = false ;
@@ -224,8 +224,8 @@ public InternalEngine(EngineConfig engineConfig) {
224224 throw e ;
225225 }
226226 }
227- internalSearcherManager = createSearcherManager (new SearcherFactory (), false );
228- externalSearcherManager = createSearcherManager ( new SearchFactory ( logger , isClosed , engineConfig ), true ) ;
227+ externalSearcherManager = createSearcherManager (new SearchFactory ( logger , isClosed , engineConfig ) );
228+ internalSearcherManager = externalSearcherManager . internalSearcherManager ;
229229 this .internalSearcherManager = internalSearcherManager ;
230230 this .externalSearcherManager = externalSearcherManager ;
231231 internalSearcherManager .addListener (versionMap );
@@ -238,7 +238,7 @@ public InternalEngine(EngineConfig engineConfig) {
238238 success = true ;
239239 } finally {
240240 if (success == false ) {
241- IOUtils .closeWhileHandlingException (writer , translog , externalSearcherManager , internalSearcherManager , scheduler );
241+ IOUtils .closeWhileHandlingException (writer , translog , internalSearcherManager , externalSearcherManager , scheduler );
242242 if (isClosed .get () == false ) {
243243 // failure we need to dec the store reference
244244 store .decRef ();
@@ -248,6 +248,75 @@ public InternalEngine(EngineConfig engineConfig) {
248248 logger .trace ("created new InternalEngine" );
249249 }
250250
251+ /**
252+ * This reference manager delegates all it's refresh calls to another (internal) SearcherManager
253+ * The main purpose for this is that if we have external refreshes happening we don't issue extra
254+ * refreshes to clear version map memory etc. this can cause excessive segment creation if heavy indexing
255+ * is happening and the refresh interval is low (ie. 1 sec)
256+ *
257+ * This also prevents segment starvation where an internal reader holds on to old segments literally forever
258+ * since no indexing is happening and refreshes are only happening to the external reader manager, while with
259+ * this specialized implementation an external refresh will immediately be reflected on the internal reader
260+ * and old segments can be released in the same way previous version did this (as a side-effect of _refresh)
261+ */
262+ @ SuppressForbidden (reason = "reference counting is required here" )
263+ private static final class ExternalSearcherManager extends ReferenceManager <IndexSearcher > {
264+ private final SearcherFactory searcherFactory ;
265+ private final SearcherManager internalSearcherManager ;
266+
267+ ExternalSearcherManager (SearcherManager internalSearcherManager , SearcherFactory searcherFactory ) throws IOException {
268+ IndexSearcher acquire = internalSearcherManager .acquire ();
269+ try {
270+ IndexReader indexReader = acquire .getIndexReader ();
271+ assert indexReader instanceof ElasticsearchDirectoryReader :
272+ "searcher's IndexReader should be an ElasticsearchDirectoryReader, but got " + indexReader ;
273+ indexReader .incRef (); // steal the reader - getSearcher will decrement if it fails
274+ current = SearcherManager .getSearcher (searcherFactory , indexReader , null );
275+ } finally {
276+ internalSearcherManager .release (acquire );
277+ }
278+ this .searcherFactory = searcherFactory ;
279+ this .internalSearcherManager = internalSearcherManager ;
280+ }
281+
282+ @ Override
283+ protected IndexSearcher refreshIfNeeded (IndexSearcher referenceToRefresh ) throws IOException {
284+ // we simply run a blocking refresh on the internal reference manager and then steal it's reader
285+ // it's a save operation since we acquire the reader which incs it's reference but then down the road
286+ // steal it by calling incRef on the "stolen" reader
287+ internalSearcherManager .maybeRefreshBlocking ();
288+ IndexSearcher acquire = internalSearcherManager .acquire ();
289+ final IndexReader previousReader = referenceToRefresh .getIndexReader ();
290+ assert previousReader instanceof ElasticsearchDirectoryReader :
291+ "searcher's IndexReader should be an ElasticsearchDirectoryReader, but got " + previousReader ;
292+ try {
293+ final IndexReader newReader = acquire .getIndexReader ();
294+ if (newReader == previousReader ) {
295+ // nothing has changed - both ref managers share the same instance so we can use reference equality
296+ return null ;
297+ } else {
298+ newReader .incRef (); // steal the reader - getSearcher will decrement if it fails
299+ return SearcherManager .getSearcher (searcherFactory , newReader , previousReader );
300+ }
301+ } finally {
302+ internalSearcherManager .release (acquire );
303+ }
304+ }
305+
306+ @ Override
307+ protected boolean tryIncRef (IndexSearcher reference ) {
308+ return reference .getIndexReader ().tryIncRef ();
309+ }
310+
311+ @ Override
312+ protected int getRefCount (IndexSearcher reference ) {
313+ return reference .getIndexReader ().getRefCount ();
314+ }
315+
316+ @ Override
317+ protected void decRef (IndexSearcher reference ) throws IOException { reference .getIndexReader ().decRef (); }
318+ }
319+
251320 @ Override
252321 public void restoreLocalCheckpointFromTranslog () throws IOException {
253322 try (ReleasableLock ignored = writeLock .acquire ()) {
@@ -456,18 +525,18 @@ private String loadOrGenerateHistoryUUID(final IndexWriter writer, boolean force
456525 return uuid ;
457526 }
458527
459- private SearcherManager createSearcherManager (SearcherFactory searcherFactory , boolean readSegmentsInfo ) throws EngineException {
528+ private ExternalSearcherManager createSearcherManager (SearchFactory externalSearcherFactory ) throws EngineException {
460529 boolean success = false ;
461- SearcherManager searcherManager = null ;
530+ SearcherManager internalSearcherManager = null ;
462531 try {
463532 try {
464533 final DirectoryReader directoryReader = ElasticsearchDirectoryReader .wrap (DirectoryReader .open (indexWriter ), shardId );
465- searcherManager = new SearcherManager (directoryReader , searcherFactory );
466- if ( readSegmentsInfo ) {
467- lastCommittedSegmentInfos = readLastCommittedSegmentInfos ( searcherManager , store );
468- }
534+ internalSearcherManager = new SearcherManager (directoryReader , new SearcherFactory () );
535+ lastCommittedSegmentInfos = readLastCommittedSegmentInfos ( internalSearcherManager , store );
536+ ExternalSearcherManager externalSearcherManager = new ExternalSearcherManager ( internalSearcherManager ,
537+ externalSearcherFactory );
469538 success = true ;
470- return searcherManager ;
539+ return externalSearcherManager ;
471540 } catch (IOException e ) {
472541 maybeFailEngine ("start" , e );
473542 try {
@@ -479,7 +548,7 @@ private SearcherManager createSearcherManager(SearcherFactory searcherFactory, b
479548 }
480549 } finally {
481550 if (success == false ) { // release everything we created on a failure
482- IOUtils .closeWhileHandlingException (searcherManager , indexWriter );
551+ IOUtils .closeWhileHandlingException (internalSearcherManager , indexWriter );
483552 }
484553 }
485554 }
@@ -1229,24 +1298,24 @@ public void refresh(String source) throws EngineException {
12291298 }
12301299
12311300 final void refresh (String source , SearcherScope scope ) throws EngineException {
1232- long bytes = 0 ;
12331301 // we obtain a read lock here, since we don't want a flush to happen while we are refreshing
12341302 // since it flushes the index as well (though, in terms of concurrency, we are allowed to do it)
1303+ // both refresh types will result in an internal refresh but only the external will also
1304+ // pass the new reader reference to the external reader manager.
1305+
1306+ // this will also cause version map ram to be freed hence we always account for it.
1307+ final long bytes = indexWriter .ramBytesUsed () + versionMap .ramBytesUsedForRefresh ();
1308+ writingBytes .addAndGet (bytes );
12351309 try (ReleasableLock lock = readLock .acquire ()) {
12361310 ensureOpen ();
1237- bytes = indexWriter .ramBytesUsed ();
12381311 switch (scope ) {
12391312 case EXTERNAL :
12401313 // even though we maintain 2 managers we really do the heavy-lifting only once.
12411314 // the second refresh will only do the extra work we have to do for warming caches etc.
1242- writingBytes .addAndGet (bytes );
12431315 externalSearcherManager .maybeRefreshBlocking ();
12441316 // the break here is intentional we never refresh both internal / external together
12451317 break ;
12461318 case INTERNAL :
1247- final long versionMapBytes = versionMap .ramBytesUsedForRefresh ();
1248- bytes += versionMapBytes ;
1249- writingBytes .addAndGet (bytes );
12501319 internalSearcherManager .maybeRefreshBlocking ();
12511320 break ;
12521321 default :
@@ -1709,7 +1778,7 @@ protected final void closeNoLock(String reason, CountDownLatch closedLatch) {
17091778 }
17101779
17111780 @ Override
1712- protected SearcherManager getSearcherManager (String source , SearcherScope scope ) {
1781+ protected ReferenceManager < IndexSearcher > getSearcherManager (String source , SearcherScope scope ) {
17131782 switch (scope ) {
17141783 case INTERNAL :
17151784 return internalSearcherManager ;
0 commit comments