Skip to content

Commit 189b24f

Browse files
authored
Test index retention through GC events.
2 parents dcc13e3 + 0e9c778 commit 189b24f

File tree

2 files changed

+136
-0
lines changed

2 files changed

+136
-0
lines changed

engine/table/src/test/java/io/deephaven/engine/table/impl/QueryTableTest.java

+37
Original file line numberDiff line numberDiff line change
@@ -964,6 +964,43 @@ public void testStringContainsFilter() {
964964
}
965965
}
966966

967+
public void testIndexRetentionThroughGC() {
968+
final Table childTable;
969+
970+
// We don't need this liveness scope for liveness management, but rather to opt out of the enclosing scope's
971+
// enforceStrongReachability
972+
try (final SafeCloseable ignored = LivenessScopeStack.open()) {
973+
final Map<String, Object> retained = new HashMap<>();
974+
final Random random = new Random(0);
975+
final int size = 500;
976+
final QueryTable parentTable = getTable(false, size, random,
977+
initColumnInfos(new String[] {"S1", "S2"},
978+
new SetGenerator<>("aa", "bb", "cc", "dd", "AA", "BB", "CC", "DD"),
979+
new SetGenerator<>("aaa", "bbb", "ccc", "ddd", "AAA", "BBB", "CCC", "DDD")));
980+
981+
// Explicitly retain the index references.
982+
retained.put("di1", DataIndexer.getOrCreateDataIndex(parentTable, "S1"));
983+
retained.put("di2", DataIndexer.getOrCreateDataIndex(parentTable, "S2"));
984+
childTable = parentTable.update("isEven = ii % 2 == 0");
985+
986+
// While retained, the indexes will survive GC
987+
System.gc();
988+
989+
// While the references are held, the parent and child tables should have the indexes.
990+
Assert.assertTrue(DataIndexer.hasDataIndex(parentTable, "S1"));
991+
Assert.assertTrue(DataIndexer.hasDataIndex(parentTable, "S2"));
992+
Assert.assertTrue(DataIndexer.hasDataIndex(childTable, "S1"));
993+
Assert.assertTrue(DataIndexer.hasDataIndex(childTable, "S2"));
994+
995+
// Explicitly release the references.
996+
retained.clear();
997+
}
998+
// After a GC, the child table should not have the indexes.
999+
System.gc();
1000+
Assert.assertFalse(DataIndexer.hasDataIndex(childTable, "S1"));
1001+
Assert.assertFalse(DataIndexer.hasDataIndex(childTable, "S2"));
1002+
}
1003+
9671004
public void testStringMatchFilterIndexed() {
9681005
// MatchFilters (currently) only use indexes on initial creation but this incremental test will recreate
9691006
// index-enabled match filtered tables and compare them against incremental non-indexed filtered tables.

extensions/parquet/table/src/test/java/io/deephaven/parquet/table/ParquetTableReadWriteTest.java

+99
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import io.deephaven.base.FileUtils;
1111
import io.deephaven.base.verify.Assert;
1212
import io.deephaven.engine.context.ExecutionContext;
13+
import io.deephaven.engine.liveness.LivenessScopeStack;
1314
import io.deephaven.engine.primitive.function.ByteConsumer;
1415
import io.deephaven.engine.primitive.function.CharConsumer;
1516
import io.deephaven.engine.primitive.function.FloatConsumer;
@@ -58,6 +59,7 @@
5859
import io.deephaven.test.types.OutOfBandTest;
5960
import io.deephaven.time.DateTimeUtils;
6061
import io.deephaven.util.QueryConstants;
62+
import io.deephaven.util.SafeCloseable;
6163
import io.deephaven.util.codec.SimpleByteArrayCodec;
6264
import io.deephaven.util.compare.DoubleComparisons;
6365
import io.deephaven.util.compare.FloatComparisons;
@@ -88,6 +90,7 @@
8890
import java.math.BigInteger;
8991
import java.net.URI;
9092
import java.nio.file.Files;
93+
import java.nio.file.Path;
9194
import java.nio.file.StandardCopyOption;
9295
import java.time.Instant;
9396
import java.time.LocalDate;
@@ -337,6 +340,102 @@ public void vectorParquetFormat() {
337340
groupedTable("largeAggParquet", LARGE_TABLE_SIZE, false);
338341
}
339342

343+
@Test
344+
public void indexRetentionThroughGC() {
345+
final String destPath = Path.of(rootFile.getPath(), "ParquetTest_indexRetention_test").toString();
346+
final int tableSize = 10_000;
347+
final Table testTable = TableTools.emptyTable(tableSize).update(
348+
"symbol = randomInt(0,4)",
349+
"price = randomInt(0,10000) * 0.01",
350+
"str_id = `str_` + String.format(`%08d`, randomInt(0,1_000_000))",
351+
"indexed_val = ii % 10_000");
352+
final ParquetInstructions writeInstructions = ParquetInstructions.builder()
353+
.setGenerateMetadataFiles(true)
354+
.addIndexColumns("indexed_val")
355+
.build();
356+
final PartitionedTable partitionedTable = testTable.partitionBy("symbol");
357+
ParquetTools.writeKeyValuePartitionedTable(partitionedTable, destPath, writeInstructions);
358+
final Table child;
359+
360+
// We don't need this liveness scope for liveness management, but rather to opt out of the enclosing scope's
361+
// enforceStrongReachability
362+
try (final SafeCloseable ignored = LivenessScopeStack.open()) {
363+
// Read from disk and validate the indexes through GC.
364+
Table parent = ParquetTools.readTable(destPath);
365+
child = parent.update("new_val = indexed_val + 1")
366+
.update("new_val = new_val + 1")
367+
.update("new_val = new_val + 1")
368+
.update("new_val = new_val + 1");
369+
370+
// These indexes will survive GC because the parent table is holding strong references.
371+
System.gc();
372+
373+
// The parent table should have the indexes.
374+
Assert.eqTrue(DataIndexer.hasDataIndex(parent, "symbol"), "hasDataIndex -> symbol");
375+
Assert.eqTrue(DataIndexer.hasDataIndex(parent, "indexed_val"), "hasDataIndex -> indexed_val");
376+
377+
// The child table should have the indexes while the parent is retained.
378+
Assert.eqTrue(DataIndexer.hasDataIndex(child, "symbol"), "hasDataIndex -> symbol");
379+
Assert.eqTrue(DataIndexer.hasDataIndex(child, "indexed_val"), "hasDataIndex -> indexed_val");
380+
381+
// Force the parent to null to allow GC to collect it.
382+
parent = null;
383+
}
384+
385+
// After a GC, the child table should still have access to the indexes.
386+
System.gc();
387+
Assert.eqTrue(DataIndexer.hasDataIndex(child, "symbol"), "hasDataIndex -> symbol");
388+
Assert.eqTrue(DataIndexer.hasDataIndex(child, "indexed_val"), "hasDataIndex -> indexed_val");
389+
}
390+
391+
@Test
392+
public void remappedIndexRetentionThroughGC() {
393+
final String destPath =
394+
Path.of(rootFile.getPath(), "ParquetTest_remappedIndexRetention_test.parquet").toString();
395+
final int tableSize = 10_000;
396+
final Table testTable = TableTools.emptyTable(tableSize).update(
397+
"symbol = randomInt(0,4)",
398+
"price = randomInt(0,10000) * 0.01",
399+
"str_id = `str_` + String.format(`%08d`, randomInt(0,1_000_000))",
400+
"indexed_val = ii % 10_000");
401+
final ParquetInstructions writeInstructions = ParquetInstructions.builder()
402+
.setGenerateMetadataFiles(true)
403+
.addIndexColumns("symbol")
404+
.addIndexColumns("indexed_val")
405+
.build();
406+
ParquetTools.writeTable(testTable, destPath, writeInstructions);
407+
final Table child;
408+
409+
// We don't need this liveness scope for liveness management, but rather to opt out of the enclosing scope's
410+
// enforceStrongReachability
411+
try (final SafeCloseable ignored = LivenessScopeStack.open()) {
412+
// Read from disk and validate the indexes through GC.
413+
Table parent = ParquetTools.readTable(destPath);
414+
415+
// select() produces in-memory column sources, triggering the remapping of the indexes.
416+
child = parent.select();
417+
418+
// These indexes will survive GC because the parent table is holding strong references.
419+
System.gc();
420+
421+
// The parent table should have the indexes.
422+
Assert.eqTrue(DataIndexer.hasDataIndex(parent, "symbol"), "hasDataIndex -> symbol");
423+
Assert.eqTrue(DataIndexer.hasDataIndex(parent, "indexed_val"), "hasDataIndex -> indexed_val");
424+
425+
// The child table should have the indexes while the parent is retained.
426+
Assert.eqTrue(DataIndexer.hasDataIndex(child, "symbol"), "hasDataIndex -> symbol");
427+
Assert.eqTrue(DataIndexer.hasDataIndex(child, "indexed_val"), "hasDataIndex -> indexed_val");
428+
429+
// Force the parent to null to allow GC to collect it.
430+
parent = null;
431+
}
432+
433+
// After a GC, the child table should still have access to the indexes.
434+
System.gc();
435+
Assert.eqTrue(DataIndexer.hasDataIndex(child, "symbol"), "hasDataIndex -> symbol");
436+
Assert.eqTrue(DataIndexer.hasDataIndex(child, "indexed_val"), "hasDataIndex -> indexed_val");
437+
}
438+
340439
@Test
341440
public void indexByLongKey() {
342441
final TableDefinition definition = TableDefinition.of(

0 commit comments

Comments
 (0)