What Is LuaJIT? Why Does APISIX Choose LuaJIT?

Tao Yang

April 14, 2023

Products

API 게이트웨이의 수준을 한 단계 끌어올릴 준비가 되셨나요? 그렇다면 개발자 커뮤니티에서 주목받고 있는 클라우드 네이티브 API 게이트웨이인 Apache APISIX에 관심을 가져보세요. 더 흥미로운 점은 Apache APISIX가 주로 LuaJIT를 사용하여 구축되었다는 것입니다. LuaJIT는 가볍고 효율적인 언어로, 다른 유명한 언어들에 비해 덜 알려져 있습니다.

이 글에서는 Apache APISIX가 왜 더 유명한 언어와 기술 대신 LuaJIT를 선택했는지, 그리고 LuaJIT의 독특한 기능과 장점이 어떻게 가장 까다로운 워크로드도 처리할 수 있는 초고속 API 게이트웨이를 구축하는 데 도움이 되는지 자세히 살펴보겠습니다. 따라서 API 게이트웨이를 더욱 강력하게 만들고 싶다면 계속 읽어보세요!

LuaJIT란 무엇인가

정의

간단히 말해, LuaJIT는 Lua 프로그래밍 언어의 JIT(Just-In-Time) 컴파일러 구현체입니다. LuaJIT에 익숙하지 않은 독자들은 Lua와 JIT 두 부분으로 나누어 이해할 수 있습니다.

Lua

Lua는 우아하고 배우기 쉬운 프로그래밍 언어로, 자동 메모리 관리, 완전한 렉시컬 스코핑, 클로저, 이터레이터, 코루틴, 적절한 꼬리 호출, 연관 배열을 사용한 실용적인 데이터 처리 등의 기능을 제공합니다. Lua 문법에 대해 더 알아보고 싶다면 Lua 시작하기를 참고하세요.

Lua는 C나 다른 널리 사용되는 프로그래밍 언어와 쉽게 통합될 수 있도록 설계되어 개발자들이 해당 언어들의 강점을 활용할 수 있게 합니다. 하드웨어에 대한 높은 수준의 추상화, 동적 구조, 간단한 테스트 등 C와 같은 언어에서 일반적으로 강점이 아닌 기능들을 제공합니다. 작은 언어 커널과 ANSI C 표준에 의존함으로써 다양한 플랫폼에서 높은 이식성을 갖추고 있습니다. 결과적으로 Lua는 독립 실행형 프로그램으로 실행될 수 있는 스크립팅 언어일 뿐만 아니라 다른 애플리케이션에 통합될 수 있는 임베디드 언어이기도 합니다.

Apache APISIX는 Lua와 C를 저수준에서 모두 사용하는 훌륭한 예시입니다

그러나 이 시점에서 Lua는 여전히 전통적인 스크립팅 언어에서 발견되는 두 가지 일반적인 문제를 가지고 있었습니다: 낮은 효율성과 노출된 코드. LuaJIT가 도입한 JIT 기술은 이 두 문제를 효과적으로 해결할 수 있습니다.

JIT

JIT(Just-In-Time Compilation)는 동적 컴파일의 한 형태입니다. 동적 컴파일은 컴퓨터 과학에서 유일한 컴파일 형태가 아닙니다. 예를 들어, 널리 사용되는 C 언어는 정적 컴파일이라는 다른 형태의 컴파일을 사용합니다.

동적 컴파일과 반대되는 개념으로 Ahead-of-Time Compilation (AOT)이라는 용어를 사용하지만, 이 두 용어는 완전히 동일하지 않다는 점에 유의해야 합니다. AOT는 프로그램 실행 전에 "고수준" 언어를 "저수준" 언어로 컴파일하는 행위만을 설명합니다. 컴파일의 대상 언어는 반드시 프로그램 호스트 머신의 기계 코드일 필요는 없으며, 임의로 정의될 수 있습니다. 예를 들어, Java를 C로 컴파일하거나 JavaScript를 V8로 컴파일하는 것도 AOT로 간주될 수 있습니다. 모든 정적 컴파일은 기술적으로 실행 전에 수행되므로, 이 특정 맥락에서 AOT는 JIT와 반대되는 정적 컴파일로 볼 수 있습니다.

