Skip to content

Commit 7b1f96e

Browse files
authored
fix: 18571: Current path range should be respected when path to hash and path to KV indices are restored (#18592)
Fixes: #18571 Reviewed-by: Anthony Petrov <[email protected]>, Ivan Malygin <[email protected]> Signed-off-by: Artem Ananev <[email protected]>
1 parent 6e3b5ed commit 7b1f96e

File tree

2 files changed

+92
-45
lines changed

2 files changed

+92
-45
lines changed

platform-sdk/swirlds-merkledb/src/main/java/com/swirlds/merkledb/MerkleDbDataSource.java

+14-5
Original file line numberDiff line numberDiff line change
@@ -287,15 +287,19 @@ public MerkleDbDataSource(
287287
hasDiskStoreForHashes = tableConfig.getHashesRamToDiskThreshold() < Long.MAX_VALUE;
288288
final DataFileCompactor hashStoreDiskFileCompactor;
289289
if (hasDiskStoreForHashes) {
290-
final boolean hashIndexEmpty = pathToDiskLocationInternalNodes.size() == 0;
290+
final boolean needRestorePathToDiskLocationInternalNodes = pathToDiskLocationInternalNodes.size() == 0;
291291
final LoadedDataCallback hashRecordLoadedCallback;
292-
if (hashIndexEmpty) {
292+
if (needRestorePathToDiskLocationInternalNodes) {
293293
if (validLeafPathRange.getMaxValidKey() >= 0) {
294294
pathToDiskLocationInternalNodes.updateValidRange(0, validLeafPathRange.getMaxValidKey());
295295
}
296296
hashRecordLoadedCallback = (dataLocation, hashData) -> {
297297
final VirtualHashRecord hashRecord = VirtualHashRecord.parseFrom(hashData);
298-
pathToDiskLocationInternalNodes.put(hashRecord.path(), dataLocation);
298+
final long path = hashRecord.path();
299+
// Old data files may contain entries with paths outside the current virtual node range
300+
if (path <= validLeafPathRange.getMaxValidKey()) {
301+
pathToDiskLocationInternalNodes.put(path, dataLocation);
302+
}
299303
};
300304
} else {
301305
hashRecordLoadedCallback = null;
@@ -344,15 +348,20 @@ public MerkleDbDataSource(
344348
keyToPath.printStats();
345349

346350
final LoadedDataCallback leafRecordLoadedCallback;
347-
final boolean needRestorePathToDiskLocationLeafNodes = pathToDiskLocationLeafNodes.size() == 0;
351+
final boolean needRestorePathToDiskLocationLeafNodes =
352+
(pathToDiskLocationLeafNodes.size() == 0) && (validLeafPathRange.getMinValidKey() > 0);
348353
if (needRestorePathToDiskLocationLeafNodes) {
349354
if (validLeafPathRange.getMaxValidKey() >= 0) {
350355
pathToDiskLocationLeafNodes.updateValidRange(
351356
validLeafPathRange.getMinValidKey(), validLeafPathRange.getMaxValidKey());
352357
}
353358
leafRecordLoadedCallback = (dataLocation, leafData) -> {
354359
final VirtualLeafBytes leafBytes = VirtualLeafBytes.parseFrom(leafData);
355-
pathToDiskLocationLeafNodes.put(leafBytes.path(), dataLocation);
360+
final long path = leafBytes.path();
361+
// Old data files may contain entries with paths outside the current leaf range
362+
if (validLeafPathRange.withinRange(path)) {
363+
pathToDiskLocationLeafNodes.put(path, dataLocation);
364+
}
356365
};
357366
} else {
358367
leafRecordLoadedCallback = null;

platform-sdk/swirlds-merkledb/src/timingSensitive/java/com/swirlds/merkledb/MerkleDbDataSourceTest.java

+78-40
Original file line numberDiff line numberDiff line change
@@ -479,45 +479,65 @@ void snapshotRestoreIndex(final TestType testType) throws IOException {
479479
final Path originalDbPath = testDirectory.resolve("merkledb-snapshotRestoreIndex-" + testType);
480480
final KeySerializer keySerializer = testType.dataType().getKeySerializer();
481481
final ValueSerializer valueSerializer = testType.dataType().getValueSerializer();
482-
createAndApplyDataSource(originalDbPath, tableName, testType, count, 0, dataSource -> {
483-
final int tableId = dataSource.getTableId();
484-
// create some leaves
485-
dataSource.saveRecords(
486-
count - 1,
487-
count * 2 - 2,
488-
IntStream.range(0, count * 2 - 1).mapToObj(i -> createVirtualInternalRecord(i, i + 1)),
489-
IntStream.range(count - 1, count * 2 - 1)
490-
.mapToObj(i -> testType.dataType().createVirtualLeafRecord(i))
491-
.map(r -> r.toBytes(keySerializer, valueSerializer)),
492-
Stream.empty());
493-
// create a snapshot
494-
final Path snapshotDbPath =
495-
testDirectory.resolve("merkledb-snapshotRestoreIndex-" + testType + "_SNAPSHOT");
496-
dataSource.getDatabase().snapshot(snapshotDbPath, dataSource);
497-
// close data source
498-
dataSource.close();
499-
500-
final MerkleDb snapshotDb = MerkleDb.getInstance(snapshotDbPath, CONFIGURATION);
501-
final MerkleDbPaths snapshotPaths = new MerkleDbPaths(snapshotDb.getTableDir(tableName, tableId));
502-
// Delete all indices
503-
Files.delete(snapshotPaths.pathToDiskLocationLeafNodesFile);
504-
Files.delete(snapshotPaths.pathToDiskLocationInternalNodesFile);
505-
// There is no way to use MerkleDbPaths to get bucket index file path
506-
Files.deleteIfExists(snapshotPaths.keyToPathDirectory.resolve(tableName + "_bucket_index.ll"));
507-
508-
final MerkleDbDataSource snapshotDataSource = snapshotDb.getDataSource(tableName, false);
509-
reinitializeDirectMemoryUsage();
510-
IntStream.range(0, count * 2 - 1).forEach(i -> assertHash(snapshotDataSource, i, i + 1));
511-
IntStream.range(count - 1, count * 2 - 1)
512-
.forEach(i ->
513-
assertLeaf(testType, keySerializer, valueSerializer, snapshotDataSource, i, i, i + 1, i));
514-
// close data source
515-
snapshotDataSource.close();
516-
517-
// check db count
518-
assertEventuallyEquals(
519-
0L, MerkleDbDataSource::getCountOfOpenDatabases, Duration.ofSeconds(1), "Expected no open dbs");
520-
});
482+
final int[] deltas = {-10, 0, 10};
483+
for (int delta : deltas) {
484+
createAndApplyDataSource(originalDbPath, tableName, testType, count + Math.abs(delta), 0, dataSource -> {
485+
final int tableId = dataSource.getTableId();
486+
// create some records
487+
dataSource.saveRecords(
488+
count - 1,
489+
count * 2 - 2,
490+
IntStream.range(0, count * 2 - 1).mapToObj(i -> createVirtualInternalRecord(i, i + 1)),
491+
IntStream.range(count - 1, count * 2 - 1)
492+
.mapToObj(i -> testType.dataType().createVirtualLeafRecord(i))
493+
.map(r -> r.toBytes(keySerializer, valueSerializer)),
494+
Stream.empty());
495+
if (delta != 0) {
496+
// create some more, current leaf path range shifted by delta
497+
dataSource.saveRecords(
498+
count - 1 + delta,
499+
count * 2 - 2 + 2 * delta,
500+
IntStream.range(0, count * 2 - 1 + 2 * delta)
501+
.mapToObj(i -> createVirtualInternalRecord(i, i + 1)),
502+
IntStream.range(count - 1 + delta, count * 2 - 1 + 2 * delta)
503+
.mapToObj(i -> testType.dataType().createVirtualLeafRecord(i))
504+
.map(r -> r.toBytes(keySerializer, valueSerializer)),
505+
Stream.empty());
506+
}
507+
// create a snapshot
508+
final Path snapshotDbPath =
509+
testDirectory.resolve("merkledb-snapshotRestoreIndex-" + testType + "_SNAPSHOT");
510+
dataSource.getDatabase().snapshot(snapshotDbPath, dataSource);
511+
// close data source
512+
dataSource.close();
513+
514+
final MerkleDb snapshotDb = MerkleDb.getInstance(snapshotDbPath, CONFIGURATION);
515+
final MerkleDbPaths snapshotPaths = new MerkleDbPaths(snapshotDb.getTableDir(tableName, tableId));
516+
// Delete all indices
517+
Files.delete(snapshotPaths.pathToDiskLocationLeafNodesFile);
518+
Files.delete(snapshotPaths.pathToDiskLocationInternalNodesFile);
519+
// There is no way to use MerkleDbPaths to get bucket index file path
520+
Files.deleteIfExists(snapshotPaths.keyToPathDirectory.resolve(tableName + "_bucket_index.ll"));
521+
522+
final MerkleDbDataSource snapshotDataSource = snapshotDb.getDataSource(tableName, false);
523+
reinitializeDirectMemoryUsage();
524+
// Check hashes
525+
IntStream.range(0, count * 2 - 1 + 2 * delta).forEach(i -> assertHash(snapshotDataSource, i, i + 1));
526+
assertNullHash(snapshotDataSource, count * 2 + 2 * delta);
527+
// Check leaves
528+
IntStream.range(0, count - 2 + delta).forEach(i -> assertNullLeaf(snapshotDataSource, i));
529+
IntStream.range(count - 1 + delta, count * 2 - 1 + 2 * delta)
530+
.forEach(i -> assertLeaf(
531+
testType, keySerializer, valueSerializer, snapshotDataSource, i, i, i + 1, i));
532+
assertNullLeaf(snapshotDataSource, count * 2 + 2 * delta);
533+
// close data source
534+
snapshotDataSource.close();
535+
536+
// check db count
537+
assertEventuallyEquals(
538+
0L, MerkleDbDataSource::getCountOfOpenDatabases, Duration.ofSeconds(1), "Expected no open dbs");
539+
});
540+
}
521541
}
522542

523543
@Test
@@ -789,7 +809,16 @@ public static void assertHash(final MerkleDbDataSource dataSource, final long pa
789809
try {
790810
assertEqualsAndPrint(hash(i), dataSource.loadHash(path));
791811
} catch (final Exception e) {
792-
e.printStackTrace();
812+
e.printStackTrace(System.err);
813+
fail("Exception should not have been thrown here!");
814+
}
815+
}
816+
817+
public static void assertNullHash(final MerkleDbDataSource dataSource, final long path) {
818+
try {
819+
assertNull(dataSource.loadHash(path));
820+
} catch (final Exception e) {
821+
e.printStackTrace(System.err);
793822
fail("Exception should not have been thrown here!");
794823
}
795824
}
@@ -830,6 +859,15 @@ public static void assertLeaf(
830859
}
831860
}
832861

862+
public static void assertNullLeaf(final MerkleDbDataSource dataSource, final long path) {
863+
try {
864+
assertNull(dataSource.loadLeafRecord(path));
865+
} catch (final Exception e) {
866+
e.printStackTrace(System.err);
867+
fail("Exception should not have been thrown here!");
868+
}
869+
}
870+
833871
public static <T> void assertEqualsAndPrint(final T recordA, final T recordB) {
834872
assertEquals(
835873
recordA == null ? null : recordA.toString(),

0 commit comments

Comments
 (0)