Skip to content

Commit

Permalink
test
Browse files Browse the repository at this point in the history
  • Loading branch information
LVala committed Jul 20, 2024
1 parent 25fe50a commit 29623be
Showing 1 changed file with 159 additions and 1 deletion.
160 changes: 159 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,169 @@
![GitHub Version](https://img.shields.io/github/v/release/LVala/tang)
![GitHub License](https://img.shields.io/github/license/LVala/tang)

### Implementation of the [Raft Consensus Algorithm](https://raft.github.io/) in Zig
### The Raft Consensus Algorithm in Zig

This repository houses `zaft` - a [Raft Consensus Algorithm](https://raft.github.io/) library implemented in Zig. It provides the building blocks
for building distributed systems requiring consensus among replicated state machines, like databases.


![tang demo](tang.gif)
</div>

## Installation

This package can be installed using the Zig package manager. In your `build.zig.zon` file add `zaft` to dependency list:

```zig
// build.zig.zon
.{
.name = "my-project",
.version = "0.0.0",
.dependencies = .{
.zaft = .{
.url =
},
},
}
```

Finally, add the `zaft` module in you `build.zig`:

```zig
const zaft = b.dependency("zaft", .{ .target = target, .optimize = optimize });
exe.root_module.addImport("zaft", zaft.module("zaft"));
```

Now you should be able to import `zaft` in your `exe`s root source file:

```zig
const zaft = @import("zaft");
```

## Usage

> [!IMPORTANT]
> This usage tutorial assumes some familiarity with the Raft Consensus Algorithm. If not, I highly advise you to at least skim through
> the [Raft paper](https://raft.github.io/raft.pdf) and familiarize yourself with how the algorithm works. Don't worry, it's a very well
> written paper!
Firstly, initialise the `Raft` struct

```zig
// we'll get through to UserData and Entry later
const Raft = @import("zaft").Raft(UserData, Entry);
const config = Raft.Config{
};
const callbacks = Raft.Callbacks{
// ..
};
const initial_state = Raft.InitialState{
// ...
};
const raft = Raft.init(config, initial_state, callbacks)
```

`Raft.init` takes three arguments:

* `config` - configuration of this particular Raft node:

```zig
const config = Raft.Config{
.id = 3, // id of this Raft node
.server_no = 5, // total number of Raft nodes
// other options with sane defaults, check out this struct's definition to learn
// what else you can configure
};
```

* `callbacks` - `Raft` will call this function to perform various actions:

```zig
fn makeRPC(ud: *UserData, id: u32, rpc: Raft.RPC) !void {
// makeRPC is used to send Raft messages to other nodes
// this function should be non-blocking, that is, not wait for the response
const address = ud.node_addresse[id];
// it's your responsibility to serialize the message, consider using e.g. std.json
const msg: []u8 = serialize(rpc);
try ud.client.send(address, msg);
}
fn applyEntry(ud: *UserData, entry: Entry) !void {
// Entry can be whatever you want
// in this callback the entry should be applied to the state machine
// applying an entry must be deterministic! That is, after applying the
// same entries in the same order, the state machine must be in the same state
// let's assume that is some kind of key-value store
switch(entry) {
.add => |add| try ud.store.add(add.key, add.value),
.remove => |remove| try ud.store.remove(remove.key),
}
}
fn logAppend(ud: *UserData, log_entry: Raft.LogEntry) !void {
// this function needs to persist a new log entry
try ud.database.appendEntry(log_entry);
}
fn logPop(ud: *UserData) !Raft.LogEntry {
// this function needs to pop the last log entry from the persistent storage
const log_entry = try ud.database.popEntry();
return log_entry;
}
fn persistCurrentTerm(ud: *UserData, current_term: u32) !void {
// this function needs to persist current_term
try ud.database.persistCurrentTerm(current_term);
}
fn persistVotedFor(ud: *UserData, voted_for: ?u32) !void {
// this function needs to persist voted_for
try ud.database.persistVotedFor(voted_for);
}
```

> [!WARNING]
> Notice that all of the callbacks can return an error for the sake of convinience.
> Error returned from `makeRPC` will be ignored, the RPC will be simply retried after
> appropriate timeou. Error returned from other function, as of now, will result in panic.
```zig
// pointer to user_data will be passed as a first argument to all of the callbacks
// you can place whatever you want as the UserData
const user_data = UserData {
database: Database,
http_client: HttpClient,
node_addresses: []std.net.Address,
store: Store,
};
const callbacks = Raft.Callbacks {
.user_data = &user_data,
.makeRPC = makeRPC,
.applyEntry = applyEntry,
.logAppend = logAppend,
.logPop = logPop,
.persistCurrentTerm = persisCurrentTerm,
.persistVotedFor = persistVotedFor,
};
```

* `initial_state` - the persisted state of this Raft node. On each reboot, you need to read the persisted Raft state, that
is the `current_term`, `voted_for` and `log` and use it as the `InitialState`:

```zig
const initial_state = Raft.InitialState {
// lets assume we saved the state to a
.voted_for = user_data.database.readVotedFor(),
.current_term = user_data.database.readCurrentTerm(),
.log = user_data.database.readLog(),
};
```




0 comments on commit 29623be

Please sign in to comment.