이러한 복잡한 용어를 제쳐두고 정적 컴파일의 출력을 고려할 때, Lua 언어가 직면한 문제들도 정적 컴파일로 해결될 수 있다는 것을 발견할 수 있습니다. 그러나 이렇게 하면 Lua가 스크립팅 언어로서 제공하는 장점인 핫 업데이트의 유연성과 좋은 플랫폼 호환성을 잃게 됩니다. 따라서 현재 특별한 요구사항이 없는 대부분의 스크립팅 언어들은 JIT를 사용하여 언어 성능을 개선하려고 시도하고 있습니다. 예를 들어, Chromium 플랫폼의 V8 JavaScript와 Ruby의 YJIT가 있습니다.

JIT는 Lua의 동적 인터프리테이션과 C의 정적 컴파일의 장단점을 결합하려고 합니다. 스크립팅 언어 실행 중에 JIT는 실행 중인 코드 조각을 지속적으로 분석하고, 이를 컴파일하거나 재컴파일하여 실행 효율성을 높입니다. 이때 JIT의 가정은 이 과정에서 얻는 성능 향상이 코드를 컴파일하거나 재컴파일하는 비용을 상쇄할 것이라는 것입니다. 이론적으로 동적으로 재컴파일할 수 있기 때문에 JIT는 실행 중인 프로그램이 기반으로 하는 특정 플랫폼 아키텍처에 대해 최적화 및 가속을 수행할 수 있으며, 경우에 따라 정적 컴파일보다 더 빠른 실행 속도를 낼 수 있습니다.

JIT는 전통적인 Method JIT와 현재 LuaJIT가 사용하는 Trace JIT 두 가지로 나뉩니다. Method JIT는 각 메서드를 기계 코드로 변환하는 반면, Trace JIT는 더 발전된 형태로 "한 번 또는 두 번만 실행되는 코드의 경우 인터프리터 실행이 JIT 컴파일 실행보다 빠르다"는 가정을 기반으로 합니다. 이를 바탕으로 Trace JIT는 전통적인 JIT를 최적화하여 자주 실행되는 코드 조각(즉, 핫 경로 상의 코드)을 추적 대상으로 식별하고 이 부분의 코드를 기계 코드로 컴파일하여 실행합니다. 아래 다이어그램에서 이를 확인할 수 있습니다.

LuaJIT의 원리

LuaJIT

LuaJIT(버전 2.x)는 어셈블리 언어로 작성된 고속 인터프리터와 Static Single Assignment (SSA) 기반의 최적화된 코드 생성기 백엔드를 통합하여 JIT 성능을 크게 향상시켰습니다. 결과적으로 LuaJIT는 가장 빠른 동적 언어 구현체 중 하나가 되었습니다.

또한, 네이티브 Lua에서 C와의 상호작용을 위한 번거로운 바인딩과 비교하여 LuaJIT는 FFI(Foreign Function Interface)도 구현했습니다. 이 기술을 통해 Lua 코드에서 외부 C 함수를 호출하고 C 데이터 구조를 직접 사용할 수 있으며, 매개변수의 수와 유형을 알 필요가 없습니다. 이 기능을 통해 성능에 민감한 시나리오에서 네이티브 Lua Table 타입 대신 필요한 데이터 구조를 직접 구현할 수 있어 프로그램 성능을 더욱 향상시킬 수 있습니다. FFI를 사용하여 성능을 개선하는 기술은 이 글의 범위를 벗어나며, 더 깊이 있는 정보는 Why Does lua-resty-core Perform Better? 글에서 확인할 수 있습니다.

