-
Notifications
You must be signed in to change notification settings - Fork 607
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
Conversation
oh perfect! I was going to start working on this today for further perf improvements |
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 |
There was a problem hiding this 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
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) { |
There was a problem hiding this comment.
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
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) |
There was a problem hiding this comment.
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?
@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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// 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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// 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) |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ditto here
// Set the address-pool-position ID to position mapping. | ||
addressPoolIdPositionIdKey := types.KeyAddressPoolIdPositionId(owner, poolId, positionId) | ||
store.Set(addressPoolIdPositionIdKey, sdk.Uint64ToBigEndian(positionId)) | ||
positionIds := []uint64{} |
There was a problem hiding this comment.
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
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 |
As discussed in call, closing this in favor of #5237 Thanks for all the reviews, was still helpful in getting to our final result :) |
Closes: #4733
What is the purpose of the change
The current
Owner | Pool ID | Position ID -> Position ID
KVStore returns values like so:This PR instead keys
Owner | Pool ID -> list of Position IDs
like so:Similarly, this PR keys
Pool ID -> list of Position IDs
like so:Testing and Verifying
Documentation and Release Note
Unreleased
section ofCHANGELOG.md
?Where is the change documented?
x/{module}/README.md
)