Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf: improve CL KVStore entries #5205

Closed
wants to merge 11 commits into from
Closed

Conversation

czarcas7ic
Copy link
Member

@czarcas7ic czarcas7ic commented May 17, 2023

Closes: #4733

What is the purpose of the change

The current Owner | Pool ID | Position ID -> Position ID KVStore returns values like so:

addr1|1|1->1
addr1|1|2->2
addr1|2|3->3
addr2|1|4->4
addr2|1|5->5

This PR instead keys Owner | Pool ID -> list of Position IDs like so:

addr1|1|->[1, 2]
addr1|2|->[3]
addr2|1|->[4,5]

Similarly, this PR keys Pool ID -> list of Position IDs like so:

pool1->[1,3,5,6]
pool2->[2,4,7]

Testing and Verifying

  • DeletePosition and CreatePosition tests modified to expect positionIds returned from the KVStore
  • Osmoutils additions (SliceContains, RemoveFromSlice, BigEndianToUint64Slice, and Uint64SliceToBigEndian) have corresponding tests

Documentation and Release Note

  • Does this pull request introduce a new feature or user-facing behavior changes?
  • Changelog entry added to Unreleased section of CHANGELOG.md?

Where is the change documented?

  • Specification (x/{module}/README.md)
  • Osmosis documentation site
  • Code comments?
  • N/A

@p0mvn
Copy link
Member

p0mvn commented May 17, 2023

oh perfect! I was going to start working on this today for further perf improvements

@czarcas7ic
Copy link
Member Author

czarcas7ic commented May 17, 2023

Yeah, was not super hard to do and feels like it will save a ton of space :)

Still need to add tests for the new osmoutils funcs but should be R4R soon

@czarcas7ic czarcas7ic added no-changelog V:state/breaking State machine breaking PR and removed V:state/breaking State machine breaking PR no-changelog labels May 17, 2023
@czarcas7ic czarcas7ic changed the title refactor: improve CL KVStore entries perf: improve CL KVStore entries May 17, 2023
@github-actions github-actions bot added the C:simulator Edits simulator or simulations label May 17, 2023
@czarcas7ic czarcas7ic marked this pull request as ready for review May 17, 2023 20:43
@czarcas7ic czarcas7ic requested a review from p0mvn as a code owner May 17, 2023 20:43
Copy link
Member

@p0mvn p0mvn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm worried about doing this change at this stage of the project lifecycle.

When I commented first, I thought that this perf change was about removing Sprintf from keys. It's questionable whether there are even performance improvements since letting the storage search for keys is usually more efficient than doing it in the application layer. Additionally, getting all position IDs from store on set and delete is questionable.

Overall, the benefits of this change are negligible but the risk is high, making it a timeline risk.

For me to approve it, I would like to see a benchmark that this is making a worthwhile perf improvement

