diff --git a/nimbus/db/ledger/accounts_ledger.nim b/nimbus/db/ledger/accounts_ledger.nim index 08510cd05..3327d583e 100644 --- a/nimbus/db/ledger/accounts_ledger.nim +++ b/nimbus/db/ledger/accounts_ledger.nim @@ -34,6 +34,7 @@ const # which would cause a worst case of 386MB memory usage though in reality # code sizes are much smaller - it would make sense to study these numbers # in greater detail. + slotsLruSize = 16 * 1024 type AccountFlag = enum @@ -80,6 +81,11 @@ type ## when underpriced code opcodes are being run en masse - both advantages ## help performance broadly as well. + slots: KeyedQueue[UInt256, Hash256] + ## Because the same slots often reappear, we want to avoid writing them + ## over and over again to the database to avoid the WAL and compation + ## write amplification that ensues + ReadOnlyStateDB* = distinct AccountsLedgerRef TransactionState = enum @@ -302,7 +308,8 @@ proc originalStorageValue( # Not in the original values cache - go to the DB. let - slotKey = slot.toBytesBE.keccakHash + slotKey = ac.slots.lruFetch(slot).valueOr: + slot.toBytesBE.keccakHash rc = ac.ledger.slotFetch(acc.toAccountKey, slotKey) if rc.isOk: result = rc.value @@ -368,29 +375,39 @@ proc persistStorage(acc: AccountRef, ac: AccountsLedgerRef) = # Save `overlayStorage[]` on database for slot, value in acc.overlayStorage: - let slotKey = slot.toBytesBE.keccakHash + acc.originalStorage[].withValue(slot, v): + if v[] == value: + continue # Avoid writing A-B-A updates + + var cached = true + let slotKey = ac.slots.lruFetch(slot).valueOr: + cached = false + ac.slots.lruAppend(slot, slot.toBytesBE.keccakHash, slotsLruSize) + if value > 0: ac.ledger.slotMerge(acc.toAccountKey, slotKey, value).isOkOr: raiseAssert info & $$error + + # move the overlayStorage to originalStorage, related to EIP2200, EIP1283 + acc.originalStorage[slot] = value + else: ac.ledger.slotDelete(acc.toAccountKey, slotKey).isOkOr: if error.error != StoNotFound: raiseAssert info & $$error discard - let - key = slot.toBytesBE.keccakHash.data.slotHashToSlotKey - rc = ac.kvt.put(key.toOpenArray, blobify(slot).data) - if rc.isErr: - warn logTxt "persistStorage()", slot, error=($$rc.error) - - # move the overlayStorage to originalStorage, related to EIP2200, EIP1283 - for slot, value in acc.overlayStorage: - if value > 0: - acc.originalStorage[slot] = value - else: acc.originalStorage.del(slot) - acc.overlayStorage.clear() + if not cached: + # Write only if it was not cached to avoid writing the same data over and + # over.. + let + key = slotKey.data.slotHashToSlotKey + rc = ac.kvt.put(key.toOpenArray, blobify(slot).data) + if rc.isErr: + warn logTxt "persistStorage()", slot, error=($$rc.error) + + acc.overlayStorage.clear() proc makeDirty(ac: AccountsLedgerRef, address: EthAddress, cloneStorage = true): AccountRef = ac.isDirty = true diff --git a/nimbus/db/storage_types.nim b/nimbus/db/storage_types.nim index c0e808bcf..eaae9e441 100644 --- a/nimbus/db/storage_types.nim +++ b/nimbus/db/storage_types.nim @@ -13,20 +13,21 @@ import type DBKeyKind* = enum - genericHash - blockNumberToHash - blockHashToScore - transactionHashToBlock - canonicalHeadHash - slotHashToSlot - contractHash - transitionStatus - safeHash - finalizedHash - skeletonProgress - skeletonBlockHashToNumber - skeletonHeader - skeletonBody + # Careful - changing the assigned ordinals will break existing databases + genericHash = 0 + blockNumberToHash = 1 + blockHashToScore = 2 + transactionHashToBlock = 3 + canonicalHeadHash = 4 + slotHashToSlot = 5 + contractHash = 6 + transitionStatus = 7 + safeHash = 8 + finalizedHash = 9 + skeletonProgress = 10 + skeletonBlockHashToNumber = 11 + skeletonHeader = 12 + skeletonBody = 13 DbKey* = object # The first byte stores the key type. The rest are key-specific values