Ra is a Raft implementation that avoids process tree explosion
A Multi-Raft implementation for Erlang and Elixir that strives to be efficient and make it easier to use multiple Raft clusters in a single system.
At a glance
- What is it?
- Ra is the Raft consensus library built by the RabbitMQ team for Erlang and Elixir. It is not tied to RabbitMQ and can be used in any Erlang or Elixir project, but it was written for RabbitMQ's needs, and the two design goals that follow from that are a low resource footprint and the ability to run thousands of Ra clusters inside a single Erlang node.
- Who is it for?
- Ra fits a team building a replicated state machine in Erlang or Elixir who wants a Raft library that is not embedded in a broker, and the maturity statement is unusually specific, with the primary APIs and the on disk formats guaranteed backwards compatible in line with semantic versioning.
- Can I use it commercially?
- Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
- Is it still maintained?
- Yes. The repository last received commits 1 day ago.
- What is it written in?
- Mainly Erlang, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A consensus library, not a broker plugin
Ra is a Raft implementation by Team RabbitMQ, and the first thing the README says is that it is not tied to RabbitMQ and can be used in any Erlang or Elixir project, while being heavily inspired by and geared towards RabbitMQ needs. That framing is worth taking literally. Because Ra is a Raft implementation, it is a library that lets you build persistent, fault tolerant and replicated state machines, which is the state machine replication pattern, and it exposes that as a library rather than as a feature of a particular broker. The library is published on Hex.pm as ra, so it is consumable from both Erlang and Elixir projects, and the source tree carries both an erlang.mk based build and a rebar.config, so it fits either toolchain convention.
What is implemented, and the maturity claim
The status list names the Raft features that are implemented, and it is short. Leader election. Log replication. Cluster membership changes, one server or member at a time. Log compaction, with limitations and RabbitMQ specific extensions. And snapshot installation. The membership constraint is the one to notice, since it means a cluster cannot add or replace several members at once. The maturity statement is more interesting than the feature list. The library has been extensively tested and is suitable for production use, which means the primary APIs, the ra and ra_machine modules, and the on disk formats will be backwards compatible going forwards in line with semantic versioning, and care has been taken to version all on disk data formats to enable frictionless future upgrades. For a library that writes data to disk and reads it back during recovery, that guarantee is the part that matters most, because a format change without versioning is how a cluster loses its state.
Design goals: thousands of clusters in one node
Three design goals are stated, and they explain the shape of the code. The first is a low footprint, meaning using as few resources as possible and avoiding process tree explosion. The second is being able to run thousands of ra clusters within an Erlang node, which is an unusual and very specific target. The third is to provide adequate performance for use as a basis for a distributed data service. Together they say that Ra is designed to be embedded many times over rather than to be a single cluster in a process, and that is why the footprint goal is stated first. An Erlang node is already a process tree, and a Raft implementation that spawns a tree per cluster would multiply badly, so the library is careful about how much supervision and how many processes each cluster instance costs.
What it was written to replace
The library was primarily developed as the foundation of a replication layer for quorum queues in RabbitMQ, and today it also powers RabbitMQ streams and Khepri. The design it aims to replace uses a variant of chain based replication, and the README names two major shortcomings of that approach. The replication algorithm is linear, so every write travels the same path, and the failure recovery procedure requires expensive topology changes. Both of those are the kind of limitation a Raft implementation removes, since a leader is elected and the log is replicated in parallel to followers, and a failed member is replaced by changing membership rather than by rebuilding the chain. That is the context for the one-member-at-a-time restriction on membership changes, since replacing a failed member still requires a new member to catch up before the old one is removed.
The smallest possible example
The quick start assumes familiarity with distributed Erlang and three nodes on the local machine, named ra1, ra2 and ra3 at a hostname, which the documentation notes is not a Ra requirement. Each node is started with a rebar3 shell invocation:
``` shell # replace hostname.local with your actual hostname rebar3 shell --name [email protected]
The example then checks distribution connectivity with a ping before doing anything else, because Ra nodes cannot cluster if the Erlang nodes cannot talk to each other, starts the Ra application on every node, creates server ids, defines a state machine as a function, and starts the cluster from one designated node. The interesting line is the cluster start:
erlang {ok, ServersStarted, _ServersNotStarted} = ra:start_cluster(default, ClusterName, Machine, ServerIds), ```
The comment above it notes that repeated startup attempts will fail even if the cluster is formed, has elected a leader and is fully functional, which is a useful warning for a script that retries. Commands are then issued with ra:process_command, passing the leader id returned by the previous command, and the simple state machine in the example always returns the full state after each operation.
Local queries against leader queries
The querying section exists because a replicated state machine is only useful if you can read it, and there are two ways to do that with different trade-offs. Local queries return machine state on the target node, and leader queries return machine state from the leader node, where a query sent to a follower is redirected to the leader. The documentation is explicit about the cost. Local queries are much more efficient but can return out of date machine state, and leader queries offer the best possible machine state consistency but potentially require sending a request to a remote node. A leader query is made with the leader query function:
``` erlang {ok, _Members, LeaderId} = ra:members(quick_start), %% perform a leader query on the leader node QueryFun = {ra_lib, id, []}, {ok, {_TermMeta, State}, LeaderId1} = ra:leader_query(LeaderId, QueryFun). ```
and a local query with the local query function, against the replica hosted on the current node, which is found by looking up the current node in the member list. The choice between them is a consistency decision rather than an API preference, and for a read-heavy path the efficiency of the local query is usually worth the staleness.
Verification, OTP versions and the build
Two things signal how seriously the project takes correctness. Ra is continuously tested with the Jepsen distributed system verification framework, which is the standard way to show that a consensus implementation survives the interleavings its authors did not think of, and the link in the README points at the Jepsen test setup of the RabbitMQ key value store. The second is the version policy, where Ra supports Erlang and OTP 27.x and 26.x, and the reason given is that modern Erlang releases provide distribution traffic fragmentation, which algorithms such as Raft significantly benefit from. The build system reflects an Erlang project that is tested properly. The Makefile pulls in the elvis style checker, lists proper, meck and inet_tcp_proxy as test dependencies, configures Dialyzer with the test sources included, runs EUnit with the report and profiling options, and excludes the mock and benchmark modules from coverage, which is the usual way to keep a coverage number honest.
Editorial conclusion
Ra fits a team building a replicated state machine in Erlang or Elixir who wants a Raft library that is not embedded in a broker, and the maturity statement is unusually specific, with the primary APIs and the on disk formats guaranteed backwards compatible in line with semantic versioning. It does not fit a team that needs multi-server membership changes, since membership changes are implemented one server at a time, and it does not fit a project on an older OTP release, since only 27.x and 26.x are supported. Before you adopt it, read the design goals, because a library that promises to run thousands of clusters in one node is a different shape of library from one that assumes a handful, and note the release state, with v3.2.0 published on 2026-09-01 and the last push on 2026-10-19.
Frequently asked questions
What is Ra?
It is a Raft implementation by the RabbitMQ team for Erlang and Elixir, published on Hex.pm as ra. It is not tied to RabbitMQ and can be used in any Erlang or Elixir project, but it was written to be the foundation for RabbitMQ quorum queues, and it now also powers RabbitMQ streams and Khepri.
Which Raft features does Ra implement?
Leader election, log replication, cluster membership changes one server at a time, log compaction with limitations and RabbitMQ specific extensions, and snapshot installation. The design it replaces, a variant of chain based replication, had a linear replication algorithm and needed expensive topology changes for failure recovery.
Is Ra safe to use in production?
The README states that the library has been extensively tested and is suitable for production use, which means the primary APIs, the ra and ra_machine modules, and the on disk formats will be backwards compatible going forwards in line with semantic versioning, with all on disk data formats versioned to allow frictionless upgrades.
When should I use a local query instead of a leader query?
Local queries return machine state on the target node and are much more efficient, but they can return out of date state. Leader queries return state from the leader, are redirected there if you ask a follower, and give the best possible consistency at the cost of possibly sending a request to a remote node.
Is Ra still updated?
The repository is not archived and the last push was on 2026-10-19, with v3.2.0 published on 2026-09-01 after v3.1.10 in July 2026 and v3.1.9 in the same month. Ra supports Erlang and OTP 27.x and 26.x, and the licence files in the repository are a custom set that includes Apache 2 and an MPL for RabbitMQ.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/rabbitmq-ra)Was this useful?