Coding for Performance: Why We Chose Rust
The next major version of the LogDNA agent is right around the corner, and we’re introducing some significant improvements. Most notably, we’ve completely rewritten the agent using Rust instead of Node.js. As we gear up for the release, we wanted to explain why we chose Rust for the LogDNA agent, the benefits that it offers over other languages, and how it can help you log faster.
What is Rust?
Despite being relatively new, Rust is being adopted at an incredible rate. It’s the most loved language by developers according to the Stack Overflow Developer Survey, and it’s the fifth fastest growing language according to GitHub’s State of the Octoverse.
What Are the Benefits of Rust?
One of Rust’s key strengths is its memory and thread management. While Rust doesn’t have garbage collection like Node.js, Java, Python, and others, the Rust compiler enforces check for invalid memory references, leaks, and other dangerous or undefined behaviors.
Rust follows the principle of resource allocation is initialization (RAII), where any resources that must be acquired (such as a file or database connection) are bound to the lifetime of their parent object. This prevents a lot of the traps that C developers fall into, such as dangling pointers, null pointers, and accessing uninitialized memory. It also reduces the risk of memory leaks.
When multithreading, Rust requires each thread to own (or borrow) the resources that it wants to access. It also prevents resources from going out of scope until a thread is no longer using them. This avoids race conditions caused by multiple threads accessing the same data, but also prevents one thread from deleting a resource that another thread is using. This blog post explains concurrency in greater detail.
Rust provides a package management tool called Cargo. This might not seem like a big deal, since package management is common in languages like Node.js, Python, and Java. But considering that neither C or C++ have universal package management tools, having the ability to automatically resolve dependencies in a systems language is powerful. Instead of having a complex build process, we can simply run cargo build to build the agent from scratch.
The advantage of Rust being built in this way is the predictable performance at compile time unlike the JIT compilation and interpreter of Node.js. Performance is clearly why we were drawn to Rust.
Why we chose Rust
The key benefit that Rust provides is performance. Rust programs are compiled directly into machine code similar to languages such as C/C++, Objective-C, Haskell, and Swift. There’s no virtual machine or interpreter sitting between your code and your computer.
In one benchmark comparing REST API performance (using Rocket for Rust and Restify for Node.js), Rust handled 72,000 requests per second compared to Node.js’ 8,000, and used just over 1MB of memory when idle compared to Node.js’ 19MB. In another benchmark comparing web frameworks (nickel for Rust and restana for Node.js), Rust responded to requests nearly 100x faster on average than Node.js.
This level of lightweight, high throughput performance is essential for something like a logging agent. It helps us prevent the agent from exhausting system resources and becoming a bottleneck, no matter how many logs it needs to process. You can feel confident knowing that your logs are being sent to LogDNA without affecting the performance of your applications.
Another advantage of moving from Node.js to Rust is the shift to a lower level in the stack and dependency on the kernel. The advantage of interacting with the Linux kernel is that our agent can use inotify to monitor changes in the filesystem which relieves the CPU utilization of the active polling for changes in log files.
We will share benchmarks and more details about our new agent shortly.