WebAssembly for the Server Side: A New Way to NGINX

This is the first of a two-part series.
The meteoric rise of WebAssembly (Wasm) started because it’s a language-agnostic runtime environment for the browser that enables safe and fast execution of languages other than JavaScript. Although Wasm’s initial focus was in the browser, developers have begun to explore the possibilities of Wasm on the backend, where it opens many possibilities for server and network management.
Similar to NGINX , many server-side technologies operate with a standard plugin model, which relies on statically or dynamically injecting linked object files into an executable running in the same address space.
However, plugins have considerable limitations. In particular, they allow extensibility through native language extensions, which limits developer choice in terms of languages and language-specific capabilities. Other plugins must conform to complex linking methods that require both server and client languages to support the same functionality interface. This can add complexity for creators of plugins.
Finally, some plugins work through dynamic languages and scripting layers. These are easier to use but sacrifice performance. Dynamic scripting can introduce layers of abstraction as well as additional security risk. For example, remote procedure calls (RPCs) must address network communication, serialization and deserialization, error handling, asynchronous behavior, multiplatform compatibility, and latency when those challenges cause problems. While a plugin that uses RPCs is flexible, it’s at the cost of greatly increased complexity.
Why Wasm Rocks: Fast, Secure, Flexible
So, what is this Wasm thing? Wasm is a binary format and runtime environment for executing code. In short, Wasm was created as a low-level, efficient and secure way to run code at near-native speeds. Wasm code is designed to be compiled from high-level programming languages such as C, C++, Golang and Rust. In reality, Wasm is language-agnostic and portable. This is becoming more important as developers who deploy and maintain applications increasingly prefer to write as much as possible in a single language (in other words, less YAML).
Wasm blows the standard plugin model wide open by allowing for far more flexible and manageable plugins. With Wasm, making plugins language-neutral, hardware-neutral, modular and isolated is much easier than with existing plugin models. This enables developers to customize behaviors beyond the browser, specific to their environment and use cases, in the language of their choice.
Wasm achieves all this while maintaining near-native code levels of performance thanks to:
- A compact binary format smaller than equivalent human-readable code, resulting in faster download and parse times.
- An instruction set that is closer to native machine instructions, allowing for faster interpretation and compilation to native code.
- An extremely fast JIT with strong typing that delivers better optimization opportunities for faster code generation and execution through application of a variety of optimization techniques.
- A contiguous, resizable linear memory model that simplifies memory management, allowing for more efficient memory access patterns.
- Concurrency and parallel execution that unlocks performance from multicore processors (currently a WIP).
Designed initially for running untrusted code on the web, Wasm has a particularly strong security model that includes:
- A sandboxed code execution environment that limits its access to system resources and ensures that it cannot interfere with other processes nor the operating system.
- A “memory-safe” architecture that helps prevent common security vulnerabilities such as buffer overflows.
- A robust typing system that enforces strict typing rules.
- Small code size compared to other runtimes, which reduces the attack surface.
- A bytecode format that is designed to be easy to analyze and optimize, which makes it easier to detect and fix potential security vulnerabilities.
- Minimal need to refactor code for different platforms because of its high degree of portability.
A More Flexible Way to Build Plugins
Server-side Wasm has a number of impressive potential benefits, both primary and secondary. To start, using Wasm environments can make it much easier for standard application developers to interact with backend systems. Wasm also allows anyone to set up granular guardrails for what a function can and cannot do when it attempts to interact with the lower-level functionality of a networking or server-side application. That’s important because backend systems may be interacting with sensitive data or require higher levels of trust.
Similarly, server systems can be configured or designed to limit interaction with the Wasm plugin environment by explicitly exporting only limited functionality or only providing specific file descriptors for communication. For example, every Wasm bytecode binary has an imports
section. Each import must be satisfied before instantiation. This allows a host system to register (or export in Wasm parlance) specific functions to interact with as a system.
Runtime engines will prevent instantiation of the Wasm module when those imports are not satisfied, giving host systems the ability to guardrail, control, validate and restrict what interaction the client has with the environment.
With more traditional plugin models and compiler technologies, creating this granularity and utility level is a challenge. The high degree of difficulty discourages developers from making plugins, further limiting choice. Perhaps most importantly, role-based access control and attribute-based access control, and other authorization and access control technologies, can introduce complex external systems that must be synchronized with the plugin as well as the underlying server-side technology. In contrast, Wasm access control capabilities are often built directly into the runtime engines, reducing the complexities and simplifying the development process.
Looking Ahead to the Great Wasm Future
In a future sprinkled with Wasm pixie dust, developers will be able to more easily design bespoke or semi-custom configurations and business logic for their applications. Additionally, they’ll be able to apply that to the server side to remove much of the development friction between backend, middle and frontend.
A Wasm-based plugin future could mean many cool things: easier and finer tuning of application performance, specific scaling and policy triggers based on application-level metrics and more.
With warg.io, we’re already seeing how Wasm might fuel innovative, composable approaches to building capabilities that apply the existing package management and registry approach to building with trusted Wasm code elements. In other words, Wasm might give us composable plugins that are not that different from the way a developer might put together several npm modules to achieve a specific functionality profile.
Application developers and DevOps teams generally have had blunt instruments to improve application performance. When latency issues or other problems arise, they have a few choices:
- Throw more compute at the problem.
- Increase memory (and, indirectly, I/O).
- Go into the code and try to identify the sources of latency.
The first two can be expensive. The last is incredibly laborious. With Wasm, developers can elect to run large parts of apps or functions that are slowing down performance inside a Wasm construct, and use a faster language or construct. They can do this without having to rip out the whole application and can focus on low-hanging fruit (for example, replacing slow JavaScript code used for calculations with C code or Go code compiled inside Wasm).
In fact, Wasm has a host of performance advantages over JavaScript. To paraphrase Lin Clark from Mozilla on the original Wasm team:
- It’s faster to fetch Wasm, as it is more compact than JavaScript, even when compressed.
- Decoding Wasm is faster than parsing JavaScript.
- Because Wasm is closer to machine code than JavaScript, and already has gone through optimization on the server side, compiling and optimizing takes less time.
- Code execution runs faster because there are fewer compiler tricks and gotchas necessary for the developer to know in order to write consistently performant code. Plus, Wasm’s set of instructions is more ideal for machines.
So let’s imagine this future: Microservices aren’t choreographing through expensive Kubernetes API server calls or internal east-west RPCs, but instead through modular, safe and highly performant Wasm components bounded within a smaller process space and surface area.
Traditionally, developers have used other data encoding languages like YAML to invoke custom resource definitions (CRDs) and other ways to add functionality to their applications running as microservices in Kubernetes. This adds overhead and complexity, making performance tuning more challenging. With a Wasm-based plugin, developers can take advantage of language primitives (Go, Rust, C++) that are well known and trusted rather than reinventing the wheel with more CRDs.