refactor: extract clustering system to cluster package

- Create cluster/merkle.go with Merkle tree operations
- Create cluster/gossip.go with gossip protocol implementation
- Create cluster/sync.go with data synchronization logic
- Create cluster/bootstrap.go with cluster joining functionality

Major clustering functionality now properly separated:
* MerkleService: Tree building, hashing, filtering
* GossipService: Member discovery, health checking, list merging
* SyncService: Merkle-based synchronization between nodes
* BootstrapService: Seed node joining and initial sync

Build tested and verified working. Ready for main.go integration.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-09-18 18:53:52 +03:00
parent c273b836be
commit 9f12f3dbcb
4 changed files with 1013 additions and 0 deletions

145
cluster/bootstrap.go Normal file
View File

@@ -0,0 +1,145 @@
package cluster
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"time"
"github.com/sirupsen/logrus"
"kvs/types"
)
// BootstrapService handles cluster joining and initial synchronization
type BootstrapService struct {
config *types.Config
gossipService *GossipService
syncService *SyncService
logger *logrus.Logger
setMode func(string) // Callback to set server mode
}
// NewBootstrapService creates a new bootstrap service
func NewBootstrapService(config *types.Config, gossipService *GossipService, syncService *SyncService, logger *logrus.Logger, setMode func(string)) *BootstrapService {
return &BootstrapService{
config: config,
gossipService: gossipService,
syncService: syncService,
logger: logger,
setMode: setMode,
}
}
// Bootstrap joins cluster using seed nodes
func (s *BootstrapService) Bootstrap() {
if len(s.config.SeedNodes) == 0 {
s.logger.Info("No seed nodes configured, running as standalone")
return
}
s.logger.Info("Starting bootstrap process")
s.setMode("syncing")
// Try to join cluster via each seed node
joined := false
for _, seedAddr := range s.config.SeedNodes {
if s.attemptJoin(seedAddr) {
joined = true
break
}
}
if !joined {
s.logger.Warn("Failed to join cluster via seed nodes, running as standalone")
s.setMode("normal")
return
}
// Wait a bit for member discovery
time.Sleep(2 * time.Second)
// Perform gradual sync (now Merkle-based)
s.performGradualSync()
// Switch to normal mode
s.setMode("normal")
s.logger.Info("Bootstrap completed, entering normal mode")
}
// attemptJoin attempts to join cluster via a seed node
func (s *BootstrapService) attemptJoin(seedAddr string) bool {
joinReq := types.JoinRequest{
ID: s.config.NodeID,
Address: fmt.Sprintf("%s:%d", s.config.BindAddress, s.config.Port),
JoinedTimestamp: time.Now().UnixMilli(),
}
jsonData, err := json.Marshal(joinReq)
if err != nil {
s.logger.WithError(err).Error("Failed to marshal join request")
return false
}
client := &http.Client{Timeout: 10 * time.Second}
url := fmt.Sprintf("http://%s/members/join", seedAddr)
resp, err := client.Post(url, "application/json", bytes.NewBuffer(jsonData))
if err != nil {
s.logger.WithFields(logrus.Fields{
"seed": seedAddr,
"error": err.Error(),
}).Warn("Failed to contact seed node")
return false
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
s.logger.WithFields(logrus.Fields{
"seed": seedAddr,
"status": resp.StatusCode,
}).Warn("Seed node rejected join request")
return false
}
// Process member list response
var memberList []types.Member
if err := json.NewDecoder(resp.Body).Decode(&memberList); err != nil {
s.logger.WithError(err).Error("Failed to decode member list from seed")
return false
}
// Add all members to our local list
for _, member := range memberList {
if member.ID != s.config.NodeID {
s.gossipService.AddMember(&member)
}
}
s.logger.WithFields(logrus.Fields{
"seed": seedAddr,
"member_count": len(memberList),
}).Info("Successfully joined cluster")
return true
}
// performGradualSync performs gradual sync (Merkle-based version)
func (s *BootstrapService) performGradualSync() {
s.logger.Info("Starting gradual sync (Merkle-based)")
members := s.gossipService.GetHealthyMembers()
if len(members) == 0 {
s.logger.Info("No healthy members for gradual sync")
return
}
// For now, just do a few rounds of Merkle sync
for i := 0; i < 3; i++ {
s.syncService.performMerkleSync()
time.Sleep(time.Duration(s.config.ThrottleDelayMs) * time.Millisecond)
}
s.logger.Info("Gradual sync completed")
}