PHALANXconsensus engine
v1.0.0

architecture

how phalanx moves data from a client request to a committed, replicated state change.

multi-threaded concurrency

Phalanx operates on a concurrent handler model. While the core consensus logic remains sequential and deterministic, the access to the state machine is multi-threaded. By decoupling gRPC network handling from background tasks, we enable vertical scaling and parallel processing of client requests.

Concurrency is managed via a sync.RWMutex. Incoming gRPC handlers execute in their own goroutines, locking the mutex only while interacting with the Raft core.

componentconcurrencyaction
HandleAppendEntriesgRPC goroutineacquires Lock() to step the state machine
HandleProposegRPC goroutineacquires Lock() to append to log
HandleReadgRPC goroutineacquires RLock() for parallel quorum checks
Background Rundedicated threadacquires Lock() for ticks and discovery
// node.go — the simplified background loop
 for {
     select {
     case <-ctx.Done():
         return n.shutdown()
 
     case <-ticker.C:
         n.mu.Lock()
         n.raft.Tick()
         n.applyCommitted()
         n.persistState()
         n.dispatchMessages()
         n.mu.Unlock()
 
     case event := <-n.discoveryEvents():
         n.mu.Lock()
         n.handleDiscoveryEvent(event)
         n.mu.Unlock()
     }
 }

the pure state machine pattern

raft.go does not know the network exists. It has no imports of net, no time.Now(), no goroutines. When a message is processed via Step(msg), the state machine appends outgoing messages to an internal buffer. The caller (Node) is responsible for the actual wire delivery:

// The Raft state machine produces messages.
// The Node dispatches them over gRPC.
msgs := raft.Messages()
for _, m := range msgs {
    go transport.Send(m)
}

This design allows Phalanx to run 1,000+ consensus rounds in a unit test in under 10ms, as no real time passes and no network overhead exists. The state machine is fully deterministic — given the same sequence of Tick() and Step() calls, it produces identical outputs regardless of wall-clock time.

system topology

gRPC :9000N=5 Q=3 · tolerates 2 region failuresClient (CLI)Node 0JNB · JohannesburgNode 1LHR · LondonNode 2ORD · ChicagoLEADERNode 3SIN · SingaporeNode 4FRA · FrankfurtRaftRaftRaftRaftRaftKV FSMKV FSMKV FSMKV FSMKV FSMBadgerDBBadgerDBBadgerDBBadgerDBBadgerDBSWIM Gossip Mesh · 5 Regions

phalanx global mesh — 5-node consensus cluster across 5 continents

data flow

write path (propose)

Client
   → gRPC Propose(data)
   → HandlePropose() goroutine
   → n.mu.Lock()
   → raft.Propose(data)
   → n.mu.Unlock()
   → broadcastHeartbeat → AppendEntries
   → majority ack → commitIndex advances
   → applyCommitted() → fsm.Apply(SET key=value)
   → signal doneCh
   → respond to client: success

read path (linearizable)

Client
   → gRPC Read(key)
   → HandleRead() goroutine
   → n.mu.RLock()
   → HasLeaderQuorum() → verify majority lease
   → n.mu.RUnlock()
   → fsm.Get(key)
   → respond to client: value

component boundaries

packageresponsibilityknows about
raft/consensus logicnothing (pure state machine)
network/gRPC transportpb/ types only
storage/BadgerDB persistencepb/ types only
fsm/KV state machinenothing
discovery/SWIM gossipnothing
node.goconcurrency orchestratoreverything

every package except node.go is independently testable with zero dependencies on other Phalanx packages. this is by design.