Expand description

§Guide for upgrading from v0.8 to v0.9:

Change log v0.9.0

§Major changes:

§Upgrade RaftTypeConfig

  • Add AsyncRuntime to RaftTypeConfig to specify the async runtime to use. Openraft provides a default implementations: TokioRuntime. For example:

    openraft::declare_raft_types!(
      pub TypeConfig:
          // ...
          AsyncRuntime = TokioRuntime
    );

    You can just implement the AsyncRuntime trait for your own async runtime and use it in RaftTypeConfig.

§Upgrade log storage: save committed log id

In v0.9, save_committed() is added to RaftLogStorage trait to optionally save the committed log id. For example:

impl RaftLogStorage for YourStorage {
    // ...
    fn save_committed(&self, committed: u64) -> Result<(), StorageError> {
        // ...
    }
}

If committed log id is saved, the committed log will be applied to state machine via RaftStateMachine::apply() upon startup, if they are not already applied.

Not implementing save_committed() will not affect correctness.

§Upgrade snapshot transmission

In v0.9, an application can use its own snapshot data transfer by enabling generic-snapshot-data feature flag. With this flag enabled, the snapshot data can be arbitrary type and is no longer required to implement AsyncRead + AsyncWrite trait.

To use arbitrary snapshot data, the application needs to:

  • Specify the snapshot data type in RaftTypeConfig:

    openraft::declare_raft_types!(
      pub TypeConfig:
          // ...
          SnapshotData = YourSnapshotData
    );
  • implement the snapshot transfer in the application’s network layer by implementing the RaftNetwork trait. For example:

    impl RaftNetwork for YourNetwork {
        // ...
        fn full_snapshot(&self, /*...*/) -> Result<_,_> {
            // ...
        }
    }

    full_snapshot() allows for the transfer of snapshot data using any preferred method. Openraft provides a default implementation in Chunked. Chunked just delegate the transmission to several calls to the old chunk-based API RaftNetwork::install_snapshot(). If you want to minimize the changes in your application, just call Chunked::send_snapshot() in full_snapshot():

    impl RaftNetwork for YourNetwork {
        async fn full_snapshot(&mut self, vote: Vote<C::NodeId>, snapshot: Snapshot<C>, /*...*/
        ) -> Result<SnapshotResponse<C::NodeId>, StreamingError<C, Fatal<C::NodeId>>> {
            let resp = Chunked::send_snapshot(self, vote, snapshot, /*...*/).await?;
            Ok(resp)
        }
    }

    Refer to: the default chunk-based snapshot sending

  • On the receiving end, when the application finished receiving the snapshot data, it should call Raft::install_full_snapshot() to install it.

    Chunked::receive_snapshot provides a default chunk-based implementation for receiving snapshot data. The application can just use it:

    async fn handle_install_snapshot_request(
        &self,
        req: InstallSnapshotRequest<C>,
    ) -> Result<_, _>
    where C::SnapshotData: AsyncRead + AsyncWrite + AsyncSeek + Unpin,
    {
        let my_vote = self.with_raft_state(|state| *state.vote_ref()).await?;
        let resp = InstallSnapshotResponse { vote: my_vote };
    
        let finished_snapshot = {
            use crate::network::snapshot_transport::Chunked;
            use crate::network::snapshot_transport::SnapshotTransport;
    
            let mut streaming = self.snapshot.lock().await;
            Chunked::receive_snapshot(&mut *streaming, self, req).await?
        };
    
        if let Some(snapshot) = finished_snapshot {
            let resp = self.install_full_snapshot(req_vote, snapshot).await?;
            return Ok(resp.into());
        }
        Ok(resp)
    }

    Refer to: the default chunk-based snapshot receiving

    Note that the application is responsible for maintaining a streaming state Streaming during receiving chunks.