{"id":16732599,"url":"https://github.com/lvala/zaft","last_synced_at":"2025-08-11T02:04:41.098Z","repository":{"id":249409284,"uuid":"807291148","full_name":"LVala/zaft","owner":"LVala","description":"The Raft Consensus Algorithm in Zig","archived":false,"fork":false,"pushed_at":"2024-08-02T08:17:25.000Z","size":72,"stargazers_count":19,"open_issues_count":0,"forks_count":2,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-10T11:41:33.429Z","etag":null,"topics":["consensus","distributed-systems","raft","raft-consensus-algorithm","zig"],"latest_commit_sha":null,"homepage":"","language":"Zig","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/LVala.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-05-28T20:26:31.000Z","updated_at":"2025-04-02T13:22:36.000Z","dependencies_parsed_at":"2025-02-17T04:31:58.079Z","dependency_job_id":"7c4abe01-aa70-4a7f-b68d-38e71794649b","html_url":"https://github.com/LVala/zaft","commit_stats":null,"previous_names":["lvala/zaft"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/LVala/zaft","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LVala%2Fzaft","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LVala%2Fzaft/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LVala%2Fzaft/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LVala%2Fzaft/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/LVala","download_url":"https://codeload.github.com/LVala/zaft/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LVala%2Fzaft/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":269819032,"owners_count":24480087,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-11T02:00:10.019Z","response_time":75,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["consensus","distributed-systems","raft","raft-consensus-algorithm","zig"],"created_at":"2024-10-12T23:45:46.795Z","updated_at":"2025-08-11T02:04:41.033Z","avatar_url":"https://github.com/LVala.png","language":"Zig","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n\n# `zaft`\n\n\u003ca href=\"https://ziglang.org/download/\"\u003e\u003cimg src=\"https://img.shields.io/badge/0.13.0-orange?style=flat\u0026logo=zig\u0026label=Zig\u0026color=%23eba742\"\u003e\u003c/a\u003e\n\u003ca href=\"https://github.com/LVala/zaft/releases\"\u003e\u003cimg src=\"https://img.shields.io/github/v/release/LVala/zaft?link=https%3A%2F%2Fgithub.com%2FLVala%2Fzaft%2Freleases\"\u003e\u003c/a\u003e\n\u003ca href=\"https://github.com/LVala/zaft/actions\"\u003e\u003cimg src=\"https://img.shields.io/github/actions/workflow/status/LVala/zaft/ci.yml?link=https%3A%2F%2Fgithub.com%2FLVala%2Fzaft%2Factions\"\u003e\u003c/a\u003e\n\n### The Raft Consensus Algorithm in Zig\n\nThis repository houses `zaft` - [Raft Consensus Algorithm](https://raft.github.io/) library implemented in Zig. It provides the building blocks\nfor creating distributed systems requiring consensus among replicated state machines, like databases.\n\n\u003c/div\u003e\n\n## Installation\n\nThis package can be installed using the Zig package manager. In `build.zig.zon` add `zaft` to the dependency list:\n\n```zig\n// in build.zig.zon\n.{\n    .name = \"my-project\",\n    .version = \"0.0.0\",\n    .dependencies = .{\n        .zaft = .{\n            .url = \"https://github.com/LVala/zaft/archive/\u003cgit-ref-here\u003e.tar.gz\",\n            .hash = \"12208070233b17de6be05e32af096a6760682b48598323234824def41789e993432c\"\n        },\n    },\n}\n```\n\nThe output of `zig build` will provide you with a valid hash, use it to replace the one above.\n\nAdd the `zaft` module in `build.zig`:\n\n```zig\n// in build.zig\nconst zaft = b.dependency(\"zaft\", .{ .target = target, .optimize = optimize });\nexe.root_module.addImport(\"zaft\", zaft.module(\"zaft\"));\n```\n\nNow you should be able to import `zaft` in your `exe`s root source file:\n\n```zig\nconst zaft = @import(\"zaft\");\n```\n\n## Usage\n\nThis section will show you how to integrate `zaft` with your program step-by-step. If you prefer to take a look at a fully working example,\ncheck out the [kv_store](./examples/kv_store) - in-memory, replicated key-value store based on `zaft`.\n\n\u003e [!IMPORTANT]\n\u003e This tutorial assumes some familiarity with the Raft Consensus Algorithm. If not, I highly advise you to at least skim through\n\u003e the [Raft paper](https://raft.github.io/raft.pdf). Don't worry, it's a short and very well-written paper!\n\nFirstly, initialize the `Raft` struct:\n\n```zig\n// we'll get to UserData and Entry in a second\nconst Raft = @import(\"zaft\").Raft(UserData, Entry);\n\nconst raft = Raft.init(config, initial_state, callbacks, allocator);\ndefer raft.deinit();\n```\n\n`Raft.init` takes four arguments:\n\n* `config` - configuration of this particular Raft node:\n\n```zig\nconst config = Raft.Config{\n    .id = 3,  // id of this Raft node\n    .server_no = 5,  // total number of Raft nodes, there should be nodes with ids from 0 up to server_no-1\n    // there's other options with some sane defaults, check out this struct's definition to learn\n    // what else can be configured\n};\n```\n\n* `callbacks` - `Raft` will call this function to perform various actions:\n\n```zig\n// makeRPC is used to send Raft messages to other nodes\n// this function should be non-blocking (not wait for the response)\nfn makeRPC(ud: *UserData, id: u32, rpc: Raft.RPC) !void {\n    const address = ud.node_addresse[id];\n    // it's your responsibility to serialize the message, consider using e.g. std.json\n    const msg: []u8 = serialize(rpc);\n    try ud.client.send(address, msg);\n}\n\n// Entry can be whatever you want\n// in this callback the entry should be applied to the state machine\n// applying an entry must be deterministic! This means that, after applying the\n// same entries in the same order, the state machine must end up in the same state every time\nfn applyEntry(ud: *UserData, entry: Entry) !void {\n    // let's assume that is some kind of key-value store\n    switch(entry) {\n        .add =\u003e |add| try ud.store.add(add.key, add.value),\n        .remove =\u003e |remove| try ud.store.remove(remove.key),\n    }\n}\n\n// these two functions have to append/pop entry to/from the log\nfn logAppend(ud: *UserData, log_entry: Raft.LogEntry) !void {\n    try ud.database.appendEntry(log_entry);\n}\n\nfn logPop(ud: *UserData) !Raft.LogEntry {\n    const log_entry = try ud.database.popEntry();\n    return log_entry;\n}\n\n// these functions are responsible for persisting values, that can be overridden on every save \nfn persistCurrentTerm(ud: *UserData, current_term: u32) !void {\n    try ud.database.persistCurrentTerm(current_term);\n}\n\nfn persistVotedFor(ud: *UserData, voted_for: ?u32) !void {\n    try ud.database.persistVotedFor(voted_for);\n}\n```\n\n\u003e [!WARNING]\n\u003e Notice that all of the callbacks can return an error (mostly for the sake of convenience).\n\u003e\n\u003e Error returned from `makeRPC` will be ignored, the RPC will be simply retried after\n\u003e an appropriate timeout. Errors returned from other functions, as of now, will result in a panic.\n\n```zig\n// pointer to user_data will be passed as a first argument to all of the callbacks\n// you can place whatever you want in the UserData\nconst user_data = UserData {\n    // these are some imaginary utilities\n    // necessary to make Raft work\n    database: Database,\n    http_client: HttpClient,\n    node_addresses: []std.net.Address,\n    store: Store,\n};\n\nconst callbacks = Raft.Callbacks {\n    .user_data = \u0026user_data,\n    .makeRPC = makeRPC,\n    .applyEntry = applyEntry,\n    .logAppend = logAppend,\n    .logPop = logPop,\n    .persistCurrentTerm = persisCurrentTerm,\n    .persistVotedFor = persistVotedFor,\n};\n```\n\n* `initial_state` - the persisted state of this Raft node. On each reboot, you need to read the persisted Raft state\n(`current_term`, `voted_for`, and `log`, previously saved by the callbacks) and use it as the `InitialState`:\n\n```zig\nconst initial_state = Raft.InitialState {\n    //let's assume we saved the state to a persistent database of some kind\n    .voted_for = user_data.database.readVotedFor(),\n    .current_term = user_data.database.readCurrentTerm(),\n    .log = user_data.database.readLog(),\n};\n```\n\nLastly, an `std.mem.Allocator` will be used to provide memory for the Raft log.\n\n---\n\nThe `Raft` struct needs to be periodically ticked to trigger timeouts and other necessary actions. You can use a separate thread to do that, or\nbuilt your program based on an event loop like [libexev](https://github.com/mitchellh/libxev) with its `xev.Timer`.\n\n```zig\nconst tick_after = raft.tick();\n// tick_after is the number of milliseconds after which raft should be ticked again\n```\n\nFor instance, [kv_store](./examples/kv_store/src/ticker.zig) uses a separate thread exclusively to tick the `Raft` struct.\n\n\u003e [!WARNING]\n\u003e The `Raft` struct is *not* thread-safe. Use appropriate synchronization means to make sure it is not accessed simultaneously by many threads\n\u003e (e.g. a simple `std.Thread.Mutex` will do).\n\nNext, messages from other Raft nodes need to be fed to the local `Raft` struct by calling:\n\n```zig\n// you will need to receive and deserialize the messages from other peers\nconst msg: Raft.RPC = try recv_msg();\nraft.handleRPC(msg);\n```\n\nLastly, entries can be appended to `Raft`s log by calling:\n\n```zig\nconst entry = Entry { ... };\nconst idx = try raft.appendEntry(entry);\n```\n\nIt will return an index of the new entry. According to the Raft algorithm, your program should block on client requests\nuntil the entry has been applied. You can use `std.Thread.Condition` and call its `notify` function in the `applyEntry` callback to notify\nthe program that the entry was applied. You can check whether the entry was applied by using `raft.checkIfApplied(idx)`.\nTake a look at how [kv_store](./examples/kv_store/src/main.zig) does this.\n\n`appendEntry` function will return an error if the node is not a leader. In such a case, you should redirect the client request to the leader node.\nYou can check which node is the leader by using `raft.getCurrentLeader()`. You can also check if the node is a leader proactively by calling\n`raft.checkifLeader()`.\n\n## Next steps\n\nThe library is fine to be used already, but there's plenty of room for improvements and new features, like:\n\n* Creating a simulator to test and find problems in the implementation.\n* Add auto-generated API documentation based on `zig build -femit-docs`.\n* Implementing _Cluster membership changes_.\n* Implementing _Log compaction_.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flvala%2Fzaft","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flvala%2Fzaft","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flvala%2Fzaft/lists"}