Распределённое key-value хранилище с консенсусом Raft на чистом Go.
Выборы лидера, репликация лога, WAL-персистентность, REST API — и кластер за один docker compose up.
docker compose up -d --build # кластер из 3 нод
curl -X PUT localhost:8001/kv/color -d '{"value":"purple"}' -L # запись через любую ноду
curl localhost:8003/kv/color -L # → {"value":"purple"} с ЛЮБОЙ ноды
curl localhost:8002/status # роль, term, commit indexФолловеры отвечают на записи 307 Temporary Redirect на лидера — -L у curl следует автоматически.
клиент ──PUT──▶ follower ──307──▶ LEADER
│ AppendEntries (heartbeat + лог)
┌─────┴─────┐
▼ ▼
follower follower ← кворум 2/3 → commit → apply → FSM(KV)
Пакет raft/ — самодостаточная реализация консенсуса:
| Модуль | Что внутри |
|---|---|
node.go |
Event loop ноды: таймеры, выборы, propose-канал, apply-loop |
rpc.go |
RequestVote/AppendEntries, лог-конфликты, conflict-hints (§5.3) |
persist.go |
Store-интерфейс: stable.json (atomic rename+fsync) + wal.jsonl |
transport.go |
Transport-интерфейс: HTTP (бой) и in-memory mesh (детерминированные тесты) |
fsm.go |
KV state machine с идемпотентным apply |
api.go |
REST: /kv/{key}, /raft/*, /status, leader redirect |
Ключевые свойства реализации:
- Корректность по статье: сравнение свежести логов при голосовании (§5.4.1), коммит только записей текущего терма (§5.4.2), один голос на терм (персистентно)
- Snapshots + log compaction (§7): автоматические по порогу записей,
InstallSnapshotдля отставших фолловеров, восстановление при рестарте - Membership changes: add/remove нод на лету через config-записи (single-server variant с pending-гардом; исключаемые серверы добиваются grace-периодом; голоса от серверов вне состава игнорируются)
- Prometheus
/metrics: роль, терм, commit/applied, выборы, предложения, RPC-исходы, снапшоты - noop-запись при избрании лидера — подтверждение лидерства и продвижение коммита
- Быстрый откат
nextIndexчерез conflict term/index вместо пошагового декремента - fsync на каждую запись в WAL; рестарт ноды восстанавливает term/vote/лог (проверено тестом)
go test ./... -race -count=116 сценариев под race-детектором на in-memory mesh с управляемыми разрывами сети:
- выборы ровно одного лидера; фолловеры знают его адрес
- репликация
setна все ноды + FSM-apply - перевыборы после падения лидера с ростом терма
- старый лидер отступает после исцеления сети
- регрессия «term churn»: лидер стабилен дольше таймаута
- unit: отказ устаревшему терму, отказ кандидату со старым логом, отказ кандидату вне состава
- snapshots: компакция лога, догоняющий фолловер через
InstallSnapshot, рестарт из снапшота - membership: join новой ноды с репликацией, удаление с retire, pending-гард
- WAL: roundtrip, truncate, восстановление ноды после рестарта
go run ./cmd/raftkv-dashboard╭─ raftkv cluster 14:32:07 · обновление 1s · Ctrl+C — выход
NODE ROLE TERM COMMIT APPLIED LOG KEYS LEADER
n1 follower 3 42 42 42 17 n2
n2 leader ★ 3 42 42 42 17 self
n3 follower 3 42 42 42 17 n2
go run ./cmd/raftkvd -id n1 -peers "n1=127.0.0.1:8001,n2=127.0.0.1:8002,n3=127.0.0.1:8003" -data ./data/n1 &
go run ./cmd/raftkvd -id n2 -peers "n1=127.0.0.1:8001,n2=127.0.0.1:8002,n3=127.0.0.1:8003" -data ./data/n2 &
go run ./cmd/raftkvd -id n3 -peers "n1=127.0.0.1:8001,n2=127.0.0.1:8002,n3=127.0.0.1:8003" -data ./data/n3 &- Snapshots + log compaction + InstallSnapshot
- Membership changes (add/remove ноды на лету)
- Prometheus
/metrics, TUI-дашборд кластера - Chaos-suite «Jepsen-lite» (разрывы/падения в compose + проверка линейизуемости)
- Бенчмарки ops/sec по кворуму
MIT — see LICENSE.