lol-core

A Raft implementation with thin abstraction layer.

Features

Design

Thin and Thick abstraction

abstraction

We recognize the prior arts like raft-rs and async-raft and find their designs are very similar. We call this Thick abstraction because the abstraction layer (typically called RaftStorage) is thick enough to include everything like log structure, snapshotting mechanism and storing most recent vote. Some of them also abstract networking so testing becomes deterministic and choice of networking backend are left to users.

However, the thicker the abstraction is the more frequent the common layer and the abstraction layer communicate through the abstracted interface. This makes runtime inefficient in some cases and also error handling is likely to be imperfect.

Our library called lol goes different: the abstraction is thin and the common layer includes log structure, snapshot and so on. App implementor needs to implement RaftApp only and everything else is fully managed by the library.

rust pub trait RaftApp: Sync + Send + 'static { async fn apply_message(&self, request: Message) -> anyhow::Result<Message>; async fn install_snapshot(&self, snapshot: Snapshot) -> anyhow::Result<()>; async fn fold_snapshot( &self, old_snapshot: Snapshot, requests: Vec<Message>, ) -> anyhow::Result<Snapshot>; }

Snapshot as a log entry

Unlike other Raft libraries, lol manages snapshot in the library part and it is just a log entry that other normal entries follow. This choice reduces the implementation complexity by eliminating InstallSnapshot RPC. This implementation is referred in Raft thesis $5.4.1 "Storing snapshots in the log".

Comparison

There are pros/cons between the designs.

Our recommendation is: if the app you try to implement is something like storage or database that the snapshot is gigabytes or more then choose thick abstraction and what you need is efficient log replication mechanism rather than gigantic state machine replication, lol is the way to go.

Future works