Skip to content

[TieredStorage] Optimize the storage size of HotAccountMeta from 16 bytes to 8 bytes#146

Closed
yhchiang-sol wants to merge 4 commits intoanza-xyz:masterfrom
yhchiang-sol:ts-pack-hot-meta
Closed

[TieredStorage] Optimize the storage size of HotAccountMeta from 16 bytes to 8 bytes#146
yhchiang-sol wants to merge 4 commits intoanza-xyz:masterfrom
yhchiang-sol:ts-pack-hot-meta

Conversation

@yhchiang-sol
Copy link
Copy Markdown

@yhchiang-sol yhchiang-sol commented Mar 8, 2024

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.

@yhchiang-sol yhchiang-sol force-pushed the ts-pack-hot-meta branch 3 times, most recently from 1183eb5 to 5bebea7 Compare March 8, 2024 06:13
@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented Mar 8, 2024

Codecov Report

Attention: Patch coverage is 99.40120% with 1 lines in your changes are missing coverage. Please review.

Project coverage is 81.9%. Comparing base (69b6d5a) to head (5a37959).

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     

Copy link
Copy Markdown
Author

@yhchiang-sol yhchiang-sol left a comment

Choose a reason for hiding this comment

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

Here're some tips to make the PR review easier:

Comment thread accounts-db/src/tiered_storage/meta.rs Outdated
Comment on lines +19 to +26
/// 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;
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Here's a good place to start the PR review as it includes most of the high-level concepts.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Sounds good. And I will move those constants above the struct definition.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Done

Comment thread accounts-db/src/tiered_storage/meta.rs Outdated
Comment on lines +70 to +78
/// 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>;
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Here's the API change for the TieredStorageMeta, and TieredReadableAccount will use these APIs to implement its ReadableAccount::lamport() function.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

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?)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Now they belong to HotAccount.

Comment on lines +58 to +66
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()
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

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.

@yhchiang-sol yhchiang-sol requested a review from brooksprumo March 8, 2024 09:38
@jeffwashington
Copy link
Copy Markdown

I don't see a concept of 'min lamports within this storage'.

@jeffwashington
Copy link
Copy Markdown

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.

@yhchiang-sol
Copy link
Copy Markdown
Author

I don't see a concept of 'min lamports within this storage'.

It's not in this PR yet. Will do it in a separate PR.

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.

Yes, it is a breaking change. For AccountMeta change, it will be a new AccountMetaFormat, and the code for old-format will still there.

 #[repr(u16)]
 #[derive(
     Clone,
     Copy,
     Debug,
     Default,
     Eq,
     Hash,
     PartialEq,
     num_enum::IntoPrimitive,
     num_enum::TryFromPrimitive,
 )]
 pub enum AccountMetaFormat {
     #[default]
     Hot = 0,
     HotPacked = 1,  // if we want to introduce a new one, then we will introduce a new format here.
 }

So do we want to introduce a new version here as the previous one isn't fully released to master?

@jeffwashington
Copy link
Copy Markdown

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.

@jeffwashington
Copy link
Copy Markdown

and the code for old-format will still there.

do you mean we'll still have a struct in the code with the old format?

@yhchiang-sol
Copy link
Copy Markdown
Author

yhchiang-sol commented Mar 8, 2024

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.

I think that's a good idea. Have updated the PR to bump the new version.

do you mean we'll still have a struct in the code with the old format?

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?

@yhchiang-sol
Copy link
Copy Markdown
Author

Offline discussed with @jeffwashington. The PR now uses the new version and keeps only the lastest version.

@yhchiang-sol yhchiang-sol changed the title [TieredStorage] Move lamports to AccountsMetaFlags [TieredStorage] Optimize the storage size of HotAccountMeta from 16 bytes to 8 bytes Mar 11, 2024
Copy link
Copy Markdown

@brooksprumo brooksprumo left a comment

Choose a reason for hiding this comment

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

First pass done. I think we have some opportunities here to simplify the impl.

Comment thread accounts-db/src/tiered_storage/error.rs Outdated
Comment on lines +35 to +36
#[error("UnsupportedAccountMetaFormat: format is deprecated or unsupported.")]
UnsupportedAccountMetaFormat(),
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

nit: I think we can remove "deprecated", and also the () on the variant.

Suggested change
#[error("UnsupportedAccountMetaFormat: format is deprecated or unsupported.")]
UnsupportedAccountMetaFormat(),
#[error("UnsupportedAccountMetaFormat: format is unsupported.")]
UnsupportedAccountMetaFormat,

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Done

pub enum AccountMetaFormat {
#[default]
Hot = 0,
HotPacked,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Anything written to disk must have an explicit value. Don't want to let an errant refactor break everything.

Suggested change
HotPacked,
HotPacked = 1,

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Aghh, my bad. Should definitely assign a value.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Done

Comment thread accounts-db/src/tiered_storage/meta.rs Outdated
/// 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;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

nit: There's already an existing trait that does something similar:

pub trait ZeroLamport {
fn is_zero_lamport(&self) -> bool;
}

We don't need to use that trait here, but can we at least use the same naming?

Suggested change
fn has_zero_lamports(&self) -> bool;
fn is_zero_lamport(&self) -> bool;

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Neat! Will use ZeroLamport trait!

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Done

Comment thread accounts-db/src/tiered_storage/meta.rs Outdated
Comment on lines +70 to +78
/// 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>;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

Comment thread accounts-db/src/tiered_storage/meta.rs Outdated
Comment on lines +19 to +26
/// 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;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

Comment thread accounts-db/src/tiered_storage/meta.rs Outdated
Comment on lines +19 to +26
/// 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;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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 {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

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.

@yhchiang-sol yhchiang-sol force-pushed the ts-pack-hot-meta branch 2 times, most recently from 3e0cd3b to 5f54148 Compare March 13, 2024 04:14
yhchiang-sol added a commit that referenced this pull request Mar 13, 2024
#### 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.
@yhchiang-sol
Copy link
Copy Markdown
Author

Rebased on top of master.

@yhchiang-sol yhchiang-sol marked this pull request as draft March 20, 2024 08:34
@brooksprumo brooksprumo removed their request for review March 22, 2024 17:37
@brooksprumo
Copy link
Copy Markdown

I've removed myself as a reviewer for now. Please re-add me once it's ready again. Thanks!

@mergify mergify Bot added the community label Mar 31, 2024
codebender828 pushed a commit to codebender828/agave that referenced this pull request Oct 3, 2024
…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.
@steviez
Copy link
Copy Markdown

steviez commented Apr 9, 2025

Closing this PR since it is more than 1 year old

@steviez steviez closed this Apr 9, 2025
OliverNChalk pushed a commit to OliverNChalk/agave that referenced this pull request Nov 11, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants