{"id":32184643,"url":"https://github.com/fluree/raft","last_synced_at":"2025-10-21T23:55:13.466Z","repository":{"id":48531996,"uuid":"274979038","full_name":"fluree/raft","owner":"fluree","description":"Raft as a library. Includes optional capabilities from the Raft whitepaper, such as snapshotting.","archived":false,"fork":false,"pushed_at":"2025-07-16T14:11:28.000Z","size":1742,"stargazers_count":23,"open_issues_count":1,"forks_count":2,"subscribers_count":10,"default_branch":"main","last_synced_at":"2025-10-21T23:55:10.513Z","etag":null,"topics":["consensus","fluree","raft"],"latest_commit_sha":null,"homepage":"","language":"HTML","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/fluree.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","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,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2020-06-25T17:42:55.000Z","updated_at":"2025-08-01T20:53:44.000Z","dependencies_parsed_at":"2022-08-31T19:23:00.135Z","dependency_job_id":"f3e7524b-224a-478a-a3bb-e74b13e84b11","html_url":"https://github.com/fluree/raft","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/fluree/raft","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fluree%2Fraft","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fluree%2Fraft/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fluree%2Fraft/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fluree%2Fraft/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/fluree","download_url":"https://codeload.github.com/fluree/raft/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fluree%2Fraft/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":280354180,"owners_count":26316400,"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-10-21T02:00:06.614Z","response_time":58,"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","fluree","raft"],"created_at":"2025-10-21T23:55:09.155Z","updated_at":"2025-10-21T23:55:13.459Z","avatar_url":"https://github.com/fluree.png","language":"HTML","funding_links":[],"categories":[],"sub_categories":[],"readme":"# fluree/raft\n\nAn implementation of the [Raft consensus algorithm](https://raft.github.io/) \nin Clojure. This library provides distributed consensus with pluggable \ncomponents for networking, state machines, and persistence.\n\n## Features\n\n- **Raft Core**: Leader election, log replication, and cluster membership \n  changes\n- **Persistent Storage**: Durable log storage with automatic rotation and \n  snapshot support\n- **Dynamic Membership**: Add or remove servers from the cluster\n- **Pluggable Architecture**: Customize networking, state machines, and \n  storage backends\n- **Error Handling**: Includes log corruption recovery and error handling\n- **Monitoring**: Built-in callbacks for leadership changes and other events\n\n## Prerequisites\n\n- Clojure 1.11.1 or higher\n- Java 8 or higher\n- GNU Make (for development tasks)\n\n## Installation\n\n[![Clojars Project](https://img.shields.io/clojars/v/com.fluree/raft.svg)](https://clojars.org/com.fluree/raft)\n\n### deps.edn\n\n```clojure\n{:deps {com.fluree/raft {:mvn/version \"1.0.0-beta1\"}}}\n```\n\nUsing Git dependency (for latest development version)\n\n```clojure\n{:deps {fluree/raft {:git/url \"https://github.com/fluree/raft\"\n                     :git/sha \"904d915\"}}} ; use latest commit SHA\n```\n\n## Validation \u0026 Performance\n\nThis Raft implementation has been validated with [Jepsen](https://jepsen.io/) for correctness and performance.\n\n### Latest Results (July 2025)\n\n**Consistency Validation:**\n- ✅ **Linearizability**: Zero violations across 5-minute stress test with 2-key concurrent operations\n- ✅ **High Concurrency**: [Up to 757.3 ops/sec (3-node cluster, 200 clients)](jepsen-raft/performance-results.md)\n- ✅ **Jepsen Result**: \"Valid\" - [Jepsen test results](jepsen-raft/test-results/5-minute-consistency-test/results.edn)\n\n### Test Suite\n\n**Three Test Environments:**\n\n[Complete Test Guide](jepsen-raft/README.md) - Setup, configuration, and test scenarios\n1. **[Local Net.Async Test](jepsen-raft/README.md#1-local-netasync-test-testclj)** - Fast development iteration\n2. **[Docker Network Failure Test](jepsen-raft/README.md#2-dockerized-test-with-network-failures-test_dockerclj)** - Production simulation with network partitions\n3. **[Performance Stress Test](jepsen-raft/README.md#3-performance-stress-test-test_performanceclj)** - Capacity planning and breaking point analysis\n\n## Quick Start\n\nHere's a minimal example of setting up a single-node Raft instance:\n\n```clojure\n(ns my-app.core\n  (:require [fluree.raft :as raft]))\n\n;; Track application state\n(def app-state (atom {}))\n\n;; Define your state machine\n;; Note: state machine receives entry and raft-state params\n(defn state-machine [entry raft-state]\n  ;; Process the entry and update application state\n  (swap! app-state\n         (fn [state]\n           (case (:op entry)\n             :set (assoc state (:key entry) (:value entry))\n             :delete (dissoc state (:key entry))\n             state)))\n  ;; Return result for the command callback\n  true)\n\n;; Configure and start a Raft node\n(def config\n  {:servers          [\"server1\"]\n   :this-server      \"server1\"  ; Required: identifies this node\n   :send-rpc-fn      (fn [server msg callback] \n                       ;; For single node, invoke callback with nil\n                       (when callback (callback nil)))\n   :leader-change-fn (fn [event] \n                       (println \"Leader changed to:\" (:new-leader event)))\n   :log-directory    \"/var/raft/logs\"\n   :state-machine    state-machine\n   \n   ;; Required snapshot functions\n   :snapshot-write   (fn [file state] \n                       (spit file (pr-str state)))\n   :snapshot-reify   (fn [] @app-state)\n   :snapshot-install (fn [snapshot index] \n                       (reset! app-state snapshot))\n   :snapshot-xfer    (fn [snapshot server] \n                       ;; Transfer snapshot to another server\n                       ;; For single node, this is a no-op\n                       nil)\n   :snapshot-list-indexes (fn [dir] \n                            ;; Return list of available snapshot indexes\n                            [])})\n\n;; Start the Raft instance\n(def raft-instance (raft/start config))\n\n;; Submit a command (only works on the leader)\n;; new-entry requires a callback function\n(raft/new-entry raft-instance \n                {:op :set :key \"foo\" :value \"bar\"}\n                (fn [success?] \n                  (println \"Entry submitted:\" success?))\n                5000) ; optional timeout in ms\n```\n\n### Multi-Node Cluster\n\nFor a real distributed system, you'll need to implement the networking layer:\n\n```clojure\n;; Example configuration for node 1 of a 3-node cluster\n(def config\n  {:servers          [\"server1\" \"server2\" \"server3\"]\n   :this-server      \"server1\"\n   :send-rpc-fn      my-network-send-fn  ; Your RPC implementation\n   :leader-change-fn handle-leader-change\n   :log-directory    \"/var/raft/server1/logs\"\n   :state-machine    state-machine\n   :heartbeat-ms     150    ; Leader heartbeat interval\n   :timeout-ms       300    ; Election timeout\n   \n   ;; Snapshot functions (see src/fluree/raft/kv_example.clj for full implementation)\n   :snapshot-write   snapshot-writer-fn\n   :snapshot-reify   snapshot-reify-fn\n   :snapshot-install snapshot-install-fn\n   :snapshot-xfer    snapshot-transfer-fn\n   :snapshot-list-indexes snapshot-list-fn})\n```\n\n## Configuration\n\nThe Raft configuration map requires the following options:\n\n### Required Configuration\n\n```clojure\n{:servers          [\"server1\" \"server2\" \"server3\"]  ; List of all servers in cluster\n :this-server      \"server1\"                        ; This server's ID (must be in servers list)\n :send-rpc-fn      (fn [server msg callback] ...)   ; Network layer for sending messages\n :state-machine    (fn [entry raft-state] ...)      ; Your state machine function\n \n ;; Required snapshot functions\n :snapshot-write   (fn [file state] ...)            ; Write state to snapshot file\n :snapshot-reify   (fn [] ...)                      ; Create snapshot from current state\n :snapshot-install (fn [snapshot index] ...)        ; Install received snapshot\n :snapshot-xfer    (fn [snapshot server] ...)}      ; Transfer snapshot to server\n```\n\n### Optional Configuration\n\n```clojure\n{:log-directory         \"raftlog/\"           ; Directory for logs (default: \"raftlog/\")\n :leader-change-fn      (fn [event] ...)      ; Callback for leadership changes\n :close-fn              (fn [] ...)           ; Cleanup function on shutdown\n :snapshot-threshold    100                   ; Entries before creating snapshot (default: 100)\n :heartbeat-ms          100                   ; Leader heartbeat interval in ms (default: 100)\n :timeout-ms            500                   ; Election timeout in ms (default: 500)\n :log-history           10                    ; Number of old log files to keep (default: 10)\n :entries-max           50                    ; Max entries to send at once (default: 50)\n :default-command-timeout 4000               ; Default timeout for commands in ms (default: 4000)\n :catch-up-rounds       10                    ; Rounds to attempt catching up (default: 10)\n :entry-cache-size      nil                  ; Size of entry cache (optional)\n \n ;; Additional snapshot-related options\n :snapshot-list-indexes (fn [dir] ...)       ; List available snapshots\n \n ;; Advanced options (rarely needed)\n :event-chan            async/chan           ; Custom event channel\n :command-chan          async/chan}          ; Custom command channel\n```\n\n## Architecture Overview\n\n### Raft Consensus\n\nThis implementation follows the Raft specification, providing:\n\n- **Leader Election**: Ensures one leader per term using randomized timeouts\n- **Log Replication**: Consistent replication across all nodes\n- **Safety**: Guarantees linearizability and prevents split-brain scenarios\n- **Membership Changes**: Safe cluster reconfiguration\n\n### Key Components\n\n1. **Core State Machine** (`raft.clj`): Main event loop handling all state \n   transitions\n2. **Log Management** (`log.clj`): Persistent, rotating log storage with \n   corruption recovery\n3. **Leader Operations** (`leader.clj`): Replication, heartbeats, and \n   commitment\n4. **Event Processing** (`events.clj`): Common handlers for all server states\n5. **Monitoring** (`watch.clj`): Leadership change notifications\n\n## API Reference\n\n### Core Functions\n\n#### `raft/start`\nStarts a new Raft instance with the given configuration.\n\n```clojure\n(raft/start config) ; =\u003e RaftInstance\n```\n\n#### `raft/new-entry`\nSubmits a new entry to the Raft log (leader only). Requires a callback \nfunction.\n\n```clojure\n;; With default timeout (5000ms)\n(raft/new-entry raft-instance entry callback)\n\n;; With custom timeout\n(raft/new-entry raft-instance entry callback timeout-ms)\n\n;; Example:\n(raft/new-entry raft-instance \n                {:op :set :key \"foo\" :value \"bar\"}\n                (fn [success?] \n                  (if success?\n                    (println \"Entry committed\")\n                    (println \"Entry failed\")))\n                3000)\n```\n\n#### `raft/invoke-rpc`\nGeneral RPC invocation mechanism. Used for operations like adding/removing \nservers.\n\n```clojure\n;; Add a server to the cluster (leader only)\n(raft/invoke-rpc raft-instance :add-server \"server4\" callback-fn)\n\n;; Remove a server from the cluster (leader only)\n(raft/invoke-rpc raft-instance :remove-server \"server2\" callback-fn)\n```\n\n#### `raft/get-raft-state`\nGet the current Raft state asynchronously. Useful for debugging.\n\n```clojure\n(raft/get-raft-state raft-instance \n                     (fn [state] \n                       (println \"Current state:\" (:status state))))\n```\n\n#### `raft/add-leader-watch`\nWatch for leadership changes.\n\n```clojure\n(raft/add-leader-watch raft-instance \n                       :my-watch-key\n                       (fn [leader-info]\n                         (println \"New leader:\" leader-info)))\n```\n\n#### `raft/close`\nCleanly shut down a Raft instance.\n\n```clojure\n(raft/close raft-instance)\n```\n\n#### `raft/monitor-raft`\nDebugging tool to monitor all Raft events.\n\n```clojure\n;; Register a monitoring function\n(raft/monitor-raft raft-instance \n                   (fn [event] \n                     (println \"Raft event:\" event)))\n\n;; Remove monitoring\n(raft/monitor-raft raft-instance nil)\n```\n\n#### `raft/remove-leader-watch`\nRemove a previously registered leader watch.\n\n```clojure\n(raft/remove-leader-watch raft-instance :my-watch-key)\n```\n\n### State Machine Interface\n\nYour state machine function receives the entry and the current raft state:\n\n```clojure\n(defn state-machine \n  [entry raft-state]\n  ;; Process entry and return result\n  ;; The result will be passed to the command callback\n  ;; Typically you'd maintain state in an atom and return success/failure\n  result)\n```\n\n### Snapshot Operations\n\nImplement these functions for snapshot support:\n\n```clojure\n{:snapshot-write   (fn [file state] ...)         ; Write state to snapshot file\n :snapshot-reify   (fn [] current-state)         ; Create snapshot from current state\n :snapshot-install (fn [snapshot index] ...)     ; Install received snapshot\n :snapshot-xfer    (fn [snapshot server] ...)}   ; Transfer snapshot to server\n```\n\n## Examples\n\n### Key-Value Store Example\n\nSee `src/fluree/raft/kv_example.clj` for a complete example implementing a \ndistributed key-value store. This example demonstrates:\n\n- Full networking implementation with RPC\n- Snapshot creation and restoration\n- State machine with multiple operations (read, write, delete, CAS)\n- Multi-node cluster setup\n\n### Monitoring Leadership Changes\n\n```clojure\n(def config\n  {:leader-change-fn (fn [event]\n                       (println \"Leader changed from\" (:old-leader event)\n                                \"to\" (:new-leader event)\n                                \"Event:\" (:event event)))\n   ;; ... other config\n   })\n```\n\n## Development\n\n### Building\n\n```bash\n# Download dependencies\nmake deps\n\n# Run tests\nmake test\n\n# Build JAR\nmake jar\n\n# Run linting\nmake clj-kondo\n\n# Generate coverage report\nmake coverage\n\n# Check for outdated dependencies\nmake ancient\n```\n\n### Running Tests\n\n```bash\n# Run all tests\nmake test\n\n# Run with specific test selectors\nclojure -M:test -i integration\n```\n\n### Docker Support\n\nThe project includes a Dockerfile for containerized deployment:\n\n```bash\n# Build Docker image\ndocker build -t fluree/raft .\n\n# Run with volume for logs\ndocker run -v /var/raft:/var/raft fluree/raft\n```\n\n## Troubleshooting\n\n### Common Issues\n\n**Log Corruption**\n- The library automatically detects and repairs corrupted logs\n- Check logs for: \"Corrupted log entry detected at index X\"\n\n**Leader Election Failures**\n- Ensure network connectivity between nodes\n- Check that election timeouts are properly configured\n- Verify server IDs are unique\n\n**Performance Tuning**\n```clojure\n{:snapshot-threshold 5000      ; Increase for write-heavy workloads\n :heartbeat-ms 50              ; Decrease for faster failure detection\n :timeout-ms 150               ; Decrease for quicker elections\n :entries-max 100}             ; Increase for better throughput\n```\n\n### Debug Logging\n\nEnable debug logging by configuring your logging framework appropriately.\n\n## Resources\n\n- [Raft Paper](https://raft.github.io/raft.pdf)\n- [Raft Website](https://raft.github.io/)\n- [Fluree Documentation](https://docs.flur.ee/)\n\n## Support\n\n- **Issues**: [GitHub Issues](https://github.com/fluree/raft/issues)\n- **Discussions**: [GitHub Discussions](https://github.com/fluree/raft/discussions)\n\n## License\n\nCopyright © 2018-2025 Fluree PBC\n\nDistributed under the Eclipse Public License either version 1.0 or (at\nyour option) any later version.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffluree%2Fraft","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffluree%2Fraft","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffluree%2Fraft/lists"}