January 25, 2022·6 min read·engineeringrqlitediscoveryclustering

rqlite uses Consul and etcd to find a cluster leader

rqlite 7.0.0 adds discovery through Consul and etcd key-value stores. The integration helps nodes form and join a cluster while retaining a separate role for rqlite's own consensus system.

A new database node needs an address it can contact before it can become part of a cluster. Hard-coding one existing node's address makes that first contact depend on the availability and continued identity of that machine. rqlite 7.0.0, released on January 25, adds automatic clustering through Consul and etcd. The integrations let nodes discover the leader through a shared key-value service.

This is a change to cluster formation and joining. It does not replace rqlite's database consensus with the external discovery service. The distinction is visible in the implementation, which asks the store whether it is leader and reports that identity to discovery. A lookup helps another node reach the cluster, while rqlite retains responsibility for deciding its own leadership.

I value rqlite's proportional approach to distributed relational storage. SQLite supplies the local engine, and the surrounding system adds coordination where the application needs it. Automatic discovery fits that approach when an operator already has Consul or etcd, because it reduces the need to maintain a fixed list of live bootstrap addresses.

Nodes share a discovery configuration

The versioned automatic-clustering guide gives equivalent configurations for several nodes. Each has its own HTTP and Raft addresses, but all use the same discovery mode and external service configuration. The guide says the nodes can start in any order and that later nodes can join through the same mechanism.

For example, a node using a Consul service at the documented example address can start with this form. The HTTP and Raft addresses below are illustrative addresses belonging to this node.

rqlited -http-addr=10.0.0.11:4001 -raft-addr=10.0.0.11:4002 \
  -disco-mode consul-kv \
  -disco-config '{"address":"example.com:8500"}' data

The corresponding etcd configuration uses etcd-kv and an endpoints array. These are different client configurations for the same operational purpose. The node still needs addresses other cluster members can use for its HTTP and Raft communication. Discovering a node cannot compensate for advertising an unreachable address.

The guide permits discovery configuration as a JSON string or a path to a JSON configuration file. This separates a short example from a deployment's more extensive settings. It also gives operators a way to manage connection configuration without encoding every field in a process argument. The documented Docker examples use the same discovery modes, so containerization does not introduce a different discovery protocol. The process still reads the selected configuration and uses the external key-value service to obtain its initial cluster contact.

Initial registration has two possible outcomes

The discovery service implementation defines a client with operations to get the leader, initialize a leader record, and set a leader record. Its registration loop first attempts to read an existing leader. If it finds one, it returns that leader's API address for joining.

When no leader record is available, registration attempts to initialize one. A successful initialization returns an outcome identifying this node as the initializer. Otherwise the node waits and repeats the process. These outcomes let multiple starting processes use the same configuration while determining whether to establish the first contact record or use one already present.

The guide explains that updates use check-and-set behavior. A node changes the leader key only if its value has not changed since the node read it. This prevents competing updates from blindly overwriting a newer value based on stale observations. That property belongs to maintaining the discovery record, not to executing SQL transactions.

The service loop logs errors from reads and initialization attempts. It then retries when it has not reached a usable outcome. Its delay uses a configured interval plus a randomized component. That reduces exact synchronization between processes repeatedly attempting the same operation.

This retry behavior is part of why the integration is more than replacing one address string with another. Startup has to cope with concurrent nodes and temporary failure of the discovery endpoint. A process can keep trying to obtain useful cluster information while the external service or another node becomes available.

Leader reporting continues after startup

The service registers for leadership-change notifications from the store. When notified, it checks whether the local store is leader before updating discovery. It also reports periodically, allowing a later update to repair information that became stale during an intermittent problem.

That division makes the direction of authority clear. Discovery does not tell the store to become leader on every reporting cycle. The service asks the store for its current role and publishes its address if appropriate. A newly joining node then uses the published information to find the existing cluster.

The versioned guide emphasizes this continued reporting. A leader change should not require an operator to rewrite the discovery configuration on every future node. The key identifies the cluster's current entry point, allowing the addresses behind that entry point to change over time.

For operational inspection, the service exposes diagnostic information including the client name, registration interval, reporting interval, and last-contact time. These fields describe interaction with discovery. They should be interpreted alongside the store's actual state, because contact with an external registry and a functioning database cluster are different observations.

A discovery key separates clusters

The guide provides -disco-key for running several rqlite clusters against one Consul or etcd system. Each cluster uses a different value. This is a namespace decision that determines which leader record a node reads and updates.

Sharing an external service therefore does not require sharing one database cluster. Nodes intended to form the same cluster need the same discovery namespace. Nodes belonging to independent clusters need different namespaces. The configuration carries that intended grouping before the nodes have any local database membership history.

This is useful for deployment automation because the command can remain structurally identical while configuration supplies cluster identity. It also means that an apparently small key change can redirect a fresh node toward a different cluster. The key is operational identity, not a cosmetic label attached after joining.

The design keeps the external service's role narrow. It stores the address information needed for contact and initialization. rqlite's own store supplies leadership state for continued reporting. That makes the boundary between discovery configuration and database operation inspectable in a small amount of code.

Upgrading changes the old discovery path

The 7.0.0 changelog says the legacy discovery mechanism is not supported in this release. It directs users who need that mechanism to remain on 6.x or earlier, while leaving open whether support might return later. The new major version therefore has a configuration migration requirement for deployments using legacy discovery.

The same changelog removes -RaftWaitForLeader. It directs callers needing to wait for a leader to poll /readyz. This moves readiness observation into a separate operation that automation can repeat. A started process and a node ready to serve its intended role need not be the same state.

Version 7.0.0 also changes the database used by the Raft system, replacing BoltDB with the etcd-maintained bbolt fork. The changelog expects compatibility with earlier releases but recommends backing up existing leader nodes before upgrading. That storage change is separate from selecting Consul or etcd as discovery clients. Similar project names should not obscure their different roles in the release.

Discovery configuration, readiness observation, and local consensus storage each affect an upgrade for different reasons. The release groups them in a major version, and the tagged documentation makes their scope explicit. Operators using the new discovery path need to change arguments deliberately rather than assume every existing invocation continues unchanged.

Discovery reduces one kind of deployment coupling

The practical gain is the ability to launch a node with a stable registry configuration while the live cluster's entry address changes. The leader reporting loop keeps that registry useful after initial formation. A deployment still needs reachable node addresses and a discovery service it can contact, but it no longer needs every new process to know one permanently designated bootstrap machine.

I consider the integration useful because its responsibilities remain bounded. Consul or etcd supplies a shared discovery record, and rqlite's store supplies the leadership state reported into it. The implementation connects these two systems without making a registry lookup stand in for the database's consensus decisions. A new node can find the cluster through the shared key and then participate through rqlite's existing cluster mechanisms.