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 ;
@@ -225,10 +225,9 @@ public InternalEngine(EngineConfig engineConfig) {
225
225
throw e ;
226
226
}
227
227
}
228
-
229
228
this .translog = translog ;
230
- internalSearcherManager = createSearcherManager (new SearcherFactory (), false );
231
- externalSearcherManager = createSearcherManager ( new SearchFactory ( logger , isClosed , engineConfig ), true ) ;
229
+ externalSearcherManager = createSearcherManager (new SearchFactory ( logger , isClosed , engineConfig ) );
230
+ internalSearcherManager = externalSearcherManager . internalSearcherManager ;
232
231
this .internalSearcherManager = internalSearcherManager ;
233
232
this .externalSearcherManager = externalSearcherManager ;
234
233
internalSearcherManager .addListener (versionMap );
@@ -241,7 +240,7 @@ public InternalEngine(EngineConfig engineConfig) {
241
240
success = true ;
242
241
} finally {
243
242
if (success == false ) {
244
- IOUtils .closeWhileHandlingException (writer , translog , externalSearcherManager , internalSearcherManager , scheduler );
243
+ IOUtils .closeWhileHandlingException (writer , translog , internalSearcherManager , externalSearcherManager , scheduler );
245
244
if (isClosed .get () == false ) {
246
245
// failure we need to dec the store reference
247
246
store .decRef ();
@@ -251,6 +250,75 @@ public InternalEngine(EngineConfig engineConfig) {
251
250
logger .trace ("created new InternalEngine" );
252
251
}
253
252
253
+ /**
254
+ * This reference manager delegates all it's refresh calls to another (internal) SearcherManager
255
+ * The main purpose for this is that if we have external refreshes happening we don't issue extra
256
+ * refreshes to clear version map memory etc. this can cause excessive segment creation if heavy indexing
257
+ * is happening and the refresh interval is low (ie. 1 sec)
258
+ *
259
+ * This also prevents segment starvation where an internal reader holds on to old segments literally forever
260
+ * since no indexing is happening and refreshes are only happening to the external reader manager, while with
261
+ * this specialized implementation an external refresh will immediately be reflected on the internal reader
262
+ * and old segments can be released in the same way previous version did this (as a side-effect of _refresh)
263
+ */
264
+ @ SuppressForbidden (reason = "reference counting is required here" )
265
+ private static final class ExternalSearcherManager extends ReferenceManager <IndexSearcher > {
266
+ private final SearcherFactory searcherFactory ;
267
+ private final SearcherManager internalSearcherManager ;
268
+
269
+ ExternalSearcherManager (SearcherManager internalSearcherManager , SearcherFactory searcherFactory ) throws IOException {
270
+ IndexSearcher acquire = internalSearcherManager .acquire ();
271
+ try {
272
+ IndexReader indexReader = acquire .getIndexReader ();
273
+ assert indexReader instanceof ElasticsearchDirectoryReader :
274
+ "searcher's IndexReader should be an ElasticsearchDirectoryReader, but got " + indexReader ;
275
+ indexReader .incRef (); // steal the reader - getSearcher will decrement if it fails
276
+ current = SearcherManager .getSearcher (searcherFactory , indexReader , null );
277
+ } finally {
278
+ internalSearcherManager .release (acquire );
279
+ }
280
+ this .searcherFactory = searcherFactory ;
281
+ this .internalSearcherManager = internalSearcherManager ;
282
+ }
283
+
284
+ @ Override
285
+ protected IndexSearcher refreshIfNeeded (IndexSearcher referenceToRefresh ) throws IOException {
286
+ // we simply run a blocking refresh on the internal reference manager and then steal it's reader
287
+ // it's a save operation since we acquire the reader which incs it's reference but then down the road
288
+ // steal it by calling incRef on the "stolen" reader
289
+ internalSearcherManager .maybeRefreshBlocking ();
290
+ IndexSearcher acquire = internalSearcherManager .acquire ();
291
+ final IndexReader previousReader = referenceToRefresh .getIndexReader ();
292
+ assert previousReader instanceof ElasticsearchDirectoryReader :
293
+ "searcher's IndexReader should be an ElasticsearchDirectoryReader, but got " + previousReader ;
294
+ try {
295
+ final IndexReader newReader = acquire .getIndexReader ();
296
+ if (newReader == previousReader ) {
297
+ // nothing has changed - both ref managers share the same instance so we can use reference equality
298
+ return null ;
299
+ } else {
300
+ newReader .incRef (); // steal the reader - getSearcher will decrement if it fails
301
+ return SearcherManager .getSearcher (searcherFactory , newReader , previousReader );
302
+ }
303
+ } finally {
304
+ internalSearcherManager .release (acquire );
305
+ }
306
+ }
307
+
308
+ @ Override
309
+ protected boolean tryIncRef (IndexSearcher reference ) {
310
+ return reference .getIndexReader ().tryIncRef ();
311
+ }
312
+
313
+ @ Override
314
+ protected int getRefCount (IndexSearcher reference ) {
315
+ return reference .getIndexReader ().getRefCount ();
316
+ }
317
+
318
+ @ Override
319
+ protected void decRef (IndexSearcher reference ) throws IOException { reference .getIndexReader ().decRef (); }
320
+ }
321
+
254
322
@ Override
255
323
public void restoreLocalCheckpointFromTranslog () throws IOException {
256
324
try (ReleasableLock ignored = writeLock .acquire ()) {
@@ -469,18 +537,18 @@ private String loadOrGenerateHistoryUUID(final IndexWriter writer, boolean force
469
537
return uuid ;
470
538
}
471
539
472
- private SearcherManager createSearcherManager (SearcherFactory searcherFactory , boolean readSegmentsInfo ) throws EngineException {
540
+ private ExternalSearcherManager createSearcherManager (SearchFactory externalSearcherFactory ) throws EngineException {
473
541
boolean success = false ;
474
- SearcherManager searcherManager = null ;
542
+ SearcherManager internalSearcherManager = null ;
475
543
try {
476
544
try {
477
545
final DirectoryReader directoryReader = ElasticsearchDirectoryReader .wrap (DirectoryReader .open (indexWriter ), shardId );
478
- searcherManager = new SearcherManager (directoryReader , searcherFactory );
479
- if ( readSegmentsInfo ) {
480
- lastCommittedSegmentInfos = readLastCommittedSegmentInfos ( searcherManager , store );
481
- }
546
+ internalSearcherManager = new SearcherManager (directoryReader , new SearcherFactory () );
547
+ lastCommittedSegmentInfos = readLastCommittedSegmentInfos ( internalSearcherManager , store );
548
+ ExternalSearcherManager externalSearcherManager = new ExternalSearcherManager ( internalSearcherManager ,
549
+ externalSearcherFactory );
482
550
success = true ;
483
- return searcherManager ;
551
+ return externalSearcherManager ;
484
552
} catch (IOException e ) {
485
553
maybeFailEngine ("start" , e );
486
554
try {
@@ -492,7 +560,7 @@ private SearcherManager createSearcherManager(SearcherFactory searcherFactory, b
492
560
}
493
561
} finally {
494
562
if (success == false ) { // release everything we created on a failure
495
- IOUtils .closeWhileHandlingException (searcherManager , indexWriter );
563
+ IOUtils .closeWhileHandlingException (internalSearcherManager , indexWriter );
496
564
}
497
565
}
498
566
}
@@ -1242,24 +1310,24 @@ public void refresh(String source) throws EngineException {
1242
1310
}
1243
1311
1244
1312
final void refresh (String source , SearcherScope scope ) throws EngineException {
1245
- long bytes = 0 ;
1246
1313
// we obtain a read lock here, since we don't want a flush to happen while we are refreshing
1247
1314
// since it flushes the index as well (though, in terms of concurrency, we are allowed to do it)
1315
+ // both refresh types will result in an internal refresh but only the external will also
1316
+ // pass the new reader reference to the external reader manager.
1317
+
1318
+ // this will also cause version map ram to be freed hence we always account for it.
1319
+ final long bytes = indexWriter .ramBytesUsed () + versionMap .ramBytesUsedForRefresh ();
1320
+ writingBytes .addAndGet (bytes );
1248
1321
try (ReleasableLock lock = readLock .acquire ()) {
1249
1322
ensureOpen ();
1250
- bytes = indexWriter .ramBytesUsed ();
1251
1323
switch (scope ) {
1252
1324
case EXTERNAL :
1253
1325
// even though we maintain 2 managers we really do the heavy-lifting only once.
1254
1326
// the second refresh will only do the extra work we have to do for warming caches etc.
1255
- writingBytes .addAndGet (bytes );
1256
1327
externalSearcherManager .maybeRefreshBlocking ();
1257
1328
// the break here is intentional we never refresh both internal / external together
1258
1329
break ;
1259
1330
case INTERNAL :
1260
- final long versionMapBytes = versionMap .ramBytesUsedForRefresh ();
1261
- bytes += versionMapBytes ;
1262
- writingBytes .addAndGet (bytes );
1263
1331
internalSearcherManager .maybeRefreshBlocking ();
1264
1332
break ;
1265
1333
default :
@@ -1722,7 +1790,7 @@ protected final void closeNoLock(String reason, CountDownLatch closedLatch) {
1722
1790
}
1723
1791
1724
1792
@ Override
1725
- protected SearcherManager getSearcherManager (String source , SearcherScope scope ) {
1793
+ protected ReferenceManager < IndexSearcher > getSearcherManager (String source , SearcherScope scope ) {
1726
1794
switch (scope ) {
1727
1795
case INTERNAL :
1728
1796
return internalSearcherManager ;
0 commit comments