AgentSkillsCN

babuza-statemachine

用于配置状态机存储类型。触发关键词: 状态机、babuza 存储类型、内存状态机、磁盘状态机、并发快照。

SKILL.md
--- frontmatter
name: babuza-statemachine
description: |
  Use for configuring state machine storage types. Triggers on:
  state machine, babuza storage type, memory state machine, disk state machine, concurrent snapshot

Babuza State Machine Skill

Package: github.com/fanaujie/babuza/ibabuza

State machine interface implementation for Babuza distributed consensus.

You are an expert at babuza state machine implementation. Help users by:

  • Writing code: Implement the ibabuza.BaseStateMachine interface correctly.
  • Answering questions: Explain state machine requirements, snapshot handling, and concurrency.

Documentation

Refer to the local files for detailed API:

  • ./references/statemachine-api.md - State machine interface and implementation guidelines.

Key Patterns

1. Implementing BaseStateMachine Interface

The state machine must implement ibabuza.BaseStateMachine:

go
import (
    "sync"
    "encoding/json"
    "github.com/fanaujie/babuza/ibabuza"
)

type KVStore struct {
    mu   sync.RWMutex
    data map[string]string
}

// Apply - called serially by Raft, must be deterministic
func (s *KVStore) Apply(e ibabuza.Entry) ibabuza.ApplyResult {
    var cmd Command
    if err := json.Unmarshal(e.Command, &cmd); err != nil {
        return ibabuza.ApplyResult{LogIndex: e.Index, Error: err}
    }
    s.mu.Lock()
    defer s.mu.Unlock()
    s.data[cmd.Key] = cmd.Value
    return ibabuza.ApplyResult{LogIndex: e.Index, Response: "ok"}
}

// Query - may be called concurrently, use proper locking
func (s *KVStore) Query(key any) (any, error) {
    s.mu.RLock()
    defer s.mu.RUnlock()
    return s.data[key.(string)], nil
}

// SaveSnapshot - serialize state to snapshot writer
func (s *KVStore) SaveSnapshot(_ ibabuza.StateMachineSnapshotContext, w ibabuza.StateMachineSnapshotWriter) error {
    s.mu.RLock()
    defer s.mu.RUnlock()

    // Create a file in the snapshot (fileTag identifies the file)
    file, err := w.CreateStateMachineFile("kvdata", 0) // 0 = no compression
    if err != nil {
        return err
    }
    defer file.Close()

    return json.NewEncoder(file).Encode(s.data)
}

// RestoreFromSnapshot - restore state from snapshot reader
func (s *KVStore) RestoreFromSnapshot(r ibabuza.StateMachineSnapshotReader) error {
    s.mu.Lock()
    defer s.mu.Unlock()

    // Open the file from snapshot
    file, _, err := r.Open("kvdata")
    if err != nil {
        return err
    }

    s.data = make(map[string]string)
    return json.NewDecoder(file).Decode(&s.data)
}

// Close - cleanup resources
func (s *KVStore) Close() error { return nil }

2. ApplyResult Structure

go
type ApplyResult struct {
    LogIndex uint64  // The log index that was applied
    Response any     // Result to return to the proposer (NOT "Result")
    Error    error   // Error if apply failed (field, not method)
}

3. Snapshot Writer/Reader Interfaces

IMPORTANT: These are NOT io.Writer/io.Reader directly!

go
// StateMachineSnapshotWriter - use CreateStateMachineFile to get io.WriteCloser
type StateMachineSnapshotWriter interface {
    CreateStateMachineFile(fileTag string, compression babuzapb.SnapshotFileCompressionType) (io.WriteCloser, error)
    AddStateMachineFileMetadata(fileTag string, metadata []byte) error
}

// StateMachineSnapshotReader - use Open to get io.Reader
type StateMachineSnapshotReader interface {
    Open(fileTag string) (io.Reader, StateMachineFileDesc, error)
    Metadata() babuzapb.SnapshotMetadata
}

State Machine Requirements

RequirementDescription
DeterministicApply() must produce same result for same input on all nodes
Thread-safeQuery() may be called concurrently with Apply()
SerializableState must be serializable for snapshots
IdempotentConsider using client sessions for exactly-once semantics

Common Mistakes

  1. Using Result instead of Response in ApplyResult
  2. Using snapshot writer/reader as io.Writer/io.Reader - must call CreateStateMachineFile() and Open()
  3. Non-deterministic Apply() - using time.Now(), random, or external state
  4. Missing locking in Query() when state is shared with Apply()
  5. Calling result.Error() as method - it's a field: result.Error

When Writing Code

  1. Interface: Implement all 5 methods of ibabuza.BaseStateMachine.
  2. Determinism: Never use time, random, or external state in Apply().
  3. Locking: Use sync.RWMutex - write lock in Apply(), read lock in Query().
  4. Snapshot FileTag: Use consistent file tags between SaveSnapshot and RestoreFromSnapshot.
  5. Error Handling: Return errors in ApplyResult.Error field, check with result.Error != nil.

When Answering Questions

  1. Determinism: Explain why all nodes must produce identical state from identical log.
  2. Concurrency: Apply() is serial, Query() may be concurrent.
  3. Recovery: State is rebuilt from latest snapshot + replayed log entries after restart.