15
15
import org .apache .lucene .document .Field ;
16
16
import org .apache .lucene .document .StringField ;
17
17
import org .apache .lucene .index .DirectoryReader ;
18
+ import org .apache .lucene .index .IndexReader ;
18
19
import org .apache .lucene .index .IndexWriter ;
19
20
import org .apache .lucene .index .IndexWriterConfig ;
20
21
import org .apache .lucene .index .LeafReaderContext ;
83
84
public class DocumentSubsetBitsetCacheTests extends ESTestCase {
84
85
85
86
private static final int FIELD_COUNT = 10 ;
87
+ // This value is based on the internal implementation details of lucene's FixedBitSet
88
+ // If the implementation changes, this can be safely updated to match the new ram usage for a single bitset
89
+ private static final long EXPECTED_BYTES_PER_BIT_SET = 56 ;
86
90
private ExecutorService singleThreadExecutor ;
87
91
88
92
@ Before
@@ -137,12 +141,8 @@ public void testNullEntriesAreNotCountedInMemoryUsage() throws Exception {
137
141
}
138
142
139
143
public void testCacheRespectsMemoryLimit () throws Exception {
140
- // This value is based on the internal implementation details of lucene's FixedBitSet
141
- // If the implementation changes, this can be safely updated to match the new ram usage for a single bitset
142
- final long expectedBytesPerBitSet = 56 ;
143
-
144
144
// Enough to hold exactly 2 bit-sets in the cache
145
- final long maxCacheBytes = expectedBytesPerBitSet * 2 ;
145
+ final long maxCacheBytes = EXPECTED_BYTES_PER_BIT_SET * 2 ;
146
146
final Settings settings = Settings .builder ()
147
147
.put (DocumentSubsetBitsetCache .CACHE_SIZE_SETTING .getKey (), maxCacheBytes + "b" )
148
148
.build ();
@@ -158,29 +158,29 @@ public void testCacheRespectsMemoryLimit() throws Exception {
158
158
final Query query = queryBuilder .toQuery (searchExecutionContext );
159
159
final BitSet bitSet = cache .getBitSet (query , leafContext );
160
160
assertThat (bitSet , notNullValue ());
161
- assertThat (bitSet .ramBytesUsed (), equalTo (expectedBytesPerBitSet ));
161
+ assertThat (bitSet .ramBytesUsed (), equalTo (EXPECTED_BYTES_PER_BIT_SET ));
162
162
163
163
// The first time through we have 1 entry, after that we have 2
164
164
final int expectedCount = i == 1 ? 1 : 2 ;
165
165
assertThat (cache .entryCount (), equalTo (expectedCount ));
166
- assertThat (cache .ramBytesUsed (), equalTo (expectedCount * expectedBytesPerBitSet ));
166
+ assertThat (cache .ramBytesUsed (), equalTo (expectedCount * EXPECTED_BYTES_PER_BIT_SET ));
167
167
168
168
// Older queries should get evicted, but the query from last iteration should still be cached
169
169
if (previousQuery != null ) {
170
170
assertThat (cache .getBitSet (previousQuery , leafContext ), sameInstance (previousBitSet ));
171
171
assertThat (cache .entryCount (), equalTo (expectedCount ));
172
- assertThat (cache .ramBytesUsed (), equalTo (expectedCount * expectedBytesPerBitSet ));
172
+ assertThat (cache .ramBytesUsed (), equalTo (expectedCount * EXPECTED_BYTES_PER_BIT_SET ));
173
173
}
174
174
previousQuery = query ;
175
175
previousBitSet = bitSet ;
176
176
177
177
assertThat (cache .getBitSet (queryBuilder .toQuery (searchExecutionContext ), leafContext ), sameInstance (bitSet ));
178
178
assertThat (cache .entryCount (), equalTo (expectedCount ));
179
- assertThat (cache .ramBytesUsed (), equalTo (expectedCount * expectedBytesPerBitSet ));
179
+ assertThat (cache .ramBytesUsed (), equalTo (expectedCount * EXPECTED_BYTES_PER_BIT_SET ));
180
180
}
181
181
182
182
assertThat (cache .entryCount (), equalTo (2 ));
183
- assertThat (cache .ramBytesUsed (), equalTo (2 * expectedBytesPerBitSet ));
183
+ assertThat (cache .ramBytesUsed (), equalTo (2 * EXPECTED_BYTES_PER_BIT_SET ));
184
184
185
185
cache .clear ("testing" );
186
186
@@ -190,12 +190,8 @@ public void testCacheRespectsMemoryLimit() throws Exception {
190
190
}
191
191
192
192
public void testLogWarningIfBitSetExceedsCacheSize () throws Exception {
193
- // This value is based on the internal implementation details of lucene's FixedBitSet
194
- // If the implementation changes, this can be safely updated to match the new ram usage for a single bitset
195
- final long expectedBytesPerBitSet = 56 ;
196
-
197
193
// Enough to hold less than 1 bit-sets in the cache
198
- final long maxCacheBytes = expectedBytesPerBitSet - expectedBytesPerBitSet / 3 ;
194
+ final long maxCacheBytes = EXPECTED_BYTES_PER_BIT_SET - EXPECTED_BYTES_PER_BIT_SET / 3 ;
199
195
final Settings settings = Settings .builder ()
200
196
.put (DocumentSubsetBitsetCache .CACHE_SIZE_SETTING .getKey (), maxCacheBytes + "b" )
201
197
.build ();
@@ -214,7 +210,7 @@ public void testLogWarningIfBitSetExceedsCacheSize() throws Exception {
214
210
cache .getClass ().getName (),
215
211
Level .WARN ,
216
212
"built a DLS BitSet that uses ["
217
- + expectedBytesPerBitSet
213
+ + EXPECTED_BYTES_PER_BIT_SET
218
214
+ "] bytes; the DLS BitSet cache has a maximum size of ["
219
215
+ maxCacheBytes
220
216
+ "] bytes; this object cannot be cached and will need to be rebuilt for each use;"
@@ -227,7 +223,7 @@ public void testLogWarningIfBitSetExceedsCacheSize() throws Exception {
227
223
final Query query = queryBuilder .toQuery (searchExecutionContext );
228
224
final BitSet bitSet = cache .getBitSet (query , leafContext );
229
225
assertThat (bitSet , notNullValue ());
230
- assertThat (bitSet .ramBytesUsed (), equalTo (expectedBytesPerBitSet ));
226
+ assertThat (bitSet .ramBytesUsed (), equalTo (EXPECTED_BYTES_PER_BIT_SET ));
231
227
});
232
228
233
229
mockAppender .assertAllExpectationsMatched ();
@@ -238,12 +234,8 @@ public void testLogWarningIfBitSetExceedsCacheSize() throws Exception {
238
234
}
239
235
240
236
public void testLogMessageIfCacheFull () throws Exception {
241
- // This value is based on the internal implementation details of lucene's FixedBitSet
242
- // If the implementation changes, this can be safely updated to match the new ram usage for a single bitset
243
- final long expectedBytesPerBitSet = 56 ;
244
-
245
237
// Enough to hold slightly more than 1 bit-sets in the cache
246
- final long maxCacheBytes = expectedBytesPerBitSet + expectedBytesPerBitSet / 3 ;
238
+ final long maxCacheBytes = EXPECTED_BYTES_PER_BIT_SET + EXPECTED_BYTES_PER_BIT_SET / 3 ;
247
239
final Settings settings = Settings .builder ()
248
240
.put (DocumentSubsetBitsetCache .CACHE_SIZE_SETTING .getKey (), maxCacheBytes + "b" )
249
241
.build ();
@@ -272,7 +264,7 @@ public void testLogMessageIfCacheFull() throws Exception {
272
264
final Query query = queryBuilder .toQuery (searchExecutionContext );
273
265
final BitSet bitSet = cache .getBitSet (query , leafContext );
274
266
assertThat (bitSet , notNullValue ());
275
- assertThat (bitSet .ramBytesUsed (), equalTo (expectedBytesPerBitSet ));
267
+ assertThat (bitSet .ramBytesUsed (), equalTo (EXPECTED_BYTES_PER_BIT_SET ));
276
268
}
277
269
});
278
270
@@ -311,12 +303,8 @@ public void testCacheRespectsAccessTimeExpiry() throws Exception {
311
303
}
312
304
313
305
public void testIndexLookupIsClearedWhenBitSetIsEvicted () throws Exception {
314
- // This value is based on the internal implementation details of lucene's FixedBitSet
315
- // If the implementation changes, this can be safely updated to match the new ram usage for a single bitset
316
- final long expectedBytesPerBitSet = 56 ;
317
-
318
306
// Enough to hold slightly more than 1 bit-set in the cache
319
- final long maxCacheBytes = expectedBytesPerBitSet + expectedBytesPerBitSet / 2 ;
307
+ final long maxCacheBytes = EXPECTED_BYTES_PER_BIT_SET + EXPECTED_BYTES_PER_BIT_SET / 2 ;
320
308
final Settings settings = Settings .builder ()
321
309
.put (DocumentSubsetBitsetCache .CACHE_SIZE_SETTING .getKey (), maxCacheBytes + "b" )
322
310
.build ();
@@ -360,16 +348,12 @@ public void testIndexLookupIsClearedWhenBitSetIsEvicted() throws Exception {
360
348
}
361
349
362
350
public void testCacheUnderConcurrentAccess () throws Exception {
363
- // This value is based on the internal implementation details of lucene's FixedBitSet
364
- // If the implementation changes, this can be safely updated to match the new ram usage for a single bitset
365
- final long expectedBytesPerBitSet = 56 ;
366
-
367
351
final int concurrentThreads = randomIntBetween (5 , 8 );
368
352
final int numberOfIndices = randomIntBetween (3 , 8 );
369
353
370
354
// Force cache evictions by setting the size to be less than the number of distinct queries we search on.
371
355
final int maxCacheCount = randomIntBetween (FIELD_COUNT / 2 , FIELD_COUNT * 3 / 4 );
372
- final long maxCacheBytes = expectedBytesPerBitSet * maxCacheCount ;
356
+ final long maxCacheBytes = EXPECTED_BYTES_PER_BIT_SET * maxCacheCount ;
373
357
final Settings settings = Settings .builder ()
374
358
.put (DocumentSubsetBitsetCache .CACHE_SIZE_SETTING .getKey (), maxCacheBytes + "b" )
375
359
.build ();
@@ -412,7 +396,7 @@ public void testCacheUnderConcurrentAccess() throws Exception {
412
396
final Query query = queryBuilder .toQuery (randomContext .searchExecutionContext );
413
397
final BitSet bitSet = cache .getBitSet (query , randomContext .leafReaderContext );
414
398
assertThat (bitSet , notNullValue ());
415
- assertThat (bitSet .ramBytesUsed (), equalTo (expectedBytesPerBitSet ));
399
+ assertThat (bitSet .ramBytesUsed (), equalTo (EXPECTED_BYTES_PER_BIT_SET ));
416
400
uniqueBitSets .add (bitSet );
417
401
}
418
402
}
@@ -446,6 +430,62 @@ public void testCacheUnderConcurrentAccess() throws Exception {
446
430
}
447
431
}
448
432
433
+ public void testCleanupWorksWhenIndexIsClosing () throws Exception {
434
+ // Enough to hold slightly more than 1 bit-set in the cache
435
+ final long maxCacheBytes = EXPECTED_BYTES_PER_BIT_SET + EXPECTED_BYTES_PER_BIT_SET / 2 ;
436
+ final Settings settings = Settings .builder ()
437
+ .put (DocumentSubsetBitsetCache .CACHE_SIZE_SETTING .getKey (), maxCacheBytes + "b" )
438
+ .build ();
439
+ final ExecutorService threads = Executors .newFixedThreadPool (1 );
440
+ final ExecutorService cleanupExecutor = Mockito .mock (ExecutorService .class );
441
+ final CountDownLatch cleanupReadyLatch = new CountDownLatch (1 );
442
+ final CountDownLatch cleanupCompleteLatch = new CountDownLatch (1 );
443
+ final CountDownLatch indexCloseLatch = new CountDownLatch (1 );
444
+ final AtomicReference <Throwable > cleanupException = new AtomicReference <>();
445
+ when (cleanupExecutor .submit (any (Runnable .class ))).thenAnswer (inv -> {
446
+ final Runnable runnable = (Runnable ) inv .getArguments ()[0 ];
447
+ return threads .submit (() -> {
448
+ try {
449
+ cleanupReadyLatch .countDown ();
450
+ assertTrue ("index close did not completed in expected time" , indexCloseLatch .await (1 , TimeUnit .SECONDS ));
451
+ runnable .run ();
452
+ } catch (Throwable e ) {
453
+ logger .warn ("caught error in cleanup thread" , e );
454
+ cleanupException .compareAndSet (null , e );
455
+ } finally {
456
+ cleanupCompleteLatch .countDown ();
457
+ }
458
+ return null ;
459
+ });
460
+ });
461
+
462
+ final DocumentSubsetBitsetCache cache = new DocumentSubsetBitsetCache (settings , cleanupExecutor );
463
+ assertThat (cache .entryCount (), equalTo (0 ));
464
+ assertThat (cache .ramBytesUsed (), equalTo (0L ));
465
+
466
+ try {
467
+ runTestOnIndex ((searchExecutionContext , leafContext ) -> {
468
+ final Query query1 = QueryBuilders .termQuery ("field-1" , "value-1" ).toQuery (searchExecutionContext );
469
+ final BitSet bitSet1 = cache .getBitSet (query1 , leafContext );
470
+ assertThat (bitSet1 , notNullValue ());
471
+
472
+ // Second query should trigger a cache eviction
473
+ final Query query2 = QueryBuilders .termQuery ("field-2" , "value-2" ).toQuery (searchExecutionContext );
474
+ final BitSet bitSet2 = cache .getBitSet (query2 , leafContext );
475
+ assertThat (bitSet2 , notNullValue ());
476
+
477
+ final IndexReader .CacheKey indexKey = leafContext .reader ().getCoreCacheHelper ().getKey ();
478
+ assertTrue ("cleanup did not trigger in expected time" , cleanupReadyLatch .await (1 , TimeUnit .SECONDS ));
479
+ cache .onClose (indexKey );
480
+ indexCloseLatch .countDown ();
481
+ assertTrue ("cleanup did not complete in expected time" , cleanupCompleteLatch .await (1 , TimeUnit .SECONDS ));
482
+ assertThat ("caught error in cleanup thread: " + cleanupException .get (), cleanupException .get (), nullValue ());
483
+ });
484
+ } finally {
485
+ threads .shutdown ();
486
+ }
487
+ }
488
+
449
489
public void testCacheIsPerIndex () throws Exception {
450
490
final DocumentSubsetBitsetCache cache = newCache (Settings .EMPTY );
451
491
assertThat (cache .entryCount (), equalTo (0 ));
0 commit comments