Lunar - Performance
Built in optimized C++
Lunar is written in modern, carefully optimized C++ and compiled with optimizations on. There is no interpreter, no garbage collector and no runtime in the hot path. Just tight, cache-friendly native code. Wherever a data structure or algorithm is on the packet path, it is chosen for low, predictable latency: fixed work per packet, no hidden allocations, no unexpected pauses.
Lock-free hot paths
The core of Lunar is a staged pipeline where each stage runs on its own pool of threads and hands work to the next over LOCK-FREE queues. On the hot path, a thread never takes a mutex to receive or pass on work, so they do not block each other, do not convoy behind a lock, and scale cleanly across CPU cores.
Two design choices matter most:
Lock-free, round-robin work queues. Work is dispatched across consumer threads evenly, so no single thread becomes a hot spot while its siblings sit idle. Adding cores adds throughput.
Read-copy-update (RCU) for live state. Configuration for virtuals servers and their clients is read on the packet path without any lock at all. Updates build a new snapshot and swap it in atomically, so config changes never stall packet processing.
Massively parallel, non-blocking backend calls
Because lunar delegates every decision to a backend API, each request involves an HTTP call to that backend. A single such call adds latency (a network round trip to the backend and back). The trick is that Lunar does this asynchronously: it never parks a thread waiting for the backend.
Instead, a small pool of worker threads keeps thousands of backend requests in flight at once over multiplexed, keep-alive HTTPS connections. While one request waits on the backend, the same threads are busy issuing and completing many others. So the per-request latency of a backend call is hidden behind sheer parallelism, and total throughput is bounded by how fast the backend can answer in aggregate (not by any single call's round trip).
For the RADIUS authentication service, this means Lunar can have thousands of Access-Requests outstanding against the backend simultaneously, all without blocking, answering each NAS as its decision returns.
Scale the backend independently
This async model scales naturally on the backend side too. Because Lunar talks to the backend over ordinary HTTPS, you can put a load balancer (for example NGINX) in front of your backend API and run as many backend instances behind it as you need. Lunar keeps its many concurrent connections open to the load balancer, which spreads the authentication requests across your backend pool.
You scale AAA capacity by scaling the backend, not by touching Lunar. And it stays fast even over secure, TLS-encrypted HTTPS endpoints. Connections are reused and kept warm, so the encryption cost is amortized, not paid per request.
What that adds up to
Three things combine to make Lunar extremely fast: native C++, lock-free hot paths that avoid mutex contention, and massively parallel, non-blocking backend calls where nothing pending stalls a thread. For RADIUS alone, on suitable hardware and with a backend that can keep up, it can readily handle well over 100,000 Access-Requests and responses per second on a single server.
We continue to apply the latest available techniques to maximize throughput, so these figures reflect deliberate engineering, not a lucky benchmark.
Benchmarking lunar
Because Lunar is so heavily parallel, it is easy to under-measure it: the benchmark becomes the bottleneck long before Lunar does. Keep these points in mind when you test.
Single-threaded load tools cannot saturate lunar. Common RADIUS test tools (for example radperf) run on a single core / thread. That one thread runs out of CPU generating packets, signing them and tracking outstanding requests, well before lunar reaches its own limit. So what you measure is the tool's ceiling, not Lunar. A flat result usually means the load generator is maxed, not the server.
Drive load from multiple threads, processes and machines. To find Lunar's real limit, run several instances of the test in parallel, across multiple cores and, ideally, multiple separate servers. Add up their throughput. Scale the generators out until the aggregate stops rising. Only then are you measuring Lunar (or the backend), not a single client.
Watch the whole chain. Lunar delegates decisions to the backend API, so a realistic benchmark also needs a backend (and any load balancer in front of it) that can answer fast enough. If throughput plateaus, check whether the limit is the load generator, the backend, the network, or resources on host running Lunar itself before drawing conclusions.
Give lunar the cores. Its scaling comes from running work across many threads. Benchmarking it pinned to a couple of cores, or against a tiny backend, will not show what it can do.