-
Notifications
You must be signed in to change notification settings - Fork 627
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
feat(resharding) - Introducing ShardLayoutV2 and nighshade layout v4 #12066
Conversation
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #12066 +/- ##
==========================================
+ Coverage 71.59% 71.60% +0.01%
==========================================
Files 823 824 +1
Lines 165137 165348 +211
Branches 165137 165348 +211
==========================================
+ Hits 118223 118400 +177
- Misses 41789 41819 +30
- Partials 5125 5129 +4
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. |
core/primitives/src/shard_layout.rs
Outdated
/// A mapping from the shard id to the account range of this shard. | ||
shards_account_range: ShardsAccountRange, |
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.
Why not to leave boundary_accounts: Vec<AccountId>
and just add new field shard_ids: Vec<ShardId>
? Then we would have to validate only that boundary_accounts.len() + 1 == shard_ids.len()
and that boundary_accounts
is in ascending order. After that, we can construct immediately valid ShardsAccountRange
for local usage. This would simplify validation in general.
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.
Yeah, this can be a fine and more simplistic strategy, but I don't feel too strongly about it vs the current implementation.
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 either would work but I feel like shard id => range mapping is easier for humans to parse. I'm also considering changing it to range => shard id as that would make the account_id_to_shard_id
query faster.
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 agree with human readability argument. But I think readability should be handled by some tool over the shard layout format, like we already do for the latest snapshot of runtime config. I prefer the base primitive to be as concise as possible. For me it's like getting rpc response in json or text - both make sense, but json must be the base format.
If we go with ranges, start
and end
are valid accounts so we need some placeholders which are definitely invalid ($start
?)
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.
All right, I'm fine with that, will do tomorrow.
If we go with ranges, start and end are valid accounts so we need some placeholders which are definitely invalid ($start?)
I wouldn't want to use magic values but perhaps I can use Unbounded for those.
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.
Done and it actually simplified a lot of things, thanks!
The account ranges are no longer needed so I didn't need to use the std ranges.
core/primitives/src/shard_layout.rs
Outdated
let prev = values[i - 1]; | ||
let curr = values[i]; | ||
if prev.end != curr.start { | ||
return Err(err("account ranges should be contiguous")); |
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.
nit: can probably also include the offending account value in the error message?
/// A mapping from the shard id to the account range of this shard. | ||
shards_account_range: ShardsAccountRange, | ||
|
||
/// A mapping from the parent shard to child shards. Maps shards from the |
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 wondering if it makes sense to have this struct contain all historic mappings instead of just the latest split?
That would be quite useful for store layer where we may recursively need to find parent shard_ids
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.
Maybe but let's keep it simple for now and we can enhance it later as needed.
shards_parent_map: Option<ShardsParentMapV2>, | ||
|
||
/// Version of the shard layout, this is useful for uniquely identify the shard layout | ||
version: ShardVersion, |
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 hope with LayoutV2, we would finally increment version by 1?
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.
There are a few options here:
- bump the version and do a migration (since state and flat state are keyed by shard uid)
- bump the version and add an override on storage layer access, along the lines of
if version >= 3 { version = 3 }
- do not bump the version
The third one doesn't require any extra changes and for that reason it's my favourite. We can just stick to version 3 until we need to make a real change and then we can figure it out. WDYT?
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.
no bump ➕
core/primitives/src/shard_layout.rs
Outdated
/// A mapping from the shard id to the account range of this shard. | ||
shards_account_range: ShardsAccountRange, |
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.
Yeah, this can be a fine and more simplistic strategy, but I don't feel too strongly about it vs the current implementation.
core/primitives/src/shard_layout.rs
Outdated
fn test_shard_layout_v4() { | ||
// Test ShardsAccountRange validation | ||
|
||
// Test account id to shard id |
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.
Is this intentionally left empty?
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.
No, I forgot to add this test, thanks!
We might want to do sth with
|
core/primitives/src/shard_layout.rs
Outdated
|reason: &str| ShardLayoutError::InvalidShardsAccountRange { reason: reason.to_string() }; | ||
|
||
let values = shards_account_range.values().sorted().collect_vec(); | ||
println!("{:?}", values); |
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.
is this meant to be a log message?
core/primitives/src/shard_layout.rs
Outdated
Self { start: AccountBoundary::Middle(start.parse().unwrap()), end: AccountBoundary::End } | ||
} | ||
|
||
pub fn new_mid(start: &str, end: &str) -> Self { |
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.
why is &str
preferred to &AccountId
?
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.
Technically it should be AccountId but it's just so much more conveninent to
pass &str because then I just need to do .parse().unwrap() in one place. I'm
happy to do it proper way and add some convenience functions just for the tests,
please let me now if you think it's worth it.
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 inclined to think it's worth it, just because all these methods are public, but I don't have a strong preference.
Reviewers, can you have another look please? The most notable change is that the mapping is now from account range to shard id (used to be the other way around). It will allows us to optimize the account id to shard mapping in the future. Also addressed the comments and did some cleanups. |
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.
🦀 🚀
core/primitives/src/shard_layout.rs
Outdated
// In this shard layout the accounts are divided into ranges, each range is | ||
// mapped to a shard. The shards are contiguous and start from 0. | ||
fn account_id_to_shard_id(&self, account_id: &AccountId) -> ShardId { | ||
// Note: As we scale up the number of shards we can consider |
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.
nit: note is outdated, in a certain sense
I think no one will add more shards to V1 layout
/// This layout is provisional, the actual shard layout should be determined | ||
/// based on the fresh data before the resharding. | ||
pub fn get_simple_nightshade_layout_v4() -> ShardLayout { | ||
// the boundary accounts in lexicographical order |
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.
nit: is it worth to add todo!()
or similar compiler help?
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.
Add todo where?
I'm hoping we can get v4 into nightly soon enough and start testing it there. I wouldn't want compiler panicking there.
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.
Just to prevent using the provisional layout by mistake, but anyway we are going to check this again before releasing
}; | ||
|
||
let mut shards_parent_map = ShardsParentMapV2::new(); | ||
for (&parent_shard_id, shard_ids) in shards_split_map.iter() { |
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 wondering if at some point we would need to implement a check such as: at most one shard is being split in a given ShardLayout. Maybe another day :)
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 wondering if we can perform a few splits at the same time :)
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.
Maybe another day :)
Co-authored-by: Andrea <[email protected]>
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.
My main concern is about where readability should be handled.
/// TODO(resharding) Determine the shard layout for v4. | ||
/// This layout is provisional, the actual shard layout should be determined | ||
/// based on the fresh data before the resharding. | ||
pub fn get_simple_nightshade_layout_v4() -> ShardLayout { |
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.
Marketing nit: what about dropping simple
and saying get_nightshade_layout_v4
since now?
The ultimate version may be called get_dynamic_nightshade_layout
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.
Technically speaking this particular layout will not be dynamic so I wouldn't want to call it that quite yet. I called it simple for consistency and greppability. I'm not sure the codebase is the best place for marketing but if you like it more I'm happy to rename it to get_nightshade_layout_v4
.
core/primitives/src/shard_layout.rs
Outdated
fn contains(&self, account_id: &AccountId) -> bool { | ||
match (&self.start, &self.end) { | ||
(AccountBoundary::Start, AccountBoundary::End) => true, | ||
(AccountBoundary::Start, AccountBoundary::Middle(end)) => account_id < end, | ||
(AccountBoundary::Middle(start), AccountBoundary::End) => account_id >= start, | ||
(AccountBoundary::Middle(start), AccountBoundary::Middle(end)) => { | ||
account_id >= start && account_id < end | ||
} | ||
(AccountBoundary::End, _) => panic!("invalid account range"), | ||
(_, AccountBoundary::Start) => panic!("invalid account range"), | ||
} | ||
} | ||
} |
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.
Alternative:
I remembered there are primitives std::ops::RangeBounds
and std::ops::Bound
. We can represent shard ranges using them. RangeBounds::contains
looks exactly like this function, with exception that there is Bound::Unbounded
which by definition means both start and end, depending on which side of range it represents. So panic is not needed there.
There are also RangeFull, RangeTo, RangeFrom.
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.
Oh nice, thanks! I should have guessed there is some ready structure for this. I'll check it out tomorrow!
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.
Would be nice to use primitives in std if it's simple enough for our use case
core/primitives/src/shard_layout.rs
Outdated
/// A mapping from the shard id to the account range of this shard. | ||
shards_account_range: ShardsAccountRange, |
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 agree with human readability argument. But I think readability should be handled by some tool over the shard layout format, like we already do for the latest snapshot of runtime config. I prefer the base primitive to be as concise as possible. For me it's like getting rpc response in json or text - both make sense, but json must be the base format.
If we go with ranges, start
and end
are valid accounts so we need some placeholders which are definitely invalid ($start
?)
shards_split_map: Option<ShardsSplitMapV2>, | ||
/// A mapping from the child shard to the parent shard. Maps shards in this | ||
/// shard layout to their parent shards. | ||
shards_parent_map: Option<ShardsParentMapV2>, |
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.
Why are the shards_parent_map and shards_split_map optional?
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's for the genesis case. It's used in tests mostly.
@@ -135,6 +213,51 @@ impl ShardLayout { | |||
}) | |||
} | |||
|
|||
/// Return a V2 Shardlayout | |||
pub fn v2( | |||
boundary_accounts: Vec<AccountId>, |
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.
We can also add debug assert that boundary_accounts is strictly sorted.
Adding a new shard layout structure. It replaces the boundary accounts with a mapping from shard id to the account range of that shard. This is necessary in order to implement the account id to shard id mapping. Previously it relied on shard ids being contiguous and ordered by the account ranges, neither of which will be the case in this shard layout.