{"id":27696947,"url":"https://github.com/lablup/raftify","last_synced_at":"2025-04-25T15:26:22.838Z","repository":{"id":183873291,"uuid":"670539840","full_name":"lablup/raftify","owner":"lablup","description":"Experimental High level Raft framework","archived":false,"fork":false,"pushed_at":"2024-10-28T00:57:05.000Z","size":1361,"stargazers_count":39,"open_issues_count":51,"forks_count":15,"subscribers_count":6,"default_branch":"main","last_synced_at":"2025-04-25T15:26:17.336Z","etag":null,"topics":["distributed","distributed-systems","python","raft","raft-framework","rust"],"latest_commit_sha":null,"homepage":"https://docs.rs/raftify/latest/raftify","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":"lablup/riteraft-py","license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/lablup.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}},"created_at":"2023-07-25T09:25:23.000Z","updated_at":"2025-03-18T15:09:05.000Z","dependencies_parsed_at":"2023-07-29T05:28:30.891Z","dependency_job_id":null,"html_url":"https://github.com/lablup/raftify","commit_stats":null,"previous_names":["lablup/riteraft-ng","lablup/raftify"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lablup%2Fraftify","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lablup%2Fraftify/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lablup%2Fraftify/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lablup%2Fraftify/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lablup","download_url":"https://codeload.github.com/lablup/raftify/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250842704,"owners_count":21496284,"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","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":["distributed","distributed-systems","python","raft","raft-framework","rust"],"created_at":"2025-04-25T15:26:22.195Z","updated_at":"2025-04-25T15:26:22.830Z","avatar_url":"https://github.com/lablup.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Raftify\n\n⚠️ WARNING: This library is in a very experimental stage. The API could be broken.\n\nRaftify is a *high-level* implementation of [Raft](https://raft.github.io/), developed with the goal of making it easy and straightforward to integrate the Raft algorithm.\n\nIt uses [tikv/raft-rs](https://github.com/tikv/raft-rs) and gRPC for the network layer and [heed](https://github.com/meilisearch/heed) (LMDB wrapper) for the storage layer.\n\n## Quick guide\n\nI strongly recommend to read the [basic memstore example code](https://github.com/lablup/raftify/blob/main/examples/memstore/static-members/src/main.rs) to get how to use this library for starters, but here's a quick guide.\n\n### Define your own log entry\n\nDefine the data to be stored in `LogEntry` and how to *serialize* and *deserialize* it.\n\n```rust\n#[derive(Clone, Debug, Serialize, Deserialize)]\npub enum LogEntry {\n    Insert { key: u64, value: String },\n}\n\nimpl AbstractLogEntry for LogEntry {\n    fn encode(\u0026self) -\u003e Result\u003cVec\u003cu8\u003e\u003e {\n        serialize(self).map_err(|e| e.into())\n    }\n\n    fn decode(bytes: \u0026[u8]) -\u003e Result\u003cLogEntry\u003e {\n        let log_entry: LogEntry = deserialize(bytes)?;\n        Ok(log_entry)\n    }\n}\n```\n\n### Define your application Raft FSM\n\nEssentially, the following three methods need to be implemented for the `Store`.\n\n- `apply`: applies a committed entry to the store.\n- `snapshot`: returns snapshot data for the store.\n- `restore`: applies the snapshot passed as argument.\n\nAnd also similarly to `LogEntry`, you need to implement `encode` and `decode`.\n\n```rust\n#[derive(Clone, Debug)]\npub struct HashStore(pub Arc\u003cRwLock\u003cHashMap\u003cu64, String\u003e\u003e\u003e);\n\nimpl HashStore {\n    pub fn new() -\u003e Self {\n        Self(Arc::new(RwLock::new(HashMap::new())))\n    }\n\n    pub fn get(\u0026self, id: u64) -\u003e Option\u003cString\u003e {\n        self.0.read().unwrap().get(\u0026id).cloned()\n    }\n}\n\n#[async_trait]\nimpl AbstractStateMachine for HashStore {\n    async fn apply(\u0026mut self, data: Vec\u003cu8\u003e) -\u003e Result\u003cVec\u003cu8\u003e\u003e {\n        let log_entry: LogEntry = LogEntry::decode(\u0026data)?;\n        match log_entry {\n            LogEntry::Insert { ref key, ref value } =\u003e {\n                let mut db = self.0.write().unwrap();\n                log::info!(\"Inserted: ({}, {})\", key, value);\n                db.insert(*key, value.clone());\n            }\n        };\n        Ok(data)\n    }\n\n    async fn snapshot(\u0026self) -\u003e Result\u003cVec\u003cu8\u003e\u003e {\n        Ok(serialize(\u0026self.0.read().unwrap().clone())?)\n    }\n\n    async fn restore(\u0026mut self, snapshot: Vec\u003cu8\u003e) -\u003e Result\u003c()\u003e {\n        let new: HashMap\u003cu64, String\u003e = deserialize(\u0026snapshot[..]).unwrap();\n        let mut db = self.0.write().unwrap();\n        let _ = std::mem::replace(\u0026mut *db, new);\n        Ok(())\n    }\n\n    fn encode(\u0026self) -\u003e Result\u003cVec\u003cu8\u003e\u003e {\n        serialize(\u0026self.0.read().unwrap().clone()).map_err(|e| e.into())\n    }\n\n    fn decode(bytes: \u0026[u8]) -\u003e Result\u003cSelf\u003e {\n        let db: HashMap\u003cu64, String\u003e = deserialize(bytes)?;\n        Ok(Self(Arc::new(RwLock::new(db))))\n    }\n}\n```\n\n### Bootstrap a raft cluster\n\nFirst bootstrap the cluster that contains the leader node.\n\n```rust\nlet raft_addr = \"127.0.0.1:60061\".to_owned();\nlet node_id = 1;\n\nlet log_storage = HeedStorage::create(\u0026storage_pth, \u0026raft_config.clone(), logger.clone())\n    .expect(\"Failed to create heed storage\");\n\nlet raft = Raft::bootstrap(\n    node_id,\n    raft_addr,\n    log_storage,\n    store.clone(),\n    raft_config,\n    logger.clone(),\n)?;\n\ntokio::spawn(raft.clone().run());\n\n// ...\ntokio::try_join!(raft_handle)?;\n```\n\n### Join follower nodes to the cluster\n\nThen join the follower nodes.\n\nIf peer specifies the configuration of the initial members, the cluster will operate after all member nodes are bootstrapped.\n\n```rust\nlet raft_addr = \"127.0.0.1:60062\".to_owned();\nlet peer_addr = \"127.0.0.1:60061\".to_owned();\nlet join_ticket = Raft::request_id(raft_addr, peer_addr).await;\n\nlet log_storage = HeedStorage::create(\u0026storage_pth, \u0026raft_config.clone(), logger.clone())\n    .expect(\"Failed to create heed storage\");\n\nlet raft = Raft::bootstrap(\n    join_ticket.reserved_id,\n    raft_addr,\n    log_storage,\n    store.clone(),\n    raft_config,\n    logger.clone(),\n)?;\n\nlet raft_handle = tokio::spawn(raft.clone().run());\nraft.join_cluster(vec![join_ticket]).await?;\n\n// ...\ntokio::try_join!(raft_handle)?;\n```\n\n### Manipulate FSM by RaftServiceClient\n\nIf you want to operate the FSM remotely, you can use [RaftServiceClient](https://docs.rs/raftify/latest/raftify/raft_service/raft_service_client/struct.RaftServiceClient.html).\n\n```rust\nlet mut leader_client = create_client(\u0026\"127.0.0.1:60061\").await.unwrap();\n\nleader_client\n    .propose(raft_service::ProposeArgs {\n        msg: LogEntry::Insert {\n            key: 1,\n            value: \"test\".to_string(),\n        }\n        .encode()\n        .unwrap(),\n    })\n    .await\n    .unwrap();\n```\n\n### Manipulate FSM by RaftNode\n\nIf you want to operate FSM locally, use the [RaftNode](https://docs.rs/raftify/latest/raftify/struct.RaftNode.html) type of the [Raft](https://docs.rs/raftify/latest/raftify/struct.Raft.html) object.\n\n```rust\nraft.propose(LogEntry::Insert {\n    key: 123,\n    value: \"test\".to_string(),\n}.encode().unwrap()).await;\n```\n\n## Debugging\n\nYou can use a collection of CLI commands that let you inspect the data persisted in stable storage and the status of Raft Servers.\n\n```\n❯ raftify-cli describe logs ./logs/node-1\n┌───────┬────────────┬──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┬──────┐\n│ index ┆    type    ┆                                                             data                                                             ┆ term │\n╞═══════╪════════════╪══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╪══════╡\n│   2   ┆ ConfChange ┆ ConfChangeV2 { transition: 0, changes: [ConfChangeSingle { change_type: AddNode, node_id: 2 }], context: [127.0.0.1:60062] } ┆   1  │\n├╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┤\n│   3   ┆ ConfChange ┆ ConfChangeV2 { transition: 0, changes: [ConfChangeSingle { change_type: AddNode, node_id: 3 }], context: [127.0.0.1:60063] } ┆   1  │\n└───────┴────────────┴──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┴──────┘\n```\n\n```\n❯ raftify-cli describe metadata ./logs/node-1\n┌───────────┬─────────────────────────────────────┬──────────────────────────┐\n│           ┆ Field                               ┆ Value                    │\n╞═══════════╪═════════════════════════════════════╪══════════════════════════╡\n│ HardState ┆ term                                ┆ 1                        │\n├╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤\n│           ┆ vote                                ┆ 1                        │\n├╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤\n│           ┆ commit                              ┆ 3                        │\n├╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤\n│ ConfState ┆ voters                              ┆ {1, 2, 3}                │\n├╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤\n│           ┆ learners                            ┆ {}                       │\n├╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤\n│           ┆ voters_outgoing                     ┆ {}                       │\n├╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤\n│           ┆ learners_next                       ┆ {}                       │\n├╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤\n│           ┆ auto_leave                          ┆ false                    │\n├╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤\n│ Snapshot  ┆ data                                ┆ [0, 0, 0, 0, 0, 0, 0, 0] │\n├╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤\n│           ┆ metadata.index                      ┆ 3                        │\n├╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤\n│           ┆ metadata.term                       ┆ 1                        │\n├╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤\n│           ┆ metadata.conf_state.voters          ┆ {1, 2, 3}                │\n├╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤\n│           ┆ metadata.conf_state.learners        ┆ {}                       │\n├╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤\n│           ┆ metadata.conf_state.voters_outgoing ┆ {}                       │\n├╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤\n│           ┆ metadata.conf_state.learners_next   ┆ {}                       │\n├╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤\n│           ┆ metadata.conf_state.auto_leave      ┆ false                    │\n├╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤\n│ LastIndex ┆ last index                          ┆ 3                        │\n└───────────┴─────────────────────────────────────┴──────────────────────────┘\n```\n\n## Bootstrapping from WAL\n\nIf there are previous logs remaining in the log directory, the raft node will automatically apply them after the node is bootstrapped.\nIf you intend to bootstrap the cluster from the scratch, please remove the previous log directory.\nTo ignore the previous logs and bootstrap the cluster from a snapshot, use the `Config.bootstrap_from_snapshot` option.\n\n## Support for other languages\n\nRaftify provides bindings for the following languages.\n\n- [Python](https://github.com/lablup/raftify/tree/main/binding/python)\n\n## Building from Source\n\nIf you want to build Raftify from the source code or set up a development environment, please refer to the [DEVELOPMENT.md](https://github.com/lablup/raftify/blob/main/DEVELOPMENT.md).\n\n## References\n\nRaftify was inspired by a wide variety of previous Raft implementations.\n\nGreat thanks to all the relevant developers.\n\n- [tikv/raft-rs](https://github.com/tikv/raft-rs) - Raft distributed consensus algorithm implemented using in this lib under the hood.\n- [ritelabs/riteraft](https://github.com/ritelabs/riteraft) - A raft framework, for regular people. Raftify was forked from this lib.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flablup%2Fraftify","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flablup%2Fraftify","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flablup%2Fraftify/lists"}