What Is LuaJIT? Why Does APISIX Choose LuaJIT?
Are you ready to take your API gateway game to the next level? Then you might want to pay attention to Apache APISIX, a cloud-native API gateway that's been making waves in the developer community. What's even more interesting is that Apache APISIX is primarily built using LuaJIT, a lightweight and efficient language that's not as well-known as some of its counterparts.
In this article, we'll take a closer look at why Apache APISIX chose LuaJIT over more popular languages and technologies, and how LuaJIT's unique features and advantages can help you build blazing-fast API gateways that can handle even the most demanding workloads. So, if you're looking to supercharge your API gateway, keep reading!
What is LuaJIT
Simply put, LuaJIT is the implementation of a just-in-time (JIT) compiler for the Lua programming language. For better understanding, readers who are unfamiliar with LuaJIT can break it down into two parts: Lua and JIT.
Lua is an elegant and easy-to-learn programming language that features automatic memory management, complete lexical scoping, closures, iterators, coroutines, proper tail calls, and practical data handling using associative arrays. For more reading about Lua syntax, welcome to read Getting Started With Lua for more information.
Lua is designed to be easily integrated with C or other widely used programming languages, allowing developers to take advantage of the strengths of those languages. It provides features that are not typically strong points of languages like C, such as high-level abstractions relative to hardware, dynamic structures, and simple testing. Its small language kernel and reliance on the ANSI C standard make it highly portable across different platforms. As a result, Lua is not only a scripting language that can run as a standalone program but also an embedded language that can be integrated into other applications.
However, at this time, Lua still had two common problems found in traditional scripting languages: low efficiency and exposed code. The JIT technology introduced by LuaJIT can effectively solve these two problems.
JIT (Just-In-Time Compilation), is a form of dynamic compilation. Dynamic compilation is not the only form of compilation in computer science. For instance, the widely used C language employs a different form of compilation, known as static compilation.
JIT tries to combine the advantages and disadvantages of Lua's dynamic interpretation and C's static compilation. During the execution of the scripting language, JIT continuously analyzes the code fragments being executed, and compiles or recompiles them to improve execution efficiency. At this point, the assumption behind JIT is that the performance gain obtained from this process will outweigh the cost of compiling or recompiling the code. In theory, since it can be dynamically recompiled, JIT can optimize and accelerate for the specific platform architecture on which the running program is based, and in some cases, produce faster execution speeds than a static compilation.
JIT is divided into two types: the traditional
Method JIT and the
Trace JIT currently used by LuaJIT. Method JIT translates each method into machine code, while Trace JIT, which is more advanced, assumes that "For code that is only executed once or twice, interpreted execution is faster than JIT compiled execution". Based on this, Trace JIT optimizes traditional JIT by identifying frequently executed code fragments (i.e., code on the hot path) as code that needs to be traced and compiling this portion of the code into machine code for execution, as shown in the diagram below.
LuaJIT (version 2.x) improves JIT performance significantly by integrating a high-speed interpreter written in Assembly language and an optimized code generator backend based on Static Single Assignment (SSA). As a result, LuaJIT has become one of the fastest dynamic language implementations.
In addition, compared to the cumbersome binding of Lua and C in native Lua for C interaction, LuaJIT also implements FFI (Foreign Function Interface). This technology allows us to call external C functions and use C data structures directly from Lua code without knowing the number and type of parameters. With this feature, we can directly use FFI to implement the required data structures instead of the native Lua
Table type, further improving program performance in performance-sensitive scenarios. The techniques for using FFI to improve performance are beyond the scope of this article, and more in-depth information can be found in the article Why Does lua-resty-core Perform Better?.
In summary, LuaJIT has implemented one of the fastest Trace JITs in scripting languages to date, using Lua syntax. Additionally, it provides features, such as FFI, to address the low efficiency and exposed code issues of Lua, thereby making Lua a highly flexible, high-performance, and ultra-low memory footprint scripting and embedded language.
Comparison to WASM and Other Languages
- Lua's syntax is designed for non-software engineers. Similar to R language, Lua also has an array index starting from 1, which is suitable for ordinary people.
- Lua is very suitable as an embedded language. Lua itself has a lightweight VM, and LuaJIT is still lightweight even after adding various features and optimizations. As a result, LuaJIT's size doesn't increase significantly when integrated directly into C programs, unlike large runtime environments such as Node.js and Python. Thus, Lua is, in fact, the most widely used and mainstream choice among all embedded languages.
- WASM's performance is limited and cannot reach the level of assembly. In general scenarios, WASM is certainly better than Lua in terms of performance, but there is still a gap between WASM and LuaJIT.
- The data transmission efficiency between WASM and the host program is relatively low. On the other hand, LuaJIT can perform efficient data transmission through FFI.
Why Does Apache APISIX Choose LuaJIT?
Although many advantages of LuaJIT have been described above, Lua is neither a popular language nor a popular choice for most developers. So why did Apache APISIX, a cloud-native API gateway under the Apache Foundation, choose LuaJIT?
As a cloud-native API gateway, Apache APISIX has the characteristics of being dynamic, real-time, and high-performance, providing rich traffic management functions such as load balancing, dynamic upstream, canary release, service degradation, identity authentication, observability, etc. We can use Apache APISIX to handle traditional north-south traffic, as well as east-west traffic between services, and it can also serve as an Ingress controller for k8s.
All of them are built on the NGINX and LuaJIT technology stack chosen by Apache APISIX.
Advantages of Combining LuaJIT with NGINX
NGINX is a well-known high-performance web server that serves as an HTTP, TCP/UDP proxy and reverse proxy.
However, in practice, we find it annoying that every time we modify the NGINX configuration file, we need to use the command
nginx -s reload to reload the NGINX configuration.
Moreover, frequent use of this command to reload the configuration may cause connection instability and increase the likelihood of business loss. In some cases, the NGINX configuration reloading mechanism may also cause old processes to take too long to be reclaimed, affecting normal business operations. For a more comprehensive analysis of this topic, we recommend reading the article Why NGINX's reload is not a hot reload?. We will not explore this subject in further detail here.
One of the purposes of Apache APISIX is to solve the dynamic configuration problem of NGINX. LuaJIT's high flexibility, high performance, and ultra-low memory usage make this possible. Taking the most common route as an example, Apache APISIX only configures a single location as the main entry point in the NGINX configuration file, and the subsequent route distribution is completed by APISIX's route distribution module, thereby achieving a dynamic configuration of routes.
In order to achieve high performance, Apache APISIX uses a prefix tree-based route-matching algorithm written in C, and on this basis, it provides an interface for Lua using FFI provided by LuaJIT. The flexibility of Lua also enables the routing distribution module of Apache APISIX to easily support the matching of subordinate routes of the same prefix through specific expressions and other methods. Ultimately, by replacing the native routing distribution function of NGINX, it achieves dynamic configuration functionality with high performance and flexibility. For the detailed implementation of this feature, you can refer to lua-resty-radixtree and route.lua.
In addition to routing, APISIX can also reload functions like balancing, health checks, upstream node configuration, server certificates, and plugins that extend APISIX capabilities without restarting the server.
Moreover, in addition to developing plugins and other features using LuaJIT, Apache APISIX also supports the development of plugins using various languages such as Java, Go, Node, Python, and WASM. This greatly reduces the threshold for custom development of Apache APISIX, resulting in a rich plugin ecosystem and an active open-source community.
LuaJIT is an implementation of Lua, a just-in-time compiler.
As a dynamic, real-time, high-performance open-source API gateway, Apache APISIX provides rich traffic management functions such as load balancing, dynamic upstream, canary release, circuit breaker, identity authentication, and observability, based on the high-performance and high flexibility brought by NGINX and LuaJIT.
Currently, Apache APISIX has released a new version, 3.x, which includes more integrations with open-source projects and cloud providers, native gRPC support, additional plugin development options, and service mesh support. Join the Apache APISIX community to learn more about the application of LuaJIT in cloud-native API gateways.