June 2023

WASM for microservices

In this blog post, I will share with you my experience of trying to use WebAssembly (Wasm) for building microservices. Wasm is a new technology that allows you to run compiled code in a browser or on a server. It promises to be faster, safer and more portable than traditional languages. However, as I learned the hard way, Wasm is not yet ready for prime time when it comes to microservices.

My project was inspired by a talk I attended at the Open Source Summit North America, where Kingdon Barrett of Weaveworks and Will Christensen of Defense Unicorns presented their findings on using Wasm for microservices. They had spent about three weeks exploring this topic and came up with a simple prototype that failed to meet their expectations. They also shared some of the challenges and limitations they faced with Wasm, such as:

– **No direct access to the network, file system or strings.**
– **No standard way to communicate with other services or external APIs.**
– **No mature tools or frameworks for developing and deploying Wasm microservices.**

I was intrigued by their talk and decided to give it a try myself. I wanted to see if I could overcome some of the obstacles they encountered and build a working microservice using Wasm. I chose Rust as my programming language, since it has good support for compiling to Wasm and is designed for performance and reliability. I also used WASI (WebAssembly System Interface) and WAGI (WebAssembly Gateway Interface) as the runtime environments for my microservice. WASI provides a set of system calls that Wasm modules can use, such as reading and writing files or opening sockets. WAGI allows Wasm modules to handle HTTP requests and responses using standard input and output.

My goal was to create a simple microservice that would take a text input from the user and return a summary of it using an external API. I thought this would be a good use case for Wasm, since it involves some computation and data processing that could benefit from the speed and safety of Wasm. I also wanted to test how easy or hard it would be to integrate with an existing service using Wasm.

The first challenge I faced was how to pass a string as an argument to my microservice. As Barrett and Christensen pointed out, there is no string type in Wasm. Instead, you have to manually allocate memory and copy bytes from one place to another. This is not only tedious but also error-prone. Fortunately, there are some libraries that can help with this task, such as wasm-bindgen for Rust. This library allows you to use Rust types like strings or vectors in your Wasm code and automatically handles the memory management for you.

The next challenge was how to make an HTTP request to the external API from my microservice. Since Wasm does not have direct access to the network, I had to use WASI’s socket functions to create a TCP connection and send and receive data over it. This was not too difficult, but it required me to write some low-level code that dealt with byte arrays, network protocols and error handling. It would have been much easier if I could use a high-level library like reqwest or curl for Rust, but unfortunately they do not work with Wasm yet.

The final challenge was how to deploy my microservice and make it accessible from the web. I used WAGI as the gateway for my microservice, which allowed me to run it as a standalone executable on any platform that supports WASI. However, WAGI is still very experimental and lacks some features that are essential for production use, such as logging, monitoring or authentication. Moreover, WAGI does not support HTTPS or load balancing, which means that I had to use another layer of proxy or service mesh to expose my microservice securely and reliably.

After spending several hours coding, debugging and testing, I managed to get my microservice working. It was able to take a text input from the user and return a summary of it using an external API. However, I was not very satisfied with the result. The code was complex, verbose and fragile. The performance was not impressive either. The microservice took about 300 milliseconds to respond on average, which is not much faster than a typical Node.js or Python service. The deployment process was also cumbersome and insecure.

In conclusion, I learned that Wasm is not yet ready for microservices. It has some potential advantages over traditional languages, such as speed, safety and portability, but it also has many drawbacks and limitations that make it unsuitable for real-world scenarios. It lacks standardization, maturity and ecosystem support that are essential for developing and deploying microservices effectively. It may improve in the future as more tools and frameworks emerge around it, but for now I would not recommend using it for microservices.

WASM for microservices Read More »

WebAssembly Binary Format Review

In this blog post, we will review the main concepts of WebAssembly binary format, which is a dense linear encoding of the abstract syntax of WebAssembly modules. WebAssembly (abbreviated as Wasm) is a binary instruction format for a stack-based virtual machine, designed as a portable compilation target for programming languages. Wasm can be executed at native speed by taking advantage of common hardware capabilities available on a wide range of platforms.

The binary format for WebAssembly modules is defined by an attribute grammar whose only terminal symbols are bytes. A byte sequence is a well-formed encoding of a module if and only if it is generated by the grammar. The grammar specifies how to encode each syntactic construct of WebAssembly using a variable-length integer encoding scheme that is similar to UTF-8 and LEB128.

The binary format has several advantages over a textual format. It is more compact, reducing the size of modules and improving loading times. It is also more efficient to parse and validate, as it can be done in a single pass over the bytes. Moreover, it is designed to be easy to generate and manipulate by compilers and tools.

The binary format consists of four main components:

  • A module header that identifies the file as a WebAssembly module and indicates the version of the format.
  • A section table that lists the sections present in the module and their sizes.
  • A sequence of sections that contain the actual data of the module, such as types, functions, globals, tables, memories, etc.
  • A name section that provides optional human-readable names for the elements of the module.

Each section has a unique id and a payload that depends on the section type. The sections can appear in any order, except for the custom sections that must be interleaved with the predefined sections. Custom sections can be used to store additional information that is not part of the core specification, such as debugging symbols or source maps.

The following diagram shows an example of a WebAssembly binary module with three sections: type, function, and code.

Module headerSection tableType sectionFunction sectionCode section
0x0061736d0x030x010x030x0a
0x010000000x070x010x020x09
0x600x010x02
0x000x000x07
0x010x00
0x7f0x41
0x01
0x10
0x00
0x0b

The module header consists of four bytes that spell out “\asm” in ASCII, followed by four bytes that indicate the version number in little-endian order. In this case, the version is 1.

The section table consists of a single byte that indicates the number of sections in the module, followed by pairs of bytes that indicate the id and size of each section. In this case, there are three sections: type (id = 1, size = 1), function (id = 3, size = 2), and code (id = 10, size = 9).

The type section consists of a single byte that indicates the number of function types in the module, followed by sequences of bytes that encode each function type. A function type is encoded as a byte that indicates the form of the type (currently only 0x60 is allowed), followed by two vectors of bytes that indicate the parameter types and the return types respectively. A vector is encoded as a byte that indicates the length of the vector, followed by one byte per element. A value type is encoded as a single byte that indicates its numeric representation: 0x7f for i32, 0x7e for i64, 0xf32 for f32, and 0xf64 for f64. In this case, there is one function type: (func) -> (i32).

The function section consists of a single byte that indicates the number of functions in the module, followed by one byte per function that indicates its type index. The type index is an unsigned integer that refers to an entry in the type section. In this case, there is one function with type index 0.

The code section consists of a single byte that indicates the number of function bodies in the module, followed by sequences of bytes that encode each function body. A function body is encoded as a vector of bytes that indicates its size, followed by a vector of bytes that indicates its local variables, followed by a sequence of bytes that indicates its instructions. A local variable is encoded as two bytes: one that indicates its count and one that indicates its type. An instruction is encoded as a single byte that indicates its opcode, followed by zero or more bytes that indicate its immediate operands. In this case, there is one function body with size 7, no local variables, and four instructions: i32.const 1 (opcode = 0x41, operand = 1), call 0 (opcode = 0x10, operand = 0), end (opcode = 0x0b), end (opcode = 0x0b).

This example illustrates how the WebAssembly binary format encodes modules in a compact and efficient way. For more details on the binary format and its grammar rules, you can refer to the official specification.

WebAssembly Binary Format Review Read More »