How Does the Balancer Filter Work in Fabrica-Kit for Traffic Management
Fabrica-Kit's balancer filter implements a gRPC load-balancing mechanism that first attempts sticky routing via a route table lookup using OID and Color identifiers, falls back to a weighted round-robin algorithm when no match exists, and persists master-type selections to maintain session affinity across subsequent requests.
The go-pantheon/fabrica-kit repository provides a sophisticated traffic management system for gRPC microservices. At its core, the balancer filter orchestrates request distribution across backend nodes, combining context-aware sticky sessions with intelligent load distribution algorithms.
The Balancer Filter Decision Flow
The filter operates as a gRPC picker that executes a deterministic four-step process for every request. This flow ensures that existing sessions remain pinned to their assigned nodes while new connections distribute evenly according to configured weights.
Extracting Routing Context from gRPC Metadata
The balancer begins by extracting routing identifiers from the outgoing gRPC context. It retrieves two critical values using helper functions defined in xcontext/context.go:
- OID – A unique identifier representing the client or session (retrieved via
xcontext.OIDFromOutgoingContext) - Color – An optional routing tag for multi-tenant or regional traffic segmentation (retrieved via
xcontext.ColorFromOutgoingContext)
These values serve as composite keys for the routing decision.
Route-Table Lookup and Node Filtering
Using the extracted OID and Color, the balancer queries a route table implementation. Depending on configuration, it uses either routetable.ReadOnlyRouteTable or routetable.MasterRouteTable interfaces defined in router/routetable/routetable.go.
In router/balancer/balancer.go (lines 49‑58), the filter checks if the route table contains an address for the (color, oid) pair. If a match exists and the corresponding node appears in the available candidate list, the balancer immediately returns that node, ensuring sticky session behavior.
Weighted Round-Robin Fallback
When the route table has no entry—or when the stored address does not match any currently available node—the balancer falls back to a weighted round-robin (WRR) algorithm. The weightSelect function (lines 99‑126 in balancer.go) implements an Nginx-style weighted round-robin distribution, selecting nodes proportionally to their assigned weights while maintaining fairness across the fleet.
Sticky-Binding Persistence for Master Traffic
For balancers created with master type (TypeMaster), an additional persistence step occurs after WRR selection (lines 71‑96 in balancer.go). The balancer attempts to write the selected address back to the master route table using SetNxOrGet:
- If the write succeeds (indicating no existing entry), the selected node becomes the sticky target for future requests from the same
(color, oid)pair. - If another balancer instance has already written a different address, the current balancer reads that address and returns the corresponding node instead.
This atomic set-if-not-exists-or-get operation ensures that concurrent requests for the same session never create split-brain scenarios.
Architecture and Registration Components
Builder Pattern and Type Distinction
The newBalancerBuilder function in router/balancer/balancerbuilder.go constructs selectors that encapsulate both the balancer type (TypeMaster or read-only) and the route table reference. This builder pattern allows the system to maintain separate routing behaviors for read replicas versus master nodes while sharing the same underlying selection logic.
Registering Balancers with gRPC
The system exposes two registration functions in router/balancer/register.go:
RegisterMasterBalancer(rt routetable.MasterRouteTable)– Registers the balancer under the gRPC name"master"with write-back capabilitiesRegisterReadOnlyBalancer(rt routetable.ReadOnlyRouteTable)– Registers the balancer under the name"reader"for read-only sticky routing
Both functions invoke registerBalancerBuilder to add the Fabrica-Kit balancer to the global gRPC balancer registry.
Practical Implementation Examples
Registering a Master Balancer for Sticky Sessions
To enable sticky load balancing with session persistence, register the master balancer with a MasterRouteTable implementation (such as Redis-backed storage):
import (
"github.com/go-pantheon/fabrica-kit/router/balancer"
"github.com/go-pantheon/fabrica-kit/router/routetable"
)
// Assume myRedisMasterTable implements routetable.MasterRouteTable
var rt routetable.MasterRouteTable = myRedisMasterTable
func init() {
// Registers under the name "master"
balancer.RegisterMasterBalancer(rt)
}
Registering a Read-Only Balancer
For scenarios requiring sticky routing without write-back (such as cache layer routing), use a ReadOnlyRouteTable:
import (
"github.com/go-pantheon/fabrica-kit/router/balancer"
"github.com/go-pantheon/fabrica-kit/router/routetable"
)
var rt routetable.ReadOnlyRouteTable = myStaticRouteTable
func init() {
// Registers under the name "reader"
balancer.RegisterReadOnlyBalancer(rt)
}
Injecting Routing Context into Requests
Clients must propagate OID and Color values through the gRPC context for the balancer to route correctly:
import (
"context"
"github.com/go-pantheon/fabrica-kit/xcontext"
)
ctx := context.Background()
ctx = xcontext.WithOID(ctx, 12345) // Session identifier
ctx = xcontext.WithColor(ctx, "blue") // Routing tag
// The balancer reads these values during Pick()
client.SomeMethod(ctx, &pb.Request{})
Core Selection Logic
The Pick method in weightBalancer (simplified from balancer.go) demonstrates the complete decision flow:
func (p *weightBalancer) Pick(ctx context.Context, nodes []selector.WeightedNode) (selector.WeightedNode, selector.DoneFunc, error) {
oid, _ := xcontext.OIDFromOutgoingContext(ctx)
color := xcontext.ColorFromOutgoingContext(ctx)
// 1. Sticky route-table lookup
if addr, err := p.routeTable.Get(ctx, color, oid); err == nil {
for _, n := range nodes {
if n.Address() == addr {
return n, emptyDoneFunc, nil // Sticky hit
}
}
}
// 2. Weighted round-robin fallback
selected := p.weightSelect(nodes)
// 3. Persist for master balancers
if p.balancerType == balancer.TypeMaster {
if ok, _, _ := p.routeTable.(routetable.MasterRouteTable).SetNxOrGet(
ctx, color, oid, selected.Address(),
); ok {
return selected, selected.Pick(), nil
}
// Handle race: another writer won, fetch their value
}
return selected, selected.Pick(), nil
}
Summary
- The balancer filter extracts OID and Color from gRPC context to identify routing targets.
- It queries
ReadOnlyRouteTableorMasterRouteTableinterfaces before falling back to weighted round-robin selection. - Master-type balancers persist selections using atomic
SetNxOrGetoperations to establish sticky session affinity. - Registration occurs through
RegisterMasterBalancerandRegisterReadOnlyBalancer, exposing"master"and"reader"balancer names to gRPC. - Core implementation resides in
router/balancer/balancer.go, with builder logic inbalancerbuilder.goand context helpers inxcontext/context.go.
Frequently Asked Questions
What is the difference between Master and ReadOnly balancers?
Master balancers write selected node addresses back to the route table using SetNxOrGet, establishing persistent sticky sessions for subsequent requests. ReadOnly balancers only perform lookups against existing route table entries without modifying state, making them suitable for cache or replica routing where persistence is handled externally.
How does the balancer handle route table entries pointing to failed nodes?
When a route table entry resolves to an address not present in the current available node list, the balancer automatically falls back to the weightSelect algorithm. This weighted round-robin selection (lines 99‑126 in balancer.go) distributes traffic across healthy nodes according to their configured weights, ensuring high availability even when sticky targets become unreachable.
What are OID and Color in Fabrica-Kit traffic management?
OID (Object Identifier) is a unique numeric identifier representing a specific client, user session, or logical entity that requires routing consistency. Color is an optional string tag enabling multi-dimensional routing (such as region, version, or tenant isolation). Together they form the composite key (color, oid) used for route table lookups in routetable.ReadOnlyRouteTable.Get.
Is the weighted round-robin implementation compatible with Nginx?
Yes, the weightSelect function in router/balancer/balancer.go implements an Nginx-style weighted round-robin algorithm. This approach maintains intermediate state to ensure that nodes with higher weights receive proportionally more requests while preventing starvation of lower-weighted backends, matching the distribution behavior found in Nginx upstream configurations.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →