[TieredStorage] Optimize the storage size of HotAccountMeta from 16 bytes to 8 bytes#146
[TieredStorage] Optimize the storage size of HotAccountMeta from 16 bytes to 8 bytes#146yhchiang-sol wants to merge 4 commits intoanza-xyz:masterfrom
Conversation
1183eb5 to
5bebea7
Compare
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #146 +/- ##
=========================================
- Coverage 81.9% 81.9% -0.1%
=========================================
Files 836 836
Lines 226629 226728 +99
=========================================
+ Hits 185650 185726 +76
- Misses 40979 41002 +23 |
yhchiang-sol
left a comment
There was a problem hiding this comment.
Here're some tips to make the PR review easier:
| /// this fewer-than-u64 lamports info stores lamports that can fit | ||
| /// within its limitation, or a bit indicating the lamport is stored | ||
| /// separately as an optional field. | ||
| /// | ||
| /// Note that the number of bits using in this field must match | ||
| /// the const LAMPORTS_INFO_BITS. | ||
| pub lamports_info: B30, | ||
| } | ||
|
|
||
| /// The number of bits used in lamports_info field. | ||
| /// Note that this value must match the bits in AccountMetaFlags::lamports_info. | ||
| pub const LAMPORTS_INFO_BITS: u64 = 30; | ||
| /// The max lamports balance that the lamports_info field can handle. | ||
| /// Any lamports beyond this value will be stored separately in optional fields. | ||
| pub const LAMPORTS_INFO_MAX_BALANCE: u64 = | ||
| ((1u64 << LAMPORTS_INFO_BITS) - 1) - LAMPORTS_INFO_RESERVED_VALUES; | ||
|
|
||
| /// The number of special values inside lamports_info. | ||
| /// This const MUST be updated when adding new reserved values. | ||
| pub const LAMPORTS_INFO_RESERVED_VALUES: u64 = 2; | ||
|
|
||
| /// A reserved lamports_info value indicating zero-lamports balance. | ||
| pub const LAMPORTS_INFO_IS_ZERO_BALANCE: u32 = 0; | ||
| /// A reserved lamports_info value indicating the lamports balance is stored | ||
| /// in optional fields. | ||
| pub const LAMPORTS_INFO_HAS_OPTIONAL_FIELD: u32 = 1; |
There was a problem hiding this comment.
Here's a good place to start the PR review as it includes most of the high-level concepts.
There was a problem hiding this comment.
I think the LSB == 0 should be the case for when lamports are stored here inside the flags. This allows zero lamports to be a real value of 0. And if LSB == 1, then go to the full u64 storage.
So lamports can be 29 bits, and an explicit bool can be added to the struct. This should obviate the need for LAMPORTS_INFO_RESERVED_VALUES.
There was a problem hiding this comment.
nit: Can you also move these constants above the struct definition? For me I find it easier to read code where the struct impl is close to the definition. These constants make that distance larger currently.
There was a problem hiding this comment.
Talked with Jeff and he explained the idea for the 30 vs 29 bits. I had it wrong in my head before, and now I understand the idea. So please disregard my previous comment about stealing a bit.
There was a problem hiding this comment.
Sounds good. And I will move those constants above the struct definition.
| /// Whether the account has zero lamports. | ||
| fn has_zero_lamports(&self) -> bool; | ||
|
|
||
| /// Returns the balance of the lamports associated with the account | ||
| /// from the TieredAccountMeta, or None if the lamports is stored | ||
| /// inside the optional field. | ||
| fn lamports_from_meta(&self) -> Option<u64>; | ||
|
|
||
| /// Returns the balance of the lamports associated with the account | ||
| /// from the optional fields, or None if the lamports is stored | ||
| /// inside the TieredAccountMeta. | ||
| fn lamports_from_optional_fields(&self, _account_block: &[u8]) -> Option<u64>; |
There was a problem hiding this comment.
Here's the API change for the TieredStorageMeta, and TieredReadableAccount will use these APIs to implement its ReadableAccount::lamport() function.
There was a problem hiding this comment.
Hmm, feels like another case there the implementation details are leaking into the interface.
I think the TieredAccountMeta trait should stick with just lamports(), and let the HotPacked Meta implementation handle where to read from.
There was a problem hiding this comment.
I think the TieredAccountMeta trait should stick with just lamports(), and let the HotPacked Meta implementation handle where to read from.
It is because HotAccountMeta alone cannot provide the full picture because it does not possess the data.
One solution is to convert the current HotAccountMeta to HotAccountStoredMeta, and create a new HotAccountMeta that contains everything.
(Sounds like this would be a separate PR and this PR on top of it?)
There was a problem hiding this comment.
Now they belong to HotAccount.
| if let Some(lamports) = self.meta.lamports_from_meta() { | ||
| return lamports; | ||
| } | ||
|
|
||
| // If the meta does not have the lamports, then it must inside the optional | ||
| // fields. | ||
| self.meta | ||
| .lamports_from_optional_fields(self.account_block) | ||
| .unwrap() |
There was a problem hiding this comment.
And, here's the updated lamports() function. Note that as we currently have 30 bits, this PR currently doesn't use the concept of storing the minimum non-zero lamports in the footer. It will be in a separate PR.
|
I don't see a concept of 'min lamports within this storage'. |
|
This also seems like a breaking change. Do we have a version # we can increment to cause validators running previous versions to panic when they try to load the old format. We don't currently want to support mutation or backwards compatibility. |
It's not in this PR yet. Will do it in a separate PR.
Yes, it is a breaking change. For AccountMeta change, it will be a new AccountMetaFormat, and the code for old-format will still there. So do we want to introduce a new version here as the previous one isn't fully released to master? |
I think we should start this practice now. There is value in making it so we don't waste time debugging old formats. It will also make it clear when we make a breaking change. |
do you mean we'll still have a struct in the code with the old format? |
I think that's a good idea. Have updated the PR to bump the new version.
If a format has been fully released, then we need to have a struct in the code with the old format until the format has been fully deprecated. For this PR, I can add the old format back so that we can also practice it? Then another PR to make it deprecated? |
|
Offline discussed with @jeffwashington. The PR now uses the new version and keeps only the lastest version. |
brooksprumo
left a comment
There was a problem hiding this comment.
First pass done. I think we have some opportunities here to simplify the impl.
| #[error("UnsupportedAccountMetaFormat: format is deprecated or unsupported.")] | ||
| UnsupportedAccountMetaFormat(), |
There was a problem hiding this comment.
nit: I think we can remove "deprecated", and also the () on the variant.
| #[error("UnsupportedAccountMetaFormat: format is deprecated or unsupported.")] | |
| UnsupportedAccountMetaFormat(), | |
| #[error("UnsupportedAccountMetaFormat: format is unsupported.")] | |
| UnsupportedAccountMetaFormat, |
| pub enum AccountMetaFormat { | ||
| #[default] | ||
| Hot = 0, | ||
| HotPacked, |
There was a problem hiding this comment.
Anything written to disk must have an explicit value. Don't want to let an errant refactor break everything.
| HotPacked, | |
| HotPacked = 1, |
There was a problem hiding this comment.
Aghh, my bad. Should definitely assign a value.
| /// Returns the balance of the lamports associated with the account. | ||
| fn lamports(&self) -> u64; | ||
| /// Whether the account has zero lamports. | ||
| fn has_zero_lamports(&self) -> bool; |
There was a problem hiding this comment.
nit: There's already an existing trait that does something similar:
agave/accounts-db/src/accounts_index.rs
Lines 600 to 602 in 0f1ca20
We don't need to use that trait here, but can we at least use the same naming?
| fn has_zero_lamports(&self) -> bool; | |
| fn is_zero_lamport(&self) -> bool; |
There was a problem hiding this comment.
Neat! Will use ZeroLamport trait!
| /// Whether the account has zero lamports. | ||
| fn has_zero_lamports(&self) -> bool; | ||
|
|
||
| /// Returns the balance of the lamports associated with the account | ||
| /// from the TieredAccountMeta, or None if the lamports is stored | ||
| /// inside the optional field. | ||
| fn lamports_from_meta(&self) -> Option<u64>; | ||
|
|
||
| /// Returns the balance of the lamports associated with the account | ||
| /// from the optional fields, or None if the lamports is stored | ||
| /// inside the TieredAccountMeta. | ||
| fn lamports_from_optional_fields(&self, _account_block: &[u8]) -> Option<u64>; |
There was a problem hiding this comment.
Hmm, feels like another case there the implementation details are leaking into the interface.
I think the TieredAccountMeta trait should stick with just lamports(), and let the HotPacked Meta implementation handle where to read from.
| /// this fewer-than-u64 lamports info stores lamports that can fit | ||
| /// within its limitation, or a bit indicating the lamport is stored | ||
| /// separately as an optional field. | ||
| /// | ||
| /// Note that the number of bits using in this field must match | ||
| /// the const LAMPORTS_INFO_BITS. | ||
| pub lamports_info: B30, | ||
| } | ||
|
|
||
| /// The number of bits used in lamports_info field. | ||
| /// Note that this value must match the bits in AccountMetaFlags::lamports_info. | ||
| pub const LAMPORTS_INFO_BITS: u64 = 30; | ||
| /// The max lamports balance that the lamports_info field can handle. | ||
| /// Any lamports beyond this value will be stored separately in optional fields. | ||
| pub const LAMPORTS_INFO_MAX_BALANCE: u64 = | ||
| ((1u64 << LAMPORTS_INFO_BITS) - 1) - LAMPORTS_INFO_RESERVED_VALUES; | ||
|
|
||
| /// The number of special values inside lamports_info. | ||
| /// This const MUST be updated when adding new reserved values. | ||
| pub const LAMPORTS_INFO_RESERVED_VALUES: u64 = 2; | ||
|
|
||
| /// A reserved lamports_info value indicating zero-lamports balance. | ||
| pub const LAMPORTS_INFO_IS_ZERO_BALANCE: u32 = 0; | ||
| /// A reserved lamports_info value indicating the lamports balance is stored | ||
| /// in optional fields. | ||
| pub const LAMPORTS_INFO_HAS_OPTIONAL_FIELD: u32 = 1; |
There was a problem hiding this comment.
I think the LSB == 0 should be the case for when lamports are stored here inside the flags. This allows zero lamports to be a real value of 0. And if LSB == 1, then go to the full u64 storage.
So lamports can be 29 bits, and an explicit bool can be added to the struct. This should obviate the need for LAMPORTS_INFO_RESERVED_VALUES.
| /// this fewer-than-u64 lamports info stores lamports that can fit | ||
| /// within its limitation, or a bit indicating the lamport is stored | ||
| /// separately as an optional field. | ||
| /// | ||
| /// Note that the number of bits using in this field must match | ||
| /// the const LAMPORTS_INFO_BITS. | ||
| pub lamports_info: B30, | ||
| } | ||
|
|
||
| /// The number of bits used in lamports_info field. | ||
| /// Note that this value must match the bits in AccountMetaFlags::lamports_info. | ||
| pub const LAMPORTS_INFO_BITS: u64 = 30; | ||
| /// The max lamports balance that the lamports_info field can handle. | ||
| /// Any lamports beyond this value will be stored separately in optional fields. | ||
| pub const LAMPORTS_INFO_MAX_BALANCE: u64 = | ||
| ((1u64 << LAMPORTS_INFO_BITS) - 1) - LAMPORTS_INFO_RESERVED_VALUES; | ||
|
|
||
| /// The number of special values inside lamports_info. | ||
| /// This const MUST be updated when adding new reserved values. | ||
| pub const LAMPORTS_INFO_RESERVED_VALUES: u64 = 2; | ||
|
|
||
| /// A reserved lamports_info value indicating zero-lamports balance. | ||
| pub const LAMPORTS_INFO_IS_ZERO_BALANCE: u32 = 0; | ||
| /// A reserved lamports_info value indicating the lamports balance is stored | ||
| /// in optional fields. | ||
| pub const LAMPORTS_INFO_HAS_OPTIONAL_FIELD: u32 = 1; |
There was a problem hiding this comment.
nit: Can you also move these constants above the struct definition? For me I find it easier to read code where the struct impl is close to the definition. These constants make that distance larger currently.
|
|
||
| impl AccountMetaFlags { | ||
| pub fn new_from(optional_fields: &AccountMetaOptionalFields) -> Self { | ||
| pub fn new_from(optional_fields: &AccountMetaOptionalFields, lamports: u64) -> Self { |
There was a problem hiding this comment.
I haven't fully groked all the code yet, but this does seem strange to me that we take in two lamports here. One in the optional fields, and then another one here. Feels like an opportunity to refactor and make this simpler. (And also remove/simplify the other newly added methods.)
There was a problem hiding this comment.
Agree, I don't like the current approach either since I the meta, flags, and optional fields were created before we had some special packing for lamports. Let me think more about it.
3e0cd3b to
5f54148
Compare
#### Problem As we further optimize the HotStorageMeta in #146, there is a need for a HotAccount struct that contains all the hot account information. Meanwhile, we currently don't have plans to develop a cold account format at this moment. As a result, this makes it desirable to repurpose TieredReadableAccount to HotAccount. #### Summary of Changes Repurpose TieredReadableAccount to HotAccount. #### Test Plan Existing tiered-storage tests.
…rait. Have HotPacked enum value 1. Improve Error type.
5f54148 to
5a37959
Compare
|
Rebased on top of master. |
|
I've removed myself as a reviewer for now. Please re-add me once it's ready again. Thanks! |
…yz#218) #### Problem As we further optimize the HotStorageMeta in anza-xyz#146, there is a need for a HotAccount struct that contains all the hot account information. Meanwhile, we currently don't have plans to develop a cold account format at this moment. As a result, this makes it desirable to repurpose TieredReadableAccount to HotAccount. #### Summary of Changes Repurpose TieredReadableAccount to HotAccount. #### Test Plan Existing tiered-storage tests.
|
Closing this PR since it is more than 1 year old |
Problem
Lamports-balance requires a u64 to fully represent its value range.
However, most lamports-balance can fit in fewer bits (> 95% can fit in 25 bits).
This finding allows us to utilize the reserved bits inside AccountMetaFlags
to store lamports-balance, or as an optional field when it cannot be represented
using the remaining bits inside AccountMetaFlags.
Summary of Changes
This PR moves the lamports field from HotAccountMeta to AccountsMetaFlags.
For more than 95% of the accounts, this PR can save 8 bytes per account.
Test Plan
Added new tests to cover zero lamports, lamports inside meta, and lamports inside optional fields.
Update existing tests to cover all cases.