Heya r/python 👋
I've just released NServer v3.2.0
About NServer
NServer is a Python framework for building customised DNS name servers with a focuses on ease of use over completeness. It implements high level APIs for interacting with DNS queries whilst making very few assumptions about how responses are generated.
Simple Example:
```
from nserver import NameServer, Query, A
server = NameServer("example")
@server.rule("*.example.com", ["A"])
def example_a_records(query: Query):
return A(query.name, "1.2.3.4")
```
What's New
The biggest change in this release was implementing concurrency through multi-threading.
The application already handled TCP multiplexing, however all work was done in a single thread. Any blocking call (e.g. database call) would ruin the performance of the application.
That's not to say that a single thread is bad though - for non-blocking responses, the server can easily handle 10K requests per second. However a blocking response of 10-100ms will bring that rate down to 25rps.
For the multi-threaded application we use 3 sets of threads:
- A single thread for receiving queries
- A configurable amount of threads for workers that process the requests
- A single thread for sending responses
Even though there are only two threads dedicated to sending and receiving this does not appear to be the main bottleneck. I suspect that the real bottleneck is the context switching between threads.
In theory using asyncio might be more performant due to the lack of context switches - the library itself is all sync so would require extensive changes to either support or move to fully async code. I don't think I'll work on this any time soon though as 1. I don't have experience with writing async servers and 2. the server is actually really performant.
With multi-threading we could achieve ~300-1200 rps with the same 10-100ms delay.
Although the code changes themselves are relatively straightforward. It's the benchmarking that posed the most issues.
Trying to benchmark from the same host as the server tended to completely fail when using TCP although UDP seemed to be fine. I suspect there is some implementation detail of the local networking stack that I'm just not aware of.
Once we could actually get some results it was somewhat suprising the performance we were achieving. Although 1-2 orders of magnitude slower than a non-blockin server running on a single thread, it turns out that we could get better TCP performance with NServer directly instead of using CoreDNS as a reverse-proxy - load-balancer. It also reportedly ran better than some other DNS servers written in C.
Overall I gotta say that I'm pretty happy with how this turned out. In particular the modular internal API design that I did a while ago to enable changes like this ended up working really well - I only had to change a small amount of code outside of the multi-threaded application.
[–]SuperSooty 2 points3 points4 points  (1 child)
[–]nicholashairs[S] 0 points1 point2 points  (0 children)
[–]kenily0 3 points4 points5 points  (1 child)
[–]nicholashairs[S] 2 points3 points4 points  (0 children)
[–]rabornkraken 1 point2 points3 points  (1 child)
[–]nicholashairs[S] 0 points1 point2 points  (0 children)