You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This document proposes architectural changes to Gorums that enable symmetric use of Configuration and Node abstractions on both client and server sides. This allows server replicas to communicate with connected clients (and other replicas) using the same API that clients use to communicate with servers.
Key benefits:
Replicas can broadcast to connected clients without separate outbound connections
Enables all-to-all communication patterns for distributed protocols
Simplifies implementation of consensus protocols like Paxos
Motivation
Current Limitation
In the current Gorums architecture:
Clients create a Manager → Configuration → Node hierarchy to communicate with server replicas
Servers can only respond to client requests; they cannot initiate communication
This creates awkwardness when implementing protocols like Paxos where each replica must communicate with all other replicas:
// Current: Each replica runs BOTH a client and serverfuncNewPaxosReplica(myAddrstring, peerAddrs []string) *Replica {
// Server sidesrv:=gorums.NewServer()
lis, _:=net.Listen("tcp", myAddr)
// Client side - creates N-1 OUTBOUND connectionsmgr:= gorums.NewManager(...)
cfg, _:=gorums.NewConfiguration(mgr, gorums.WithNodeList(peerAddrs))
// Problem: peers also create outbound connections to us// Result: 2*(N-1) connections instead of N-1
}
Proposed Solution
Enable servers to obtain a Configuration of connected clients, using the same API as clients:
// Proposed: Server can use Configuration of connected clientsfunc (s*PaxosServer) BroadcastPrepare(slotuint64) {
cfg:=s.sys.InboundConfig()
cfgCtx:=cfg.Context(context.Background())
pb.Multicast(cfgCtx, &PrepareMsg{Slot: slot})
}
Architecture
Current Architecture
The Channel is an exported type in internal/stream.
The wire protocol is defined in stream.proto using a single bidi NodeStream per node.
All RPC methods are multiplexed over the stream, identified by the method field.
graph TB
subgraph "gorums package — Client Side"
Manager --> Config[Configuration]
Config --> Node1["Node"]
Config --> Node2["Node"]
Node1 -->|"*stream.Channel"| Ch1[Channel]
Node2 -->|"*stream.Channel"| Ch2[Channel]
Ch1 --> Conn1["grpc.ClientConn"]
Ch2 --> Conn2["grpc.ClientConn"]
end
subgraph "gorums package — Server Side"
Server --> SS[streamServer]
SS -->|"implements"| GRPC
SS --> SrvStream["Gorums_NodeStreamServer"]
end
subgraph "internal/stream package"
Channel["Channel (exported)"]
Request["Request struct"]
Message["stream.Message (protobuf)"]
NodeResponse["NodeResponse[T]"]
GRPC["Gorums gRPC service"]
end
Ch1 -.->|"bidi NodeStream"| SrvStream
Ch2 -.->|"bidi NodeStream"| SrvStream
Loading
Proposed Architecture (symmetric configuration)
The key addition is an InboundManager struct that manages server-side peer state independently from Server.
Inbound channels are created from server-side streams, and the manager provides a live Configuration.
graph TB
subgraph "internal/stream package"
OutCh["Channel (outbound)"]
InCh["Channel (inbound)"]
BidiStream["BidiStream interface"]
OutCh -->|"implements sender/receiver"| BidiStream
InCh -->|"implements sender/receiver"| BidiStream
end
Loading
graph TB
subgraph "gorums package — Replica A"
direction TB
ServerA["Server"] --> SSA["streamServer"]
ServerA -->|"embeds"| IMA["InboundManager"]
IMA --> InboundCfgA["Configuration (auto-updated)"]
InboundCfgA --> InNodeB["Node (inbound from B)"]
InboundCfgA --> InNodeC["Node (inbound from C)"]
InNodeB --> InChB["Channel (inbound)"]
InNodeC --> InChC["Channel (inbound)"]
MgrA["Manager"] --> CfgA["Configuration (outbound)"]
CfgA --> OutNodeB["Node (outbound to B)"]
CfgA --> OutNodeC["Node (outbound to C)"]
OutNodeB --> OutChB["Channel (outbound)"]
OutNodeC --> OutChC["Channel (outbound)"]
end
SSA -.->|"receives inbound streams"| InChB
SSA -.->|"receives inbound streams"| InChC
OutChB -.->|"connects to"| ReplicaB["Replica B Server"]
OutChC -.->|"connects to"| ReplicaC["Replica C Server"]
Loading
Key components after symmetric configuration:
Component
Responsibility
Manager
Outbound node pool, dial options, message ID generation
Configuration
Immutable slice of *Node — works identically for outbound & inbound
Node
ID, address, channel reference — identical for both directions
Channel
Send queue, stream lifecycle, response routing (outbound or inbound)
Symmetric Configuration Design Document
Executive Summary
This document proposes architectural changes to Gorums that enable symmetric use of
ConfigurationandNodeabstractions on both client and server sides. This allows server replicas to communicate with connected clients (and other replicas) using the same API that clients use to communicate with servers.Key benefits:
Motivation
Current Limitation
In the current Gorums architecture:
Manager→Configuration→Nodehierarchy to communicate with server replicasThis creates awkwardness when implementing protocols like Paxos where each replica must communicate with all other replicas:
Proposed Solution
Enable servers to obtain a
Configurationof connected clients, using the same API as clients:Architecture
Current Architecture
The
Channelis an exported type ininternal/stream.The wire protocol is defined in
stream.protousing a single bidiNodeStreamper node.All RPC methods are multiplexed over the stream, identified by the
methodfield.graph TB subgraph "gorums package — Client Side" Manager --> Config[Configuration] Config --> Node1["Node"] Config --> Node2["Node"] Node1 -->|"*stream.Channel"| Ch1[Channel] Node2 -->|"*stream.Channel"| Ch2[Channel] Ch1 --> Conn1["grpc.ClientConn"] Ch2 --> Conn2["grpc.ClientConn"] end subgraph "gorums package — Server Side" Server --> SS[streamServer] SS -->|"implements"| GRPC SS --> SrvStream["Gorums_NodeStreamServer"] end subgraph "internal/stream package" Channel["Channel (exported)"] Request["Request struct"] Message["stream.Message (protobuf)"] NodeResponse["NodeResponse[T]"] GRPC["Gorums gRPC service"] end Ch1 -.->|"bidi NodeStream"| SrvStream Ch2 -.->|"bidi NodeStream"| SrvStreamProposed Architecture (symmetric configuration)
The key addition is an
InboundManagerstruct that manages server-side peer state independently fromServer.Inbound channels are created from server-side streams, and the manager provides a live
Configuration.graph TB subgraph "internal/stream package" OutCh["Channel (outbound)"] InCh["Channel (inbound)"] BidiStream["BidiStream interface"] OutCh -->|"implements sender/receiver"| BidiStream InCh -->|"implements sender/receiver"| BidiStream endgraph TB subgraph "gorums package — Replica A" direction TB ServerA["Server"] --> SSA["streamServer"] ServerA -->|"embeds"| IMA["InboundManager"] IMA --> InboundCfgA["Configuration (auto-updated)"] InboundCfgA --> InNodeB["Node (inbound from B)"] InboundCfgA --> InNodeC["Node (inbound from C)"] InNodeB --> InChB["Channel (inbound)"] InNodeC --> InChC["Channel (inbound)"] MgrA["Manager"] --> CfgA["Configuration (outbound)"] CfgA --> OutNodeB["Node (outbound to B)"] CfgA --> OutNodeC["Node (outbound to C)"] OutNodeB --> OutChB["Channel (outbound)"] OutNodeC --> OutChC["Channel (outbound)"] end SSA -.->|"receives inbound streams"| InChB SSA -.->|"receives inbound streams"| InChC OutChB -.->|"connects to"| ReplicaB["Replica B Server"] OutChC -.->|"connects to"| ReplicaC["Replica C Server"]Key components after symmetric configuration:
ManagerConfiguration*Node— works identically for outbound & inboundNodeChannelServerInboundManagerConfigurationSystemServer+ listener + closersDesign Decisions
peer.FromContext+gorums-addrmetadataInboundManagerembedded inServerIsInbound()checksconn == nilBidiStreaminterface for outbound/inboundSysteminfers server address for outbound configsWithServerAddress; address automatically includedInboundManagerowns server-sidenextMsgIDManagerto avoid coupling client and server countersInboundNodeOptionforInboundManagerconstructorNodeListOption; no interface modificationclient_identity.gofilegorumsAddrKey,identifyPeerco-located in one placehandleStreamencapsulates identify+register logicNodeStreamhandler calls one method; returns cleanup functionImplementation Plan
BidiStreaminterface + inbound channelInboundManager+ peer identity +InboundNodeOptionSystemintegration, address inference, symmetric testsEach phase is designed to be reviewed and committed independently.
All existing tests must pass after each phase.
API Changes Summary
New APIs
stream.BidiStreamstream.NewInboundChannel(...)(*Channel).IsInbound()grpc.ClientConnInboundNodeOptionNewInboundManager(myID, opt)InboundNodeOption(*InboundManager).InboundConfig()(*InboundManager).handleStream(ctx, srv)WithInboundManager(im)identifyPeer(ctx)NewSymmetricSystem(addr, myID, opt, opts)(*System).NewOutboundConfig(opts)(*System).InboundConfig()Modified Behavior
Channelstructstreamfield usesBidiStreamtype;connnil for inboundChannel.ensureStream()ErrStreamDownChannel.Close()grpc.ClientConnNodeStreamhandlerhandleStreamwhen InboundManager is presentgorums-addrmetadata when created via SystemBackward Compatibility
All changes are backward compatible:
WithInboundManageris opt-in)Systemretains existingNewSystem,RegisterService,Serve,StopAPIChanneloutbound behavior unchanged; inbound is new functionalityNodeListOptioninterface is not modified;InboundNodeOptionis a separate interface