Comment on lines +99 to +107
positionIds := []uint64{}
addressPoolIdKey := types.KeyAddressAndPoolId(sender, poolId)
// Get the existing position IDs for the given address-pool ID.
positionIdsBytes := store.Get(addressPoolIdKey)
if len(positionIdsBytes) > 0 {
positionIds = osmoutils.BigEndianToUint64Slice(positionIdsBytes)
}
isOwner, err := osmoutils.HasAnyAtPrefix(ctx.KVStore(k.storeKey), types.KeyAddressPoolIdPositionId(sender, poolId, positionId), parse)
if err != nil {
return false, err
// Check if the given positionId is in the list of existing position IDs.
if osmoutils.SliceContains(positionIds, positionId) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At first, I was slightly worried about this approach. However, on another look seems fine

Assume someone has a million positions in pool. Then,

8 * 1000000 / 1024 / 1024 = 7.6 MB in memory 

Since we are processing messages sequentially it's unlikely this will cause a problem

Comment on lines -157 to +181
positionIds, err := osmoutils.GatherValuesFromStorePrefix(ctx.KVStore(k.storeKey), prefix, ParsePositionIdFromBz)
positionIds, err := osmoutils.GatherValuesFromStorePrefix(ctx.KVStore(k.storeKey), prefix, ParsePositionIdsFromBz)
if err != nil {
return nil, err
}

// Retrieve each position from the store using its ID and add it to the result slice.
for _, positionId := range positionIds {
position, err := k.GetPosition(ctx, positionId)
if err != nil {
return nil, err
for _, poolPositionIds := range positionIds {
for _, positionId := range poolPositionIds {
position, err := k.GetPosition(ctx, positionId)
if err != nil {
return nil, err
}
positions = append(positions, position)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I understand what's happening here but it's quite unclear why we need to iterate over positionIds and then poolPositionIds. Can you please update this to make it more readable?

@czarcas7ic
Copy link
Member Author

@p0mvn I think its less of a performance change and more of a storage change. The current keys feel silly / redundant. We also have informal starting their audit tomorrow, so this would be a part of the scope. IMO I think if we don't get this in now we will never get it in, and we will have a bunch of silly redundant state that is not needed.

}

// RemoveFromSlice removes a uint64 value from a slice and returns the new slice.
// It returns an error if the value is not found in the slice.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// It returns an error if the value is not found in the slice.
// It returns an error if the value is not found in the slice.
// The computation cost of this method is O(n) as we linearly iterate over given slice

@@ -93,25 +93,37 @@ func (k Keeper) HasAnyPositionForPool(ctx sdk.Context, poolId uint64) (bool, err
}

// isPositionOwner returns true if the given positionId is owned by the given sender inside the given pool.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// isPositionOwner returns true if the given positionId is owned by the given sender inside the given pool.
// isPositionOwner returns true if the given positionId is owned by the given sender inside the given pool.
// This method also returns false if the given position is not found from state.

@@ -154,18 +166,20 @@ func (k Keeper) GetUserPositions(ctx sdk.Context, addr sdk.AccAddress, poolId ui
positions := []model.Position{}

// Gather all position IDs for the given user and pool ID.
positionIds, err := osmoutils.GatherValuesFromStorePrefix(ctx.KVStore(k.storeKey), prefix, ParsePositionIdFromBz)
positionIds, err := osmoutils.GatherValuesFromStorePrefix(ctx.KVStore(k.storeKey), prefix, ParsePositionIdsFromBz)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps a variable rename might make this less confusing, perhaps using poolPositionIds here

if len(positionIdsBytes) > 0 {
positionIds = osmoutils.BigEndianToUint64Slice(positionIdsBytes)
}
// Add the new position ID to the existing position IDs only if it doesn't exist already.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the case that the position ID we are trying to add is already existing?

if len(positionIdsBytes) > 0 {
positionIds = osmoutils.BigEndianToUint64Slice(positionIdsBytes)
}
// Add the new position ID to the existing position IDs only if it doesn't exist already.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto here

Comment on lines 220 to +221
// Set the address-pool-position ID to position mapping.
addressPoolIdPositionIdKey := types.KeyAddressPoolIdPositionId(owner, poolId, positionId)
store.Set(addressPoolIdPositionIdKey, sdk.Uint64ToBigEndian(positionId))
positionIds := []uint64{}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think go doc for this method needs to be updated accordingly

@mattverse
Copy link
Member

mattverse commented May 18, 2023

@p0mvn I think its less of a performance change and more of a storage change. The current keys feel silly / redundant. We also have informal starting their audit tomorrow, so this would be a part of the scope. IMO I think if we don't get this in now we will never get it in, and we will have a bunch of silly redundant state that is not needed.

Sorry, I think I might be lacking context. What or how does this improve existing way of storing state? At a brief glance, this PR seems more like style change more than storage efficiency change, I'm pretty sure I am missing some details or links in understanding this change

@czarcas7ic czarcas7ic marked this pull request as draft May 19, 2023 16:26
@czarcas7ic
Copy link
Member Author

As discussed in call, closing this in favor of #5237

Thanks for all the reviews, was still helpful in getting to our final result :)

@czarcas7ic czarcas7ic closed this May 19, 2023
@czarcas7ic czarcas7ic deleted the adam/cl-kvstore-perf branch August 7, 2023 23:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A:no-changelog C:simulator Edits simulator or simulations C:x/concentrated-liquidity V:state/breaking State machine breaking PR
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[CL]: Improve KVStore Entry
3 participants