요약하자면, LuaJIT는 Lua 문법을 사용하여 현재까지 스크립팅 언어 중 가장 빠른 Trace JIT 중 하나를 구현했습니다. 또한 FFI와 같은 기능을 제공하여 Lua의 낮은 효율성과 노출된 코드 문제를 해결함으로써 Lua를 높은 유연성, 고성능, 초저 메모리 사용량의 스크립팅 및 임베디드 언어로 만들었습니다.

WASM 및 다른 언어와의 비교

Lua와 LuaJIT와 비교하여 우리는 JavaScript(Node.js), Python, Golang, Java와 같은 다른 언어들에 더 익숙할 수 있습니다. Lua/LuaJIT와 이러한 인기 있는 언어들을 비교함으로써 LuaJIT의 독특한 기능과 장점을 더 잘 이해할 수 있습니다. 아래는 간단한 비교입니다:

  • Lua의 문법은 비소프트웨어 엔지니어를 위해 설계되었습니다. R 언어와 마찬가지로 Lua도 배열 인덱스가 1부터 시작하여 일반인에게 적합합니다.
  • Lua는 임베디드 언어로 매우 적합합니다. Lua 자체는 가벼운 VM을 가지고 있으며, LuaJIT는 다양한 기능과 최적화를 추가한 후에도 여전히 가볍습니다. 결과적으로 LuaJIT는 C 프로그램에 직접 통합될 때 크기가 크게 증가하지 않으며, Node.js와 Python과 같은 대형 런타임 환경과는 다릅니다. 따라서 Lua는 사실상 모든 임베디드 언어 중에서 가장 널리 사용되고 주류 선택입니다.
  • Lua는 "접착제" 언어로도 매우 적합합니다. JavaScript(Node.js)와 Python처럼 Lua도 다른 라이브러리와 코드를 잘 연결할 수 있습니다. 그러나 다른 언어들과 약간 다른 점은 Lua는 기본 생태계와 더 높은 결합도를 가지고 있기 때문에 Lua 생태계가 다른 분야에서 보편적이지 않을 수 있습니다.

WASM(Web Assembly)은 새로운 크로스 플랫폼 기술입니다. 이 기술은 초기에 JavaScript를 보완하기 위해 설계되었으며, 다른 언어를 WASM 바이트코드로 컴파일하고 보안 샌드박스로 코드를 실행할 수 있어 점점 더 많은 프로그램들이 WASM을 임베디드 또는 "접착제" 플랫폼으로 사용하려고 합니다. 그럼에도 불구하고 Lua/LuaJIT는 새로운 WASM과 비교했을 때 여전히 많은 장점을 가지고 있습니다:

  • WASM의 성능은 제한적이며 어셈블리 수준에 도달할 수 없습니다. 일반적인 시나리오에서 WASM은 Lua보다 성능이 우수하지만, LuaJIT와는 여전히 차이가 있습니다.
  • WASM과 호스트 프로그램 간의 데이터 전송 효율성은 상대적으로 낮습니다. 반면 LuaJIT는 FFI를 통해 효율적인 데이터 전송을 수행할 수 있습니다.

Apache APISIX가 LuaJIT를 선택한 이유

위에서 LuaJIT의 많은 장점을 설명했지만, Lua는 대부분의 개발자들에게 인기 있는 언어도 아니고 선택도 아닙니다. 그렇다면 Apache 재단의 클라우드 네이티브 API 게이트웨이인 Apache APISIX는 왜 LuaJIT를 선택했을까요?

클라우드 네이티브 API 게이트웨이로서 Apache APISIX는 동적, 실시간, 고성능의 특성을 가지고 있으며, 로드 밸런싱, 동적 업스트림, 카나리 릴리스, 서비스 저하, 신원 인증, 관측 가능성 등과 같은 풍부한 트래픽 관리 기능을 제공합니다. Apache APISIX를 사용하여 전통적인 남북 트래픽뿐만 아니라 서비스 간의 동서 트래픽도 처리할 수 있으며, k8s의 Ingress 컨트롤러로도 사용할 수 있습니다.

이 모든 것은 Apache APISIX가 선택한 NGINX와 LuaJIT 기술 스택 위에 구축되었습니다.

