LoadGate is a modern L4/L7 reverse proxy and load balancer written in Go.
Sub-0.5 ms proxy overhead, linear scaling to 10,000 concurrent connections under 200 MB, and automatic failover that never drops a request — every number measured.
- L4 and L7 modes — raw TCP relay or full HTTP(S) reverse proxy, selected per instance.
- Four algorithms — round-robin, smooth weighted round-robin, least-connections, and consistent hashing (150 vnodes).
- Two-plane health checking — passive eviction on real-traffic failures, active probing for readmission. Fail fast, recover carefully.
- Automatic failover — idempotent requests that fail are retried on a healthy backend.
- TLS termination — HTTPS with hot certificate rotation; swap certs on reload, no restart.
- Hot config reload —
SIGHUPvalidate-before-swap: a broken config can never take down a running server. - Rate limiting — global and per-client token buckets, reject-don't-queue, LRU-bounded.
- Observability — Prometheus metrics on a separate port, provisioned Grafana dashboard, pprof profiling.
Go install (requires Go 1.26+):
go install github.com/Sachinxmpl/loadgate/cmd/loadgate@latestFrom source:
git clone https://github.com/Sachinxmpl/loadgate.git
cd loadgate
make build # -> bin/loadgateDocker — the repository ships a Compose stack (proxy + demo backends + Prometheus + Grafana):
docker compose up --buildWrite a config (config.yaml):
mode: l7
listen: "127.0.0.1:8080"
balancer: round_robin
routes:
- match: { path_prefix: "/" }
pool: web
pools:
web:
- addr: "127.0.0.1:9001"
- addr: "127.0.0.1:9002"Validate it, then run:
loadgate check -c config.yaml # validate and exit (non-zero on error)
loadgate run -c config.yaml # serve until interruptedSend traffic, then reload without dropping a single connection:
curl http://127.0.0.1:8080/
kill -HUP $(pgrep loadgate) # re-reads config; keeps the old one if the new is invalidLoadGate is configured by a single YAML file. Top-level fields:
| Field | Type | Default | Meaning |
|---|---|---|---|
mode |
string | required | l4 (TCP relay) or l7 (HTTP proxy) |
listen |
string | required | address to bind, e.g. 0.0.0.0:8080 |
balancer |
string | round_robin |
round_robin, weighted_round_robin, least_connections, or consistent_hash |
tls |
object | disabled | { cert, key } paths; presence enables HTTPS |
timeouts |
object | dial 2s, read/write/request 30s, idle 60s, drain 15s |
connection and request deadlines |
health |
object | active { interval 2s, timeout 500ms, rise 2 }, passive { fall 3, cooldown 10s } |
probing/readmission and eviction thresholds |
rate_limit |
object | 0 (unlimited) |
global_rps, per_client_rps |
routes |
list | required for L7 | { match: { path_prefix }, pool }, longest prefix wins |
pools |
map | required | named lists of backends: { addr, weight } (weight defaults 1) |
Full annotated example
mode: l7
listen: "0.0.0.0:8080"
balancer: least_connections
tls: # optional; presence enables HTTPS
cert: /etc/loadgate/cert.pem
key: /etc/loadgate/key.pem
timeouts:
dial: 300ms
read: 30s
write: 30s
idle: 60s
request: 30s
drain: 15s # how long shutdown waits for in-flight requests
health:
active: { interval: 2s, timeout: 500ms, rise: 2 } # probing / readmission
passive: { fall: 3, cooldown: 10s } # eviction
rate_limit:
global_rps: 0 # 0 = unlimited
per_client_rps: 0
routes: # L7 only; longest path prefix wins
- match: { path_prefix: "/api/" }
pool: api
- match: { path_prefix: "/" }
pool: web
pools:
api:
- { addr: "10.0.0.1:8080", weight: 3 }
- { addr: "10.0.0.2:8080", weight: 1 }
web:
- { addr: "10.0.0.3:8080" }See config.example.yaml (L4) and configl7.example.yaml (L7) for ready-to-run examples.
loadgate check -c <file> validate a config file and exit
loadgate run -c <file> serve until interrupted
kill -HUP <pid> hot-reload the config (validate-before-swap)
run flags
| Flag | Default | Purpose |
|---|---|---|
-c |
config.yaml |
config file path |
-log-level |
info |
debug, info, warn, error |
-log-format |
json |
json or text |
-metrics-addr |
127.0.0.1:9095 |
Prometheus metrics endpoint |
-debug-addr |
(disabled) | pprof debug port, e.g. 127.0.0.1:6060 |
- Metrics — Prometheus format on
-metrics-addr(default127.0.0.1:9095/metrics), with bounded cardinality (labelled by status class and backend, never by raw code, path, or client IP). Request counts and duration histograms, active connections, backend up/down, health transitions, rate-limit rejections, and reload outcomes. - Dashboard — a Grafana dashboard is auto-provisioned in the Docker stack (
http://localhost:3000); it shows a backend being killed, evicted, and readmitted live. - Profiling — pprof endpoints on the private
-debug-addrport. Capture a full CPU/heap/goroutine set withbash test/bench/profile.sh.
Measured on a 13th-gen Intel i5 (12 cores), all traffic over loopback, open-loop constant-rate load. Full method and raw data in docs/benchmark.md and docs/profiling.md.
| Experiment | Result |
|---|---|
| E1 — proxy overhead per request | +0.42 ms (L7), +0.19 ms (L4) at the median |
| E2 — connection scaling | 10,000 live connections: 3 goroutines each, < 200 MB, zero failures |
| E3 — least-conn vs round-robin (one slow backend) | mean latency 10 ms vs 55 ms |
| E4 — consistent-hash remap (drop 1 of 10) | 10% of keys move vs 90% for naive modulo |
| E5 — value of the health loop (sick backend) | mean latency 8 ms vs 106 ms |
Under load the proxy spends its CPU on network syscalls and scheduling, holds under 4 MB of live heap, and runs on less than one core — the profile of a proxy that moves data rather than computing.
Reproduce everything:
make bench # E1–E5 -> test/bench/results/
bash test/chaos/run.sh # chaos harness (kills backends under load)
go run test/chaos/verdict.go # grade the chaos run: PASS/FAILmake build # build the binary
make test # unit tests with the race detector
make lint # golangci-lint
make bench # run the five benchmark experiments
make ci # fmt-check + vet + test (what CI runs)Resilience is verified two ways: a chaos harness kills and restarts backends under sustained load and asserts fast failover with no goroutine leak, and goleak guards every package against leaked goroutines in CI.
Contributions are welcome. To propose a change:
- Open an issue describing the problem or feature.
- Fork and create a branch (
git checkout -b feature/my-change). - Make your change with tests; ensure
make cipasses (fmt, vet, race tests). - Open a pull request describing the change and, for anything performance-related, the benchmark before/after.
Keep the dependency budget small (standard library first) and, in the spirit of this project, back new behaviour with a test or a benchmark.
- Final report — the full project report (design, results, evaluation)
- Benchmarks — the five experiments and their results
- Profiling — where CPU and memory go under load
- Chaos testing — the failover / no-leak harness
- Load simulation — driving traffic by hand
Released under the MIT License.