-
Notifications
You must be signed in to change notification settings - Fork 262
feat(metadata): impl Snapshot interface for Mux state machine #2675
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
base: master
Are you sure you want to change the base?
Conversation
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #2675 +/- ##
============================================
- Coverage 68.62% 68.46% -0.17%
Complexity 637 637
============================================
Files 734 735 +1
Lines 59660 60281 +621
Branches 56073 56694 +621
============================================
+ Hits 40943 41269 +326
- Misses 16717 16988 +271
- Partials 2000 2024 +24
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
core/metadata/src/impls/metadata.rs
Outdated
| /// # Arguments | ||
| /// * `mux` - The multiplexing state machine containing all sub-state machines | ||
| /// * `commit_number` - The VSR commit number this snapshot corresponds to | ||
| fn create<T>(mux: &MuxStateMachine<T>, commit_number: u64) -> Result<Self, Self::Error> |
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 impl StateMachine for MuxStateMachine, therefor I think we could accept as first argument stm: &mut 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.
Done.
core/metadata/src/impls/metadata.rs
Outdated
| /// This is the interface that `MetadataHandle::Snapshot` must satisfy. | ||
| /// It provides methods for creating, encoding, decoding, and restoring snapshots. | ||
| #[allow(unused)] | ||
| pub trait MetadataSnapshot: Sized { |
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.
Lets rename this to Snapshot trait, if there already exists one (that I created as an stub), remove 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.
Done.
core/metadata/src/impls/metadata.rs
Outdated
| where | ||
| T: StateMachine + SnapshotContributor; | ||
|
|
||
| /// Get the VSR commit number this snapshot corresponds to. |
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 we could use an sequence_number instead of commit number, the idea is that the sequence number would be monotonically growing on each snapshot, independent of the commit number from consensus.
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.
| } | ||
|
|
||
| /// Consumer group member snapshot representation for serialization. | ||
| #[derive(Debug, Clone, Serialize, Deserialize)] |
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 we don't need those intermediate structs, we can have the State structs impl Serialize and Deserialize directly.
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't serialize/deserialize directly because some members of these structs can't be used with serde directly. Thats why I had to use intermediate structs.
| } | ||
| } | ||
|
|
||
| /// Recursive case for variadic tuple pattern: (Head, Tail) |
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.
Hmm, I am not sure if this is the simplest way to go around it.
I imagine the flow of abstraction to be identical to one of the StateMachine trait,
e.g we would have an Snapshotable trait, that would be implemented for everything that impls StateMachine trait,
since StateMachine trait is implemented both for MuxStateMachine aswell as the variadic!() tuple of state machines, the API would be called this way:
fn snapshot_metadata(&self, ....) {
let mut snapshot = MetadataSnapshot::default(); <-- This would impl the `Snapshot` trait.
// Here is the magic, `fill_snapshot` impl for `MuxStateMachine` would proxy to the `variadic!()` tuple impl,
// impl Snapshotable for variadic!(St, ... Rest)
// where St: StateMachine + FillSnapshot,
// Rest: Snapshotable,
// {
// fn fill_snapshot<S: Snapshot>(&self, snapshot: &mut S) {
// self.0.do_fill_snapshot(snapshot);
// self.1.fill_snapshot(snapshot);
// }
// fn restore_snapshot(...) {
// // ... Here similar code for restoring.
// }
// }
// You can choose whatever trait name for the `FillSnapshot` trait, smth that covers filling/recovering
// You can have associated types on the `Snapshot` trait that would be used to represent the currently filled snapshot
// (for example a binary blob), and use those as arguments into the `fill_snapshot` and `restore_snapshot` methods.
// and the snapshot from which we would restore State.
self.mux.fill_snapshot(&mut snapshot);
}
The idea is to avoid using those `const SECTION:NAME: &'str`, rather rely on the type checking to perform the walk for us.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 could alternatively look into extending the StateMachine trait, by adding extra bounds on it
trait StateMachine + Snapshotable + ...
{
}and then have single impl block.
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've added type based recursion and snapshotting. Removed string based version.
| pub topic_name_index: Vec<((String, String), Vec<usize>)>, | ||
| } | ||
|
|
||
| impl Snapshotable for ConsumerGroupsInner { |
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.
Impl those for ConsumerGroup and Streams and Users, rather for their inners. You can create an metehod on the LeftRight, that exposes the read handle.
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.
core/metadata/src/stm/snapshot.rs
Outdated
| /// Contains metadata about the snapshot and a collection of sections, | ||
| /// where each section corresponds to one state machine's serialized state. | ||
| #[derive(Debug, Clone, Serialize, Deserialize)] | ||
| pub struct SnapshotEnvelope { |
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.
This is a valid approach, to store an type-erased bytes inside of the snapshot, but alternative approach (the one that redpanda did choose), is for the snapshot to store an intermediate representation of all the Snapshotable states and serialize those. So rather than serializing individual states, they serialize the snapshot instead.
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 didn't think about it a lot and not sure what is the trade off space between those two approaches (I guess you need more allocations with their approach, as you have to copy the data first and then serialize), where with our approach, we just serialize the state and append it to the snapshot envelope.
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.
Removed SnapshotEnvelope. Now we store each snapshot struct and serialize it in one shot.
f195436 to
9591421
Compare
- Removed `SnapshotEnvelope`. Now we store each snapshot struct and serialize in one shot
13f739b to
3bd27da
Compare
|
Fixed merge conflicts. |
The snapshot system has three layers:
Snapshotableper-state-machine trait. EachStreamsInner,UsersInner,ConsumerGroupsInnerimplementsto_snapshot()/ from_snapshot() to convert between in-memory state.SnapshotContributorvisitor trait using the same recursive variadic tuple pattern asStateMachine::update. Walks (Users, (Streams, (ConsumerGroups, ()))) at compile time, collecting each state machine's serialized section into aVec<SnapshotSection>.MetadataSnapshota top-level interface for the generic snapshot type onIggyMetadata. Defines the full lifecycle: create (snapshot from mux) → encode (to bytes) → decode (from bytes) → restore (back to mux).