don't rewrite hash->slot map (#2463)

Avoid writing the same slot/hash values to the hash->slot mapping
to avoid spamming the rocksdb WAL and cause unnecessary compaction

In the same vein, avoid writing trivially detectable A-B-A storage
changes which happen with surprising frequency.
This commit is contained in:
Jacek Sieka 2024-07-09 17:25:43 +02:00 committed by GitHub
parent 9a499eb45f
commit ab23148aab
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 46 additions and 28 deletions

View File

@ -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

View File

@ -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