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
Conversation
kevinssgh
commented
Sep 1, 2021
- Added function to save copy of batch at given height for a versioned history of the state.
- Added functionality to prune the auxiliary version data outside a given window
- Added unit tests to cover this functionality
…outside of the version window
); | ||
|
||
//Query aux values that are older than the window size to confirm batches were pruned | ||
for i in 1..10 { |
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.
Have we pruned [1, 10) or [1, 10] in this case?
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.
[1, 10)
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.
okay, I guess the window contains the current height and it actually doesn't.
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.
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.
src/state/chain_state.rs
Outdated
} | ||
//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(); |
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.
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.
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.
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.
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 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.
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.
Sure thing. Let's talk.
{ | ||
// Add the key to new window limit height | ||
batch.push(( | ||
new_window_limit |
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 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".
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 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
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.
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 |
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.
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)?
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.
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.
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.
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.
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.
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.
src/state/chain_state.rs
Outdated
/// 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()); |
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.
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 {...}.
src/state/chain_state.rs
Outdated
.map(|(k, v)| { | ||
( | ||
prefix.clone().push(k.clone().as_slice()).as_ref().to_vec(), | ||
v.clone(), |
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.
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)); |
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.
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)
Added some helper functions
Added unit test to confirm functionality
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) { |
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.
shouldn't the range be 1..(number_of_batches - new_window_size)
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 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); |
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 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?
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 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.
The checkpoint/version managements of rocksdb is implemented in a The effects are more like overlayfs, not the snapshots style of ZFS/BTRFS, etc. they are light weight compared with |
Refactored Build aux batch to ignore versioning if Ver_window == 0
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. |
looks good now. |