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

FO-284: Save and Prune versions automatically on commit #7

Merged
merged 14 commits into from Sep 15, 2021

Conversation

kevinssgh
Copy link
Contributor

  1. Added function to save copy of batch at given height for a versioned history of the state.
  2. Added functionality to prune the auxiliary version data outside a given window
  3. Added unit tests to cover this functionality

);

//Query aux values that are older than the window size to confirm batches were pruned
for i in 1..10 {
Copy link
Contributor

Choose a reason for hiding this comment

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

Have we pruned [1, 10) or [1, 10] in this case?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

[1, 10)

Copy link
Contributor

Choose a reason for hiding this comment

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

okay, I guess the window contains the current height and it actually doesn't.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The current height at this point is 20, the window is 10. This means that versions 1 - 9 were pruned. So the current version is in the window.

}
//Build range keys for window limits
let window_start_height = (height - self.ver_window).to_string();
let pruning_height = (height - self.ver_window - 1).to_string();
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we prune only one single commit? I was considering what will happen if a Node previously configured its window length as 1000 but he modified it to 500 and restart the node. In this case we probably have to prune 500+ commit batches.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You're right, there will be extra versions in the aux db if this happens. I was trying to reduce the complexity of this pruning logic, so it's really a question of whether this library is responsible for cleaning up data for an application that has changed configurations.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I was just thinking, how would you know what the previous window size was? And then how far back you need to iterate and delete. Probably need a maximum amount to look back and delete. This can be done when initializing the chainstate... but yes, we can discuss this in the morning if that's ok.

Copy link
Contributor

Choose a reason for hiding this comment

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

Sure thing. Let's talk.

{
// Add the key to new window limit height
batch.push((
new_window_limit
Copy link
Contributor

Choose a reason for hiding this comment

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

Would it be better that we add a tag before the height number, like "V100_key1" or "VER100_key2", to group up all commit history. Besides these commit history data, the only key that also lives in aux partition for now is the "Height".

Copy link
Contributor Author

@kevinssgh kevinssgh Sep 1, 2021

Choose a reason for hiding this comment

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

It's not really necessary to add a tag, its more about preference since there are no other keys being stored in the db (other than the Height). It is a simple change if you think it is necessary

Copy link
Contributor

Choose a reason for hiding this comment

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

By adding a Tag, we would be elegant and be able to get all the commit batches by a range query "VER_" "VER~". Then the first record located contains lowest height, and the last record contains the highest height.

.exists_aux(new_window_limit.push(key[1].as_bytes()).as_ref())
.unwrap_or(false)
{
// Add the key to new window limit height
Copy link
Contributor

Choose a reason for hiding this comment

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

Are you rolling up the KVs in "pruning_height" into "pruning_height+1"(window_start_height). But how did you merge/combine a the KV update (1_key1_valueB ==> 2_key2_valueB)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, the condition above is checking if the key already exists in pruning_height + 1, if it doesn't then the key is rolled into the new window. Not sure what the question is.

Copy link
Contributor

Choose a reason for hiding this comment

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

The question was how "pruning_height_key1_someValue" gets converted into "pruning_height+1_key1_someValue" and then rolled into the new window while there is ALREADY a "pruning_height+1_key1_someOtherValue" living in there.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If you see this condition: !self.exists_aux(new_window_limit.push(key[1].as_bytes()).as_ref())

I'm checking if it doesn't exist in the new window before attempting to move the Key and value. So this actually doesn't do anything if that key is already in the new window.

/// This is to keep a versioned history of KV pairs.
fn build_aux_batch(&self, height: u64, batch: &mut KVBatch) -> Result<KVBatch> {
let height_str = height.to_string();
let prefix = Prefix::new("VER".as_bytes()).push(height_str.as_bytes());
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we wrap up this prefix building logic into a private func. I will need it as well when I export a snapshot on a specific height. Like fn version_prefix(height: u64) -> Prefix {...}.

.map(|(k, v)| {
(
prefix.clone().push(k.clone().as_slice()).as_ref().to_vec(),
v.clone(),
Copy link
Contributor

Choose a reason for hiding this comment

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

if v == None, are we storing nothing in aux db? When pruning/replay works, how would we delete the value from aux db.

IterOrder::Desc,
&mut |(k, _v)| -> bool {
//Delete the key from aux db
batch.push((k, None));
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we delete commits history or should we roll up the edge (on oldest side, which is the base state) all the way to new edge (of reduced window)

kevinssgh added 3 commits September 7, 2021 20:41
let cs_new = chain_state::ChainState::new(fdb_new, "test_db".to_string(), new_window_size);

//Confirm keys older than new window size have been deleted
for i in 1..(number_of_batches - new_window_size - 1) {
Copy link
Contributor

Choose a reason for hiding this comment

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

shouldn't the range be 1..(number_of_batches - new_window_size)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I believe there is one extra version saved as the base chain-state but I can double check this to be sure.

}

//Get batch for state at H = current_height - ver_window
let batch = self.build_state(current_height - self.ver_window);
Copy link
Contributor

Choose a reason for hiding this comment

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

Just thought of an edge case when there is a "TOMBSTONE" in H ( = current_height - ver_window). The returned batch will be committed to aux as expected, but seems the TOMBSTONE will stay undeleted?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's ok, when we query the value it will be seen as None. The actual value will be deleted when prune_aux_batch is called as the window slides.

@ktmlm
Copy link
Contributor

ktmlm commented Sep 11, 2021

The checkpoint/version managements of rocksdb is implemented in a file level, not file-system block level, so we should do enough tests for this feature.

The effects are more like overlayfs, not the snapshots style of ZFS/BTRFS, etc.

they are light weight compared with full-copy, but not light weight enough.

@ws4charlie
Copy link
Contributor

The checkpoint/version managements of rocksdb is implemented in a file level, not file-system block level, so we should do enough tests for this feature.

The effects are more like overlayfs, not the snapshots style of ZFS/BTRFS, etc.

they are light weight compared with full-copy, but not light weight enough.

Yes yes. RocksDB snapshot is being used at the level of Merk library to take a copy. Storage-level versioning here is done in an incremental way like Git Commit history. The EVM JSON RPCs needs versioned data.

@ws4charlie
Copy link
Contributor

looks good now.

@kevinssgh kevinssgh merged commit f29d0ae into develop Sep 15, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants