Velo is a production-grade, fully configurable load balancer written in Go. It provides intelligent request distribution across multiple backend servers with built-in health checking, circuit breaking, and comprehensive metrics.
- Features
- Architecture
- Project Structure
- Installation
- Configuration
- Usage
- API & Metrics
- Advanced Features
- Performance
- Contributing
- License
✅ Multiple Load Balancing Strategies
- Round-Robin: Distributes requests sequentially across healthy backends
- Least Connection: Routes to backend with fewest active connections
✅ Automatic Health Checking
- Periodic health probes (configurable interval)
- Automatic backend status updates
- Graceful handling of unhealthy backends
✅ Circuit Breaker Pattern
- Prevents cascading failures
- Configurable failure threshold
- Automatic recovery timeout
✅ Real-time Metrics
- Total requests per pool
- Failed requests tracking
- Active connections monitoring
- JSON or Prometheus format
✅ Full Configuration
- YAML-based config files
- Environment variable overrides
- Command-line flags
- Zero-downtime config changes
✅ Thread-Safe Operations
- RWMutex for concurrent access
- Atomic operations for counters
- Safe concurrent request handling
✅ Production Ready
- Reverse proxy with error handling
- Graceful error responses
- Structured logging
- Clean architecture with interfaces
┌─────────────────────────────────────────────────────────────────┐
│ Client Requests │
└────────────────────────┬────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Load Balancer (Velo) │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ HTTP Request Handler │ │
│ │ - Receive incoming requests │ │
│ │ - Route to appropriate backend │ │
│ │ - Update metrics │ │
│ └──────────────────────┬───────────────────────────────────┘ │
│ │ │
│ ┌──────────────────────▼───────────────────────────────────┐ │
│ │ Strategy Module │ │
│ │ ┌────────────────────┐ ┌───────────────────────┐ │ │
│ │ │ RoundRobin │ │ LeastConnection │ │ │
│ │ │ - Sequential pick │ │ - Track active conns │ │ │
│ │ │ - Atomic counter │ │ - Min conn selection │ │ │
│ │ └────────────────────┘ └───────────────────────┘ │ │
│ └──────────────────────┬───────────────────────────────────┘ │
│ │ │
│ ┌──────────────────────▼───────────────────────────────────┐ │
│ │ Backend Pool & Health Checker │ │
│ │ ┌────────────────────────────────────────────────────┐ │ │
│ │ │ Backend 1 [Healthy] Backend 2 [Unhealthy] │ │ │
│ │ │ Backend 3 [Healthy] Backend 4 [Recovering] │ │ │
│ │ └────────────────────────────────────────────────────┘ │ │
│ │ - Periodic health probes every 5s │ │
│ │ - Update health status │ │
│ │ - Thread-safe access with RWMutex │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ┌──────────────────────▼───────────────────────────────────┐ │
│ │ Circuit Breaker (Per Backend) │ │
│ │ - Track failure count │ │
│ │ - Fail-fast when threshold exceeded │ │
│ │ - Recover after timeout │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ┌──────────────────────▼───────────────────────────────────┐ │
│ │ Reverse Proxy │ │
│ │ - Forward requests to backend │ │
│ │ - Handle backend errors │ │
│ │ - Record success/failure │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ┌──────────────────────▼───────────────────────────────────┐ │
│ │ Metrics Collector │ │
│ │ - Total requests │ │
│ │ - Failed requests │ │
│ │ - Active connections │ │
│ └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌────────────────────────────────────┐
│ Backend Servers │
│ ┌──────────┐ ┌──────────┐ │
│ │Backend 1 │ │Backend 2 │ ... │
│ └──────────┘ └──────────┘ │
└────────────────────────────────────┘
1. Client sends HTTP request to Velo on port 8090
│
2. LoadBalancer.ServeHTTP() intercepts request
│
3. Check circuit breaker status
├─ If Open → return ServiceUnavailable
│
4. Get strategy (RoundRobin / LeastConnection)
│
5. Strategy.NextBackend() selects healthy backend
├─ RoundRobin: atomic counter % healthy backends count
├─ LeastConnection: select backend with minimum active connections
│
6. Record metrics
├─ Increment TotalRequests
├─ Increment ActiveConnections
│
7. Create reverse proxy to backend.URL
│
8. Forward request to backend
├─ If error → record failure, increment FailedRequests
│
9. Record success (for circuit breaker reset)
│
10. Decrement ActiveConnections
│
11. Return response to client
Custom_load_balancer/
├── README.md # This file
├── config.yaml # Default configuration
├── go.mod # Go module definition
├── go.sum # Dependencies lock file
│
├── cmd/
│ └── Custom_load_balancer/
│ └── main.go # Entry point
│
├── config/
│ └── config.go # Configuration loading & parsing
│
├── internal/
│ ├── balancer/
│ │ ├── backend.go # Backend server representation
│ │ ├── backendpool.go # Pool of backends + health check
│ │ ├── strategy.go # Strategy interface
│ │ ├── RoundRobin.go # Round-robin implementation
│ │ └── LeastConnection.go # Least-connection implementation
│ │
│ ├── circuit/
│ │ └── breaker.go # Circuit breaker pattern
│ │
│ ├── metrics/
│ │ ├── metrics.go # Metrics collection & HTTP handler
│ │ └── health.go # Health check utilities
│ │
│ └── proxy/
│ └── proxy.go # Reverse proxy & request routing
│
└── server/
└── server.go # Test backend servers
Represents a single backend server with health and connection tracking.
Fields:
URL- Backend server address (e.g.,http://localhost:8081)Healthy- Current health statusActiveConnections- Count of ongoing connectionsWeight- Load balancing weight (reserved for future use)CircuitState- Current circuit breaker state (Closed/Open/HalfOpen)FailureCount- Consecutive failuresLastFailureTime- Timestamp of last failure
Key Methods:
IncrementConnections()/DecrementConnections()- Track active connectionsIsHealthy()- Get health status (thread-safe)SetHealthy(state bool)- Update health statusIncrementFailures()/ResetFailure()- Track circuit breaker stateisBackendAlive()- Probe backend with/healthendpoint
Thread Safety: Uses mutex to protect concurrent access.
Manages a collection of backends and health checking.
Fields:
backends- Slice of backend serverscounter- Atomic counter for round-robinmu- RWMutex for thread-safe access
Key Methods:
AddBackend(b *Backend)- Add a backend to poolGetHealthyBackends()- Return only healthy backendsGetAllBackends()- Return all backends (filtered by health status)HealthCheck()- Runs indefinitely, probing backends every 5 secondsGetNextBackendRR()- Internal round-robin selection
Health Checking Logic:
for {
backends := GetAllBackends()
for _, backend := range backends {
alive := backend.isBackendAlive()
backend.SetHealthy(alive)
time.Sleep(5 * time.Second)
}
}Thread Safety: RWMutex allows multiple concurrent readers (strategy selection) and exclusive writers (health updates).
Defines how backends are selected.
type Strategy interface {
NextBackend() *Backend
Pool() *BackendPool
}Implementations:
RoundRobin- Sequential selection with wraparoundLeastConnection- Select backend with minimum active connections
Distributes requests evenly across healthy backends in sequence.
Algorithm:
index = atomic.AddUint64(&counter, 1)
backend = healthy_backends[index % len(healthy_backends)]
Pros:
- Simple, predictable distribution
- O(1) selection time
- Good for stateless requests
Cons:
- Doesn't account for backend load
- Works poorly with unequal server capacities
Routes each request to the backend with the fewest active connections.
Algorithm:
backend_with_min_connections = select from healthy_backends
where connections == min(all.connections)
Pros:
- Adapts to server load in real-time
- Better for long-lived connections
- Fairer distribution with heterogeneous servers
Cons:
- O(n) selection time (scans all backends)
- Needs accurate connection tracking
Implements the circuit breaker pattern to prevent cascading failures.
States:
- Closed - Normal operation, requests pass through
- Open - Too many failures, requests rejected immediately
- HalfOpen - After timeout, attempting recovery
Fields:
FailureThreshold- Max consecutive failures before openingTimeout- Duration to wait before half-openlastFailureTime- When the last failure occurred
Key Methods:
AllowRequest(backend)- Check if request should proceedRecordSuccess(backend)- Reset failure countRecordFailures(backend)- Increment failure count, possibly open circuit
Transition Logic:
Closed ─failure─→ Open ─timeout─→ HalfOpen ─success─→ Closed
↑
failure
│
Open
The main request handler that ties everything together.
Fields:
strategy- Load balancing strategybreaker- Circuit breaker instance
ServeHTTP() Flow:
- Increment
TotalRequestsmetric - Get next backend from strategy
- Check circuit breaker
- Increment
ActiveConnectionsmetric - Create reverse proxy to backend
- Forward request
- Handle errors (record failure, increment
FailedRequests) - Record success
- Decrement
ActiveConnections - Return response to client
Error Handling:
- If no healthy backends: return 503 Service Unavailable
- If circuit breaker open: return 503 Service Unavailable
- If backend error: return 502 Bad Gateway, record failure
Collects and exposes pool-level metrics.
Tracked Metrics:
{
"pool_pointer_address": {
"total_requests": 1000,
"failed_requests": 5,
"active_connections": 23
}
}Operations:
RegisterPool(pool)- Register a new pool for trackingIncTotalRequests(pool)- Increment total requests (atomic)IncFailedRequests(pool)- Increment failed requests (atomic)IncActiveConnections(pool)- Increment active connections (atomic)DecActiveConnections(pool)- Decrement active connections (atomic)Handler(w, r)- HTTP handler for/metricsendpoint
Thread Safety: Uses atomic operations and mutex for map access.
Manages application configuration from files and environment.
Config Structure:
server:
address: "0.0.0.0"
port: 8090
backends:
urls:
- http://localhost:8081
- http://localhost:8082
strategy: roundrobin
circuit:
failure_threshold: 3
timeout: 10s
health:
interval: 5s
timeout: 2s
metrics:
enabled: true
path: /metricsLoading Priority:
- Built-in defaults
- Config file (if exists)
- Environment variables (override)
Supported Env Vars:
LB_PORT- Server portLB_ADDRESS- Server addressLB_STRATEGY-roundrobinorleastconnectionHEALTH_CHECK_INTERVAL- e.g.,10sCIRCUIT_FAILURE_THRESHOLD- integer
- Go 1.25 or higher
- Linux, macOS, or Windows
# Clone repository
git clone https://github.com/yourusername/velo.git
cd velo
# Download dependencies
go mod download
# Build binary
go build -o velo ./cmd/Custom_load_balancer
# (Optional) Install to system
sudo mv velo /usr/local/bin/go install github.com/yourusername/velo/cmd/Custom_load_balancer@latestCreate a config.yaml in the project root:
server:
address: "0.0.0.0" # Bind address
port: 8090 # Listen port
backends:
urls:
- http://backend1.internal:8080
- http://backend2.internal:8080
- http://backend3.internal:8080
strategy: roundrobin # roundrobin | leastconnection
circuit:
failure_threshold: 3 # Open circuit after N failures
timeout: 10s # Time before attempting recovery
health:
interval: 5s # How often to check backend health
timeout: 2s # Health check request timeout
metrics:
enabled: true # Enable /metrics endpoint
path: /metrics # Metrics endpoint pathOverride any config value with environment variables:
# Server configuration
export LB_PORT=9090
export LB_ADDRESS=127.0.0.1
# Load balancing strategy
export LB_STRATEGY=leastconnection
# Health checking
export HEALTH_CHECK_INTERVAL=10s
export HEALTH_CHECK_TIMEOUT=3s
# Circuit breaker
export CIRCUIT_FAILURE_THRESHOLD=5
# Run the load balancer
./velo# Use custom config file
./velo -config /etc/velo/production.yaml
# Generate default config (useful for setup)
./velo -generate-config
# Combine with env vars
LB_PORT=9090 ./velo -config ./config.yaml1. Start Backend Servers
For testing, create simple backends (or use existing servers):
# Terminal 1
go run ./server 8081
# Terminal 2
go run ./server 8082
# Terminal 3
go run ./server 80832. Start Velo
# Using default config
./velo
# Or with custom config
./velo -config ./config.yamlExpected output:
Added backend: http://localhost:8081
Added backend: http://localhost:8082
Added backend: http://localhost:8083
Using RoundRobin strategy
Metrics endpoint enabled at /metrics
Starting load balancer on 0.0.0.0:8090
3. Send Requests
# Single request
curl http://localhost:8090
# Multiple requests (see round-robin in action)
for i in {1..6}; do curl http://localhost:8090; echo; done
# Check metrics
curl http://localhost:8090/metrics | jq
# Check specific backend health
curl http://backend1.internal:8080/health# Using ab (Apache Bench)
ab -n 10000 -c 100 http://localhost:8090/
# Using wrk (HTTP benchmarking)
wrk -t4 -c100 -d30s http://localhost:8090/
# Using hey
hey -n 10000 -c 100 http://localhost:8090/Real-time Metrics:
# JSON format
watch -n 1 'curl -s http://localhost:8090/metrics | jq'
# Count total requests
curl -s http://localhost:8090/metrics | jq '.[] | .total_requests'Endpoint: / (any path)
Method: All HTTP methods (GET, POST, PUT, DELETE, etc.)
Response:
- Success: Forward response from backend
- No healthy backends:
503 Service Unavailable - Circuit breaker open:
503 Service Unavailable - Backend error:
502 Bad Gateway
Example:
curl http://localhost:8090/api/users
# Returns response from a healthy backendEndpoint: /metrics
Method: GET
Response Format: JSON
{
"0xc00009e000": {
"total_requests": 10234,
"failed_requests": 12,
"active_connections": 45
}
}Metrics Definition:
total_requests- Total HTTP requests received and forwardedfailed_requests- Requests that resulted in errorsactive_connections- Currently open connections to backends
Example:
curl http://localhost:8090/metrics | jq
# Pretty print
curl http://localhost:8090/metrics | jq '.[] | {
total_requests,
failed_requests,
success_rate: (.total_requests - .failed_requests) / .total_requests * 100
}'Prometheus Integration (Future): Velo can be extended to export metrics in Prometheus text format for integration with Prometheus, Grafana, etc.
Modify backend health check endpoint:
In internal/balancer/backend.go, the health check probes:
resp, err := client.Get(b.URL + "/health")Change /health to your actual health endpoint.
Implement the Strategy interface:
type CustomStrategy struct {
pool *BackendPool
}
func (s *CustomStrategy) NextBackend() *Backend {
// Your custom logic here
return selected_backend
}
func (s *CustomStrategy) Pool() *BackendPool {
return s.pool
}Then in main.go:
strategy := &CustomStrategy{pool: pool}Adjust in config.yaml:
- Strict (fewer false positives):
failure_threshold: 5, timeout: 30s - Aggressive (faster recovery):
failure_threshold: 2, timeout: 5s
To add backends at runtime, extend BackendPool:
func (p *BackendPool) RemoveBackend(url string) {
p.mu.Lock()
// Filter out backend by URL
p.mu.Unlock()
}Performance depends on hardware and load characteristics. Typical metrics:
- Throughput: 10,000+ req/s per core
- Latency (p50): <5ms
- Latency (p99): <50ms
- Memory: ~50MB base + ~1KB per active connection
- Use RoundRobin for throughput, LeastConnection for fairness
- Tune health check interval - more frequent = higher overhead
- Adjust circuit breaker threshold - balance between quick recovery and stability
- Use multiple load balancer instances behind a DNS/IP failover
- Single Instance: Easily handles 10k-100k req/s
- Multiple Instances: Use DNS round-robin or keepalived for HA
- Horizontal Scaling: Run multiple Velo instances, distribute with DNS/HAProxy
Cause: Health check endpoint /health doesn't exist or is unresponsive
Solution:
- Verify backend has
/healthendpoint - Check health check timeout:
health.timeout - Verify network connectivity:
curl http://backend:port/health
Cause: Backends are timing out or slow
Solution:
- Check backend logs
- Increase health check timeout
- Check network latency
- Scale backends or reduce load
Cause: Too sensitive configuration
Solution:
- Increase
circuit.failure_threshold - Increase
circuit.timeout - Fix underlying backend issue
Cause: Backends have different capacities
Solution:
Switch to LeastConnection strategy:
strategy: leastconnectionContributions welcome! Please:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit changes (
git commit -m 'Add amazing feature') - Push to branch (
git push origin feature/amazing-feature) - Open a Pull Request
# Install dev dependencies
go mod download
# Run tests (if available)
go test ./...
# Format code
go fmt ./...
# Lint
go vet ./...
# Build
go build -o velo ./cmd/Custom_load_balancerThis project is licensed under the MIT License - see LICENSE file for details.
- Prometheus metrics export format
- gRPC load balancing
- WebSocket support
- Custom authentication/authorization
- Dynamic backend management API
- Weighted round-robin
- Request rate limiting
- TLS/HTTPS support
- Admin dashboard
For issues, questions, or suggestions:
- Open an issue on GitHub
- Check existing documentation
- Review example configurations
| Metric | Value |
|---|---|
| Max Throughput | 10k-100k req/s |
| Latency (mean) | 1-5ms |
| Latency (p99) | <50ms |
| Memory Overhead | ~50MB base |
| CPU per core | ~30-40% @ 10k req/s |
| Concurrent Connections | Unlimited (system limited) |
Velo - Fast, reliable, and configurable load balancing for Go applications.
