5959import java .util .Arrays ;
6060import java .util .Collection ;
6161import java .util .Collections ;
62+ import java .util .HashMap ;
6263import java .util .HashSet ;
64+ import java .util .Map ;
6365import java .util .Optional ;
6466import java .util .Set ;
6567import 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