LuaJIT와 NGINX 결합의 장점

NGINX는 고성능 웹 서버로 잘 알려져 있으며, HTTP, TCP/UDP 프록시 및 리버스 프록시로 사용됩니다.

그러나 실제로 NGINX 설정 파일을 수정할 때마다 nginx -s reload 명령어를 사용하여 NGINX 설정을 다시 로드해야 하는 것이 번거롭습니다.

또한, 이 명령어를 자주 사용하여 설정을 다시 로드하면 연결 불안정성이 발생하고 비즈니스 손실 가능성이 높아질 수 있습니다. 경우에 따라 NGINX 설정 다시 로드 메커니즘이 오래된 프로세스가 너무 오래 회수되지 않아 정상적인 비즈니스 운영에 영향을 미칠 수도 있습니다. 이 주제에 대한 더 포괄적인 분석은 Why NGINX's reload is not a hot reload? 글을 참고하세요. 여기서는 이 주제를 더 깊이 다루지 않겠습니다.

Apache APISIX의 목적 중 하나는 NGINX의 동적 설정 문제를 해결하는 것입니다. LuaJIT의 높은 유연성, 고성능, 초저 메모리 사용량이 이를 가능하게 합니다. 가장 일반적인 라우팅을 예로 들면, Apache APISIX는 NGINX 설정 파일에서 단일 location을 주요 진입점으로 설정하고, 이후의 라우팅 분배는 APISIX의 라우팅 분배 모듈이 완료함으로써 라우팅의 동적 설정을 달성합니다.

고성능을 달성하기 위해 Apache APISIX는 C로 작성된 접두사 트리 기반의 라우팅 매칭 알고리즘을 사용하며, 이를 기반으로 LuaJIT가 제공하는 FFI를 사용하여 Lua 인터페이스를 제공합니다. Lua의 유연성은 Apache APISIX의 라우팅 분배 모듈이 특정 표현식 등을 통해 동일한 접두사의 하위 라우팅 매칭을 쉽게 지원할 수 있게 합니다. 궁극적으로 NGINX의 네이티브 라우팅 분배 기능을 대체함으로써 고성능과 유연성을 갖춘 동적 설정 기능을 달성합니다. 이 기능의 상세 구현은 lua-resty-radixtreeroute.lua를 참고하세요.

라우팅 외에도 APISIX는 서버를 재시작하지 않고도 로드 밸런싱, 헬스 체크, 업스트림 노드 설정, 서버 인증서, APISIX 기능을 확장하는 플러그인과 같은 기능을 다시 로드할 수 있습니다.

또한, LuaJIT를 사용하여 플러그인 및 기타 기능을 개발하는 것 외에도, Apache APISIX는 Java, Go, Node, Python, WASM과 같은 다양한 언어를 사용하여 플러그인을 개발할 수 있도록 지원합니다. 이는 Apache APISIX의 맞춤형 개발 문턱을 크게 낮추어 풍부한 플러그인 생태계와 활발한 오픈소스 커뮤니티를 형성했습니다.

Apache APISIX의 플러그인 원리와 생태계

결론

LuaJIT는 Lua의 JIT 컴파일러 구현체입니다.

동적, 실시간, 고성능 오픈소스 API 게이트웨이인 Apache APISIX는 NGINX와 LuaJIT가 제공하는 고성능과 높은 유연성을 기반으로 로드 밸런싱, 동적 업스트림, 카나리 릴리스, 서킷 브레이커, 신원 인증, 관측 가능성과 같은 풍부한 트래픽 관리 기능을 제공합니다.

현재 Apache APISIX는 새로운 버전인 3.x를 출시했으며, 오픈소스 프로젝트 및 클라우드 제공자와의 더 많은 통합, 네이티브 gRPC 지원, 추가 플러그인 개발 옵션, 서비스 메시 지원 등을 포함하고 있습니다. Apache APISIX 커뮤니티에 참여하여 클라우드 네이티브 API 게이트웨이에서 LuaJIT의 응용에 대해 더 알아보세요.

Tags: