[execution] Improve caching in accessors + simplify code#958
[execution] Improve caching in accessors + simplify code#958dmtrskv wants to merge 2 commits intoblock-accessorfrom
Conversation
22aee9a to
216db6c
Compare
nil/internal/db/accessors.go
Outdated
| func GetFromShard(tx RoTx, shardId types.ShardId, table ShardedTableName, key prettyKey) ([]byte, error) { | ||
| data, err := tx.GetFromShard(shardId, table, key.Bytes()) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("%w: key=%s", err, key) |
There was a problem hiding this comment.
Perhaps shardId we also want to add to the error message?
nil/internal/db/badger.go
Outdated
| item, err := tx.tx.Get(MakeKey(tableName, key)) | ||
| if errors.Is(err, badger.ErrKeyNotFound) { | ||
| return nil, ErrKeyNotFound | ||
| return nil, fmt.Errorf("%w: table %s", ErrKeyNotFound, tableName) |
There was a problem hiding this comment.
We have quite a few checks for errors.Is(err, db.ErrKeyNotFound). I think we might break some logic with such changes. If we want to add context to ErrrKeyNotFound, perhaps we should make it a full type and change the errors.Is checks to errors.As. Also, it might be better to make such changes as a separate PR.
There was a problem hiding this comment.
Removed it here and added this to the Gets in accessors.
Wrapping errors this way doesn't affect errors.Is.
Moved these changes to another PR: #969
| outTxCountsLRU *lru.Cache[common.Hash, [][]byte] | ||
| receiptsLRU *lru.Cache[common.Hash, [][]byte] | ||
| } | ||
| func newRawAccessorCache(blocksLRUSize, txnRLUSize int) *cache { |
There was a problem hiding this comment.
| func newRawAccessorCache(blocksLRUSize, txnRLUSize int) *cache { | |
| func newRawAccessorCache(blocksLRUSize, txnLRUSize int) *cache { |
| b.withDbTimestamp = true | ||
| return b | ||
| } | ||
| res, err := db.ReadBlockBytes(s.tx, s.shardId, hash) |
There was a problem hiding this comment.
We could also check s.cache.blocksLRU
| return nil, err | ||
| } | ||
| sa.cache.blocksLRU.Add(hash, block) | ||
| return &types.RawBlockWithExtractedData{ |
There was a problem hiding this comment.
I don't really like this use of a type without filling in some of the fields. If Go were a normal language, we should use variant here, but since we have what we have, let's keep some form of rawBlockAccessorResult to represent the mandatory + optional part of the data fields.
There was a problem hiding this comment.
I based it on the usages, but they actually could be improved/simplified. So, did that.
| check.PanicIfErr(err) | ||
| type cache struct { | ||
| blocksLRU *lru.Cache[common.Hash, *types.Block] | ||
| fullBlocksLRU *lru.Cache[common.Hash, *types.RawBlockWithExtractedData] |
There was a problem hiding this comment.
Have you considered a more normalized way of storing data, where the ExtractedData part is stored separately, and rawBlocksLRU is used for both raw headers and full blocks?
There was a problem hiding this comment.
Then I'll have to introduce ExtractedData, etc. Having an extra link to a block header is not a problem.
Instead, I merged raw and decoded headers, since they are unlikely to be used one without the other (it can only happen with the raw ones and only in a single rpc method).
| withDbTimestamp bool | ||
| withConfig bool | ||
| } | ||
| func (s RawShardAccessor) decodeBlock(hash common.Hash, data []byte) (*types.Block, error) { |
There was a problem hiding this comment.
For some reason I really want to see in the function name the information that it is caching.
| func (s RawShardAccessor) decodeBlock(hash common.Hash, data []byte) (*types.Block, error) { | |
| func (s RawShardAccessor) decodeBlockCached(hash common.Hash, data []byte) (*types.Block, error) { |
Probably because otherwise it's very easy to think from the name that it's pure, and make incorrect assumptions about behavior by reading the call locations.
There was a problem hiding this comment.
Agree, but removed this method altogether.
| if len(errMsg) > 0 { | ||
| result.Errors[txnHash] = errMsg | ||
| if len(errMsg) > 0 { | ||
| if result.Errors == nil { |
There was a problem hiding this comment.
How about building a temporary dictionary, and assigning it to result.Errors at the end if it's not empty? Or conversely, assigning nil to result.Errors if it's empty. By the way, why do we even want to maintain such an invariant?
There was a problem hiding this comment.
I thought it would be nice not to touch the result, unless there are actual errors.
| s.checkReceipt(shardId, m) | ||
| }) | ||
|
|
||
| s.Run("CheckRefundsSeqno", func() { |
There was a problem hiding this comment.
Why did we delete this test?
There was a problem hiding this comment.
It doesn't have anything to do with the proposer functionality.
216db6c to
f41374e
Compare
f41374e to
2bfd100
Compare
2bfd100 to
ba582e6
Compare
ba582e6 to
bc76ef8
Compare
bc76ef8 to
0ffafe2
Compare
0ffafe2 to
200bbca
Compare
200bbca to
528bc74
Compare
528bc74 to
0f8e20c
Compare
0f8e20c to
d4b066d
Compare
d4b066d to
bf0abe8
Compare
bf0abe8 to
dffdbbb
Compare
| blockHashByNumber *BlockHashByNumberAccessor | ||
| } | ||
|
|
||
| func NewStateAccessor(blockLRUSize, txnLRUSize int) *StateAccessor { |
There was a problem hiding this comment.
btw if you wanna improve cache it may make sense to implement slightly different algo than simple LRU
https://en.wikipedia.org/wiki/Adaptive_replacement_cache - this one seems to be quite efficient
https://github.com/alexanderGugel/arc - and here's some implementation in go
State accessor appears in profiles as an active creator of objects in memory.
Here I simplify the code of the accessor tailoring it to the current needs. The caching is reworked and improved (it wasn't happening at some places at all).
There are also some simplifications in the other code, especially improvements in the usage of accessors.