Skip to content

Commit 5a2b638

Browse files
committed
feat: GH-5715 persist LMDB value hash codes
1 parent 39a800d commit 5a2b638

12 files changed

Lines changed: 952 additions & 8 deletions

File tree

core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/ValueStore.java

Lines changed: 144 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,9 @@
5959
import java.util.Arrays;
6060
import java.util.Collection;
6161
import java.util.Collections;
62+
import java.util.HashMap;
6263
import java.util.HashSet;
64+
import java.util.Map;
6365
import java.util.Optional;
6466
import java.util.Set;
6567
import java.util.WeakHashMap;
@@ -200,6 +202,8 @@ class ValueStore extends AbstractValueFactory {
200202
*/
201203
private long nextId = 1;
202204
private boolean freeIdsAvailable;
205+
private ValueStoreHashFile hashFile;
206+
private final Map<Long, Integer> pendingHashUpdates = new HashMap<>();
203207

204208
private volatile long nextValueEvictionTime = 0;
205209

@@ -220,6 +224,7 @@ class ValueStore extends AbstractValueFactory {
220224
private Object[] previousNamespaceEntry;
221225

222226
private final long valueEvictionInterval;
227+
private final boolean valueHashCacheEnabled;
223228

224229
ValueStore(File dir, LmdbStoreConfig config) throws IOException {
225230
this.dir = dir;
@@ -228,6 +233,7 @@ class ValueStore extends AbstractValueFactory {
228233
this.autoGrow = config.getAutoGrow();
229234
this.mapSize = config.getValueDBSize();
230235
this.valueEvictionInterval = config.getValueEvictionInterval();
236+
this.valueHashCacheEnabled = config.getValueHashCacheEnabled();
231237
open();
232238

233239
int cacheSize = nextPowerOfTwo(config.getValueCacheSize());
@@ -278,6 +284,18 @@ class ValueStore extends AbstractValueFactory {
278284
commit();
279285
}
280286

287+
private void openHashFileQuietly() {
288+
hashFile = null;
289+
if (!valueHashCacheEnabled) {
290+
return;
291+
}
292+
try {
293+
hashFile = new ValueStoreHashFile(dir);
294+
} catch (IOException e) {
295+
logger.warn("Could not initialize LMDB hash cache", e);
296+
}
297+
}
298+
281299
private void logValues() throws IOException {
282300
readTransaction(env, (stack, txn) -> {
283301
long cursor = 0;
@@ -313,6 +331,7 @@ private void logValues() throws IOException {
313331
private void open() throws IOException {
314332
// create directory if it not exists
315333
dir.mkdirs();
334+
openHashFileQuietly();
316335

317336
try (MemoryStack stack = stackPush()) {
318337
PointerBuffer pp = stack.mallocPointer(1);
@@ -404,8 +423,10 @@ private long nextId(byte type) throws IOException {
404423
MDBVal keyData = MDBVal.calloc(stack);
405424
MDBVal valueData = MDBVal.calloc(stack);
406425
if (mdb_cursor_get(cursor, keyData, valueData, MDB_FIRST) == MDB_SUCCESS) {
426+
long freedId = data2id(keyData.mv_data());
427+
clearStoredHash(freedId);
407428
// remove lower 2 type bits
408-
long value = data2id(keyData.mv_data()) >> 2;
429+
long value = freedId >> 2;
409430
// delete entry
410431
E(mdb_cursor_del(cursor, 0));
411432
return value;
@@ -461,6 +482,51 @@ ValueStoreRevision getRevision() {
461482
return revision;
462483
}
463484

485+
int getStoredHash(long id) {
486+
Integer pendingHash;
487+
synchronized (pendingHashUpdates) {
488+
pendingHash = pendingHashUpdates.get(id);
489+
}
490+
if (pendingHash != null) {
491+
return pendingHash;
492+
}
493+
if (hashFile == null) {
494+
return 0;
495+
}
496+
try {
497+
return hashFile.get(id);
498+
} catch (IOException e) {
499+
resetHashFileQuietly("read", e);
500+
return 0;
501+
}
502+
}
503+
504+
void storeHash(long id, int hash) {
505+
if (id == LmdbValue.UNKNOWN_ID) {
506+
return;
507+
}
508+
if (writeTxn != 0) {
509+
synchronized (pendingHashUpdates) {
510+
pendingHashUpdates.put(id, hash);
511+
}
512+
return;
513+
}
514+
writeHashNow(id, hash);
515+
}
516+
517+
void clearStoredHash(long id) {
518+
if (id == LmdbValue.UNKNOWN_ID) {
519+
return;
520+
}
521+
if (writeTxn != 0) {
522+
synchronized (pendingHashUpdates) {
523+
pendingHashUpdates.put(id, 0);
524+
}
525+
return;
526+
}
527+
writeHashNow(id, 0);
528+
}
529+
464530
protected byte[] getData(long id) throws IOException {
465531
return readTransaction(env, (stack, txn) -> {
466532
MDBVal keyData = MDBVal.calloc(stack);
@@ -590,6 +656,7 @@ public boolean resolveValue(long id, LmdbValue value) {
590656
try {
591657
byte[] data = getData(id);
592658
if (data != null) {
659+
// System.out.println(id);
593660
data2value(id, data, value);
594661
cacheValue(id, value);
595662
return true;
@@ -963,6 +1030,7 @@ public long getId(Value value, boolean create) throws IOException {
9631030

9641031
valueIDCache.put(nv, id);
9651032
}
1033+
storeHashIfAbsent(id, value);
9661034
}
9671035

9681036
return id;
@@ -1174,6 +1242,7 @@ protected void freeUnusedIdsAndValues(MemoryStack stack, long txn, Set<Long> rev
11741242
E(mdb_put(txn, freeDbi, idVal, emptyVal, 0));
11751243
// delete id -> value association
11761244
E(mdb_del(txn, dbi, idVal, null));
1245+
clearStoredHash(data2id(idVal.mv_data().duplicate()));
11771246
// delete id and value from unused list
11781247
E(mdb_cursor_del(unusedIdsCursor, 0));
11791248

@@ -1228,6 +1297,7 @@ void endTransaction(boolean commit) throws IOException {
12281297
long stamp = revisionLock.writeLock();
12291298
try {
12301299
E(mdb_txn_commit(writeTxn));
1300+
flushPendingHashUpdates();
12311301
long revisionId = lazyRevision.getRevisionId();
12321302
cleaner.register(lazyRevision, () -> {
12331303
synchronized (unusedRevisionIds) {
@@ -1244,9 +1314,11 @@ void endTransaction(boolean commit) throws IOException {
12441314
}
12451315
} else {
12461316
E(mdb_txn_commit(writeTxn));
1317+
flushPendingHashUpdates();
12471318
}
12481319
} else {
12491320
mdb_txn_abort(writeTxn);
1321+
clearPendingHashUpdates();
12501322
}
12511323
writeTxn = 0;
12521324
invalidateRevisionOnCommit = false;
@@ -1298,6 +1370,7 @@ public void clear() throws IOException {
12981370

12991371
new File(dir, "data.mdb").delete();
13001372
new File(dir, "lock.mdb").delete();
1373+
ValueStoreHashFile.deleteIfPresent(dir);
13011374

13021375
clearCaches();
13031376
open();
@@ -1335,11 +1408,81 @@ private void closeReadTransactions() {
13351408
public void close() throws IOException {
13361409

13371410
if (env != 0) {
1411+
if (writeTxn == 0) {
1412+
flushPendingHashUpdates();
1413+
}
13381414
closeReadTransactions();
13391415
endTransaction(false);
13401416
mdb_env_close(env);
13411417
env = 0;
13421418
}
1419+
if (hashFile != null) {
1420+
try {
1421+
hashFile.close();
1422+
} catch (IOException e) {
1423+
logger.warn("Could not close LMDB hash cache", e);
1424+
}
1425+
hashFile = null;
1426+
}
1427+
}
1428+
1429+
private void storeHashIfAbsent(long id, Value value) {
1430+
if (getStoredHash(id) == 0) {
1431+
storeHash(id, value.hashCode());
1432+
}
1433+
}
1434+
1435+
private void writeHashNow(long id, int hash) {
1436+
if (hashFile == null) {
1437+
return;
1438+
}
1439+
try {
1440+
hashFile.put(id, hash);
1441+
} catch (IOException e) {
1442+
resetHashFileQuietly("write", e);
1443+
}
1444+
}
1445+
1446+
private void flushPendingHashUpdates() {
1447+
Map<Long, Integer> updates;
1448+
synchronized (pendingHashUpdates) {
1449+
if (pendingHashUpdates.isEmpty()) {
1450+
return;
1451+
}
1452+
updates = new HashMap<>(pendingHashUpdates);
1453+
pendingHashUpdates.clear();
1454+
}
1455+
1456+
for (Map.Entry<Long, Integer> entry : updates.entrySet()) {
1457+
writeHashNow(entry.getKey(), entry.getValue());
1458+
}
1459+
}
1460+
1461+
private void clearPendingHashUpdates() {
1462+
synchronized (pendingHashUpdates) {
1463+
pendingHashUpdates.clear();
1464+
}
1465+
}
1466+
1467+
private void resetHashFileQuietly(String operation, IOException cause) {
1468+
logger.warn("Resetting LMDB hash cache after {} failure", operation, cause);
1469+
clearPendingHashUpdates();
1470+
if (!valueHashCacheEnabled) {
1471+
hashFile = null;
1472+
return;
1473+
}
1474+
ValueStoreHashFile currentHashFile = hashFile;
1475+
hashFile = null;
1476+
try {
1477+
if (currentHashFile != null) {
1478+
currentHashFile.delete();
1479+
} else {
1480+
ValueStoreHashFile.deleteIfPresent(dir);
1481+
}
1482+
} catch (IOException deleteFailure) {
1483+
logger.warn("Could not delete LMDB hash cache", deleteFailure);
1484+
}
1485+
openHashFileQuietly();
13431486
}
13441487

13451488
private static final class ReadTxn {

0 commit comments

Comments
 (0)