Babuza Raft Skill
Package:
github.com/fanaujie/babuza/raftCore consensus layer wrapping etcd Raft with cluster bootstrap and management APIs.
You are an expert at the babuza/raft package. Help users by:
- •Writing code: Generate Go code for cluster bootstrapping, membership changes, and consistency guarantees.
- •Answering questions: Explain Raft internals, consistency models, and recovery procedures.
Documentation
Refer to the local files for detailed documentation:
- •
./references/raft-lifecycle.md- API reference, configuration options, and lifecycle details.
Key Patterns
1. Bootstrapping a New Cluster
Start a fresh cluster with initial peers.
go
import (
"github.com/fanaujie/babuza/ibabuza"
"github.com/fanaujie/babuza/pkg/builder"
"github.com/fanaujie/babuza/raft"
)
func bootstrapCluster(stateMachine ibabuza.BaseStateMachine) (*raft.Raft, error) {
// 1. Build components
component := builder.NewBabuzaComponentBuilder(&builder.BabuzaComponentConfig{
ClusterId: 1,
StorageRootDir: "/tmp/node1",
TransportType: builder.HttpTransport,
WalType: builder.BabuzaWal,
SessionType: builder.NoOpSession,
SnapshotType: builder.DurableSnapshot,
}).Build()
// 2. Configure Node
cfg := raft.DefaultBabuzaConfig(1, 1, "127.0.0.1:9001") // ClusterID, PeerID, Addr
// 3. Configure Peers
peers := raft.NewPeersConfiguration()
peers.AddPeer(1, "127.0.0.1:9001", false) // id, addr, isLearner
// 4. Create Bootstrap Context
bootstrap, err := raft.NewBootstrapRaftCluster(
cfg, *peers, stateMachine,
component.Cluster, component.RaftNode, component.SessionManager,
component.SnapshotManager, component.WalManager, component.Transport,
component.Logger, component.MetricsController,
)
if err != nil {
return nil, err
}
// 5. Start Raft
return raft.NewRaft(cfg, bootstrap, nil)
}
2. Joining an Existing Cluster
Join a node to an existing cluster.
go
func joinCluster(stateMachine ibabuza.BaseStateMachine) (*raft.Raft, error) {
component := builder.NewBabuzaComponentBuilder(&builder.BabuzaComponentConfig{
ClusterId: 1,
StorageRootDir: "/tmp/node2",
TransportType: builder.HttpTransport,
WalType: builder.BabuzaWal,
SessionType: builder.NoOpSession,
SnapshotType: builder.DurableSnapshot,
}).Build()
// Configure Node with Join = true
cfg := raft.DefaultBabuzaConfig(1, 2, "127.0.0.1:9002")
cfg.Join = true
// Self as learner initially
peers := raft.NewPeersConfiguration()
peers.AddPeer(2, "127.0.0.1:9002", true) // isLearner = true
bootstrap, err := raft.NewBootstrapRaftCluster(
cfg, *peers, stateMachine,
component.Cluster, component.RaftNode, component.SessionManager,
component.SnapshotManager, component.WalManager, component.Transport,
component.Logger, component.MetricsController,
)
if err != nil {
return nil, err
}
return raft.NewRaft(cfg, bootstrap, nil)
}
3. Linearizable Read
Read data with strong consistency (ensures leadership and applied index).
go
func (s *Service) Get(key string) (string, error) {
// 1. Wait for Read Index
if err := s.raft.LinearizableRead(context.Background()); err != nil {
return "", err
}
// 2. Query Local State Machine safely
val, err := s.raft.GetStateMachine().Query(key)
if err != nil {
return "", err
}
return val.(string), nil
}
4. Cluster Membership Changes
Add or remove nodes dynamically.
go
import "github.com/fanaujie/babuza/ibabuza/babuzapb"
// Add a new voting peer - returns ProposedResult
peerAttr := babuzapb.RaftPeerAttribute{
PeerID: 3,
RaftListenAddr: "192.168.1.3:9001",
IsLearner: false,
}
proposed := r.AddVotingPeer(ctx, raft.ClientSession{}, peerAttr)
defer proposed.Release()
result := proposed.WaitForApplyResult()
if result.Error != nil {
// handle error
}
// Remove a peer
proposed := r.RemovePeer(ctx, raft.ClientSession{}, peerID)
defer proposed.Release()
result := proposed.WaitForApplyResult()
// Transfer Leadership
transferResult := r.TransferLeader(ctx, targetPeerID)
transferResult.Wait()
5. Checking Cluster Status
go
status := r.Status()
fmt.Printf("Node ID: %d\n", status.LocalPeerID)
fmt.Printf("Leader ID: %d\n", status.LeaderID)
fmt.Printf("Term: %d\n", status.RaftTerm)
fmt.Printf("State: %s\n", status.State) // Follower, Candidate, Leader
fmt.Printf("Applied: %d\n", status.RaftAppliedIndex)
fmt.Printf("Committed: %d\n", status.RaftCommittedIndex)
fmt.Printf("Is Leader: %v\n", status.IsLeader())
Status Fields
| Field | Description |
|---|---|
LocalPeerID | This node's ID (NOT ID) |
LeaderID | Current leader's ID (NOT Lead) |
RaftTerm | Current Raft term (NOT Term) |
State | Raft state: Follower/Candidate/Leader (NOT RaftState) |
RaftAppliedIndex | Last applied log index (NOT Applied) |
RaftCommittedIndex | Last committed log index (NOT Commit) |
IsLeader() | Returns true if this node is the leader |
API Reference Table
| Method | Description |
|---|---|
NewBootstrapRaftCluster | Prepares the environment for starting Raft. |
NewRaft | Starts the Raft node. |
Propose | Submits a command to the log. Returns ProposedResult. |
LinearizableRead | Ensures the local state is up-to-date with the leader. |
AddVotingPeer | Adds a voting peer. Returns ProposedResult. |
AddLearner | Adds a learner (non-voting) peer. Returns ProposedResult. |
PromoteLearner | Promotes a learner to voter. Returns ProposedResult. |
RemovePeer | Removes a peer from cluster. Returns ProposedResult. |
TransferLeader | Transfers leadership to another node. |
Status | Returns current cluster status. |
Shutdown | Gracefully shuts down Raft. Returns ShutdownResult. |
ProposedResult Pattern
All write operations return ProposedResult:
go
proposed := r.Propose(ctx, raft.ClientSession{}, data)
defer proposed.Release() // IMPORTANT: Always release!
result := proposed.WaitForApplyResult()
if result.Error != nil { // Error is a FIELD, not a method
// handle error
}
// Use result.Response for the return value
When Writing Code
- •Sessions: Use
raft.ClientSession{}for proposals. For exactly-once semantics, configure session manager (LRU/Expire). - •Context: Use
context.WithTimeoutfor Raft operations to avoid hanging on network partitions. - •Release ProposedResult: Always call
proposed.Release()after using aProposeresult. - •Error Field: Check
result.Error(field), notresult.Error()(not a method). - •Status Fields: Use
LocalPeerID,LeaderID,RaftTerm,State- not the abbreviated names.
Related Skills
- •
babuza-session- Session manager configuration (LRU, Expire, NoOp) - •
babuza-transport- Network transport configuration (TCP, HTTP, gRPC) - •
babuza-snapshot- Snapshot storage configuration (Durable, S3, Volatile) - •
babuza-wal- Write-Ahead Log configuration (Babuza, Badger, Pebble) - •
babuza-statemachine- State machine implementation - •
babuza-metrics- Observability configuration (Prometheus, OpenTelemetry)
When Answering Questions
- •Consistency: Explain that
LinearizableReadavoids stale reads by confirming leadership with a quorum. - •Safety: Membership changes use
AddLearner->PromoteLearnerfor safety, orAddVotingPeerfor direct add. - •Recovery: Mention
RecoverAsStandalonefor disaster recovery scenarios.