48
48
import org .elasticsearch .Version ;
49
49
import org .elasticsearch .action .index .IndexRequest ;
50
50
import org .elasticsearch .common .Nullable ;
51
+ import org .elasticsearch .common .SuppressForbidden ;
51
52
import org .elasticsearch .common .UUIDs ;
52
53
import org .elasticsearch .common .lease .Releasable ;
53
54
import org .elasticsearch .common .lucene .LoggerInfoStream ;
57
58
import org .elasticsearch .common .lucene .uid .VersionsAndSeqNoResolver ;
58
59
import org .elasticsearch .common .lucene .uid .VersionsAndSeqNoResolver .DocIdAndSeqNo ;
59
60
import org .elasticsearch .common .metrics .CounterMetric ;
60
- import org .elasticsearch .common .unit .ByteSizeValue ;
61
61
import org .elasticsearch .common .util .concurrent .AbstractRunnable ;
62
62
import org .elasticsearch .common .util .concurrent .KeyedLock ;
63
63
import org .elasticsearch .common .util .concurrent .ReleasableLock ;
@@ -108,7 +108,7 @@ public class InternalEngine extends Engine {
108
108
109
109
private final IndexWriter indexWriter ;
110
110
111
- private final SearcherManager externalSearcherManager ;
111
+ private final ExternalSearcherManager externalSearcherManager ;
112
112
private final SearcherManager internalSearcherManager ;
113
113
114
114
private final Lock flushLock = new ReentrantLock ();
@@ -172,7 +172,7 @@ public InternalEngine(EngineConfig engineConfig) {
172
172
store .incRef ();
173
173
IndexWriter writer = null ;
174
174
Translog translog = null ;
175
- SearcherManager externalSearcherManager = null ;
175
+ ExternalSearcherManager externalSearcherManager = null ;
176
176
SearcherManager internalSearcherManager = null ;
177
177
EngineMergeScheduler scheduler = null ;
178
178
boolean success = false ;
@@ -224,8 +224,8 @@ public InternalEngine(EngineConfig engineConfig) {
224
224
throw e ;
225
225
}
226
226
}
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 ;
229
229
this .internalSearcherManager = internalSearcherManager ;
230
230
this .externalSearcherManager = externalSearcherManager ;
231
231
internalSearcherManager .addListener (versionMap );
@@ -238,7 +238,7 @@ public InternalEngine(EngineConfig engineConfig) {
238
238
success = true ;
239
239
} finally {
240
240
if (success == false ) {
241
- IOUtils .closeWhileHandlingException (writer , translog , externalSearcherManager , internalSearcherManager , scheduler );
241
+ IOUtils .closeWhileHandlingException (writer , translog , internalSearcherManager , externalSearcherManager , scheduler );
242
242
if (isClosed .get () == false ) {
243
243
// failure we need to dec the store reference
244
244
store .decRef ();
@@ -248,6 +248,75 @@ public InternalEngine(EngineConfig engineConfig) {
248
248
logger .trace ("created new InternalEngine" );
249
249
}
250
250
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
+
251
320
@ Override
252
321
public void restoreLocalCheckpointFromTranslog () throws IOException {
253
322
try (ReleasableLock ignored = writeLock .acquire ()) {
@@ -456,18 +525,18 @@ private String loadOrGenerateHistoryUUID(final IndexWriter writer, boolean force
456
525
return uuid ;
457
526
}
458
527
459
- private SearcherManager createSearcherManager (SearcherFactory searcherFactory , boolean readSegmentsInfo ) throws EngineException {
528
+ private ExternalSearcherManager createSearcherManager (SearchFactory externalSearcherFactory ) throws EngineException {
460
529
boolean success = false ;
461
- SearcherManager searcherManager = null ;
530
+ SearcherManager internalSearcherManager = null ;
462
531
try {
463
532
try {
464
533
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 );
469
538
success = true ;
470
- return searcherManager ;
539
+ return externalSearcherManager ;
471
540
} catch (IOException e ) {
472
541
maybeFailEngine ("start" , e );
473
542
try {
@@ -479,7 +548,7 @@ private SearcherManager createSearcherManager(SearcherFactory searcherFactory, b
479
548
}
480
549
} finally {
481
550
if (success == false ) { // release everything we created on a failure
482
- IOUtils .closeWhileHandlingException (searcherManager , indexWriter );
551
+ IOUtils .closeWhileHandlingException (internalSearcherManager , indexWriter );
483
552
}
484
553
}
485
554
}
@@ -1229,24 +1298,24 @@ public void refresh(String source) throws EngineException {
1229
1298
}
1230
1299
1231
1300
final void refresh (String source , SearcherScope scope ) throws EngineException {
1232
- long bytes = 0 ;
1233
1301
// we obtain a read lock here, since we don't want a flush to happen while we are refreshing
1234
1302
// 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 );
1235
1309
try (ReleasableLock lock = readLock .acquire ()) {
1236
1310
ensureOpen ();
1237
- bytes = indexWriter .ramBytesUsed ();
1238
1311
switch (scope ) {
1239
1312
case EXTERNAL :
1240
1313
// even though we maintain 2 managers we really do the heavy-lifting only once.
1241
1314
// the second refresh will only do the extra work we have to do for warming caches etc.
1242
- writingBytes .addAndGet (bytes );
1243
1315
externalSearcherManager .maybeRefreshBlocking ();
1244
1316
// the break here is intentional we never refresh both internal / external together
1245
1317
break ;
1246
1318
case INTERNAL :
1247
- final long versionMapBytes = versionMap .ramBytesUsedForRefresh ();
1248
- bytes += versionMapBytes ;
1249
- writingBytes .addAndGet (bytes );
1250
1319
internalSearcherManager .maybeRefreshBlocking ();
1251
1320
break ;
1252
1321
default :
@@ -1709,7 +1778,7 @@ protected final void closeNoLock(String reason, CountDownLatch closedLatch) {
1709
1778
}
1710
1779
1711
1780
@ Override
1712
- protected SearcherManager getSearcherManager (String source , SearcherScope scope ) {
1781
+ protected ReferenceManager < IndexSearcher > getSearcherManager (String source , SearcherScope scope ) {
1713
1782
switch (scope ) {
1714
1783
case INTERNAL :
1715
1784
return internalSearcherManager ;
0 commit comments