---
title: "Why Does lua-resty-core Perform Better？ - API7.ai"
description: "The lua-resty-core is a new FFI-based API for lua-nginx-module, which provides higher performance. Learn more from this post."
url: "https://api7.ai/ko/learning-center/openresty/why-lua-resty-core-perform-better"
---

# Why Does lua-resty-core Perform Better？

API7.ai

September 30, 2022

[OpenResty (NGINX + Lua)](https://api7.ai/learning-center/openresty)

이전 두 강의에서 말했듯이, Lua는 핵심을 짧고 간결하게 유지하는 임베디드 개발 언어입니다. Lua를 Redis와 NGINX에 임베디드하여 비즈니스 로직을 유연하게 처리할 수 있습니다. 또한 Lua는 기존의 C 함수와 데이터 구조를 호출하여 반복 작업을 피할 수 있게 해줍니다.

Lua에서는 Lua C API를 사용하여 C 함수를 호출할 수 있으며, LuaJIT에서는 FFI를 사용할 수 있습니다. OpenResty의 경우:

- `lua-nginx-module` 코어에서는 C 함수를 호출하기 위한 API가 Lua C API를 사용하여 구현됩니다.
- `lua-resty-core`에서는 `lua-nginx-module`에 이미 존재하는 일부 API가 FFI 모델을 사용하여 구현됩니다.

아마도 왜 FFI로 구현해야 하는지 궁금할 것입니다.

걱정하지 마세요. 간단한 API인 [ngx.base64\_decode](https://github.com/openresty/lua-nginx-module#ngxdecode_base64)를 예로 들어 Lua C API와 FFI 구현이 어떻게 다른지 살펴보겠습니다. 이를 통해 그들의 성능에 대한 직관적인 이해를 얻을 수 있습니다.

## Lua CFunction

먼저 `lua-nginx-module`에서 Lua C API를 사용하여 어떻게 구현되었는지 살펴보겠습니다. 프로젝트 코드에서 `decode_base64`를 검색하면 `ngx_http_lua_string.c` 파일에서 그 구현을 찾을 수 있습니다.

```
lua_pushcfunction(L, ngx_http_lua_ngx_decode_base64);
lua_setfield(L, -2, "decode_base64");
```

위 코드는 보기 어렵지만, 다행히도 `lua_`로 시작하는 두 함수와 그들의 인자의 구체적인 역할을 파헤칠 필요는 없습니다. 여기서 중요한 것은 `ngx_http_lua_ngx_decode_base64`라는 CFunction이 등록되어 있으며, 이는 `ngx.base64_decode`에 해당하는 API라는 점입니다.

이제 이 CFunction을 따라가서 `ngx_http_lua_ngx_decode_base64`를 검색해보겠습니다. 이 함수는 파일의 시작 부분에 정의되어 있습니다.

```
static int ngx_http_lua_ngx_decode_base64(lua_State *L);
```

Lua에서 호출할 수 있는 C 함수는 Lua가 요구하는 형식을 따라야 합니다. 즉, `typedef int (*lua_CFunction)(lua_State* L)`입니다. 이는 `lua_State` 타입의 포인터 L을 인자로 포함하며, 반환 값은 반환 값 자체가 아니라 반환 값의 개수를 나타내는 정수입니다.

이 함수는 다음과 같이 구현됩니다 (여기서는 오류 처리 코드를 제거했습니다).

```
static int
 ngx_http_lua_ngx_decode_base64(lua_State *L)
 {
     ngx_str_t p, src;

    src.data = (u_char *) luaL_checklstring(L, 1, &src.len);

     p.len = ngx_base64_decoded_length(src.len);

     p.data = lua_newuserdata(L, p.len);

     if (ngx_decode_base64(&p, &src) == NGX_OK) {
         lua_pushlstring(L, (char *) p.data, p.len);

     } else {
         lua_pushnil(L);
     }

     return 1;
 }
```

이 코드에서 주요한 부분은 `ngx_base64_decoded_length`와 `ngx_decode_base64`입니다. 이 둘은 NGINX가 제공하는 C 함수입니다.

C로 작성된 함수는 반환 값을 Lua 코드에 직접 전달할 수 없으며, Lua와 C 사이에서 호출 매개변수와 반환 값을 스택을 통해 전달해야 합니다. 이 때문에 처음 보기에는 이해하기 어려운 코드가 많습니다. 또한 이 코드는 JIT에 의해 추적될 수 없으므로, LuaJIT에게는 이 작업들이 블랙박스처럼 보여 최적화할 수 없습니다.

## LuaJIT FFI

FFI는 다릅니다. FFI의 상호작용 부분은 Lua에서 구현되며, 이는 JIT에 의해 추적되고 최적화될 수 있습니다. 물론 코드도 더 간결하고 이해하기 쉽습니다.

`base64_decode`를 예로 들어보겠습니다. FFI 구현은 `lua-resty-core`와 `lua-nginx-module` 두 저장소에 걸쳐 있으며, 전자의 [구현 코드](https://github.com/openresty/lua-resty-core/blob/master/lib/resty/core/base64.lua#L72)를 살펴보겠습니다.

```
ngx.decode_base64 = function (s)
     local slen = #s
     local dlen = base64_decoded_length(slen)

     local dst = get_string_buf(dlen)
     local pdlen = get_size_ptr()
     local ok = C.ngx_http_lua_ffi_decode_base64(s, slen, dst, pdlen)
     if ok == 0 then
         return nil
     end
     return ffi_string(dst, pdlen[0])
 end
```

CFunction과 비교했을 때, FFI 구현의 코드가 훨씬 깔끔하다는 것을 알 수 있습니다. 이의 구체적인 구현은 `lua-nginx-module` 저장소의 `ngx_http_lua_ffi_decode_base64`입니다. 여기서 관심이 있다면 이 함수의 성능을 직접 확인해보세요. 매우 간단하므로 여기서 코드를 더 보여드리지는 않겠습니다.

그런데 위 코드 조각에서 함수 이름 규칙을 발견하셨나요?

네, OpenResty의 모든 함수는 명명 규칙을 따르며, 이름을 통해 그 사용법을 유추할 수 있습니다. 예를 들어:

- `ngx_http_lua_ffi_`, FFI를 사용하여 NGINX HTTP 요청을 처리하는 Lua 함수.
- `ngx_http_lua_ngx_`, C 함수를 사용하여 NGINX HTTP 요청을 처리하는 Lua 함수.
- `ngx_`와 `lua_`로 시작하는 다른 함수들은 각각 NGINX와 Lua의 내장 함수입니다.

더 나아가, OpenResty의 C 코드는 엄격한 코드 규칙을 따르며, [공식 C 코드 스타일 가이드](https://openresty.org/cn/c-coding-style-guide.html)를 읽어보는 것을 추천합니다. 이는 OpenResty의 C 코드를 배우고 PR을 제출하려는 개발자에게 필수 문서입니다. 그렇지 않으면, 코드 스타일 문제로 인해 PR이 잘 작성되었더라도 반복적으로 코멘트를 받고 수정을 요청받을 수 있습니다.

FFI에 대한 더 많은 API와 세부 사항은 [공식 LuaJIT 튜토리얼](http://luajit.org/ext_ffi_tutorial.html)과 [문서](http://luajit.org/ext_ffi_api.html)를 읽어보시기 바랍니다. 기술 칼럼은 공식 문서를 대체할 수 없으며, 저는 제한된 시간 내에 학습 경로를 안내하고, 덜 돌아가도록 도울 뿐입니다. 어려운 문제는 여전히 여러분이 해결해야 합니다.

## LuaJIT FFI GC

FFI를 사용할 때, 우리는 혼란스러울 수 있습니다: FFI에서 요청한 메모리를 누가 관리할까요? C에서 수동으로 해제해야 할까요, 아니면 LuaJIT가 자동으로 회수할까요?

여기 간단한 원칙이 있습니다: LuaJIT는 자신이 할당한 리소스만 관리합니다; ffi.

예를 들어, `ffi.C.malloc`을 사용하여 메모리 블록을 요청했다면, 이에 대응하는 `ffi.C.free`로 해제해야 합니다. 공식 LuaJIT 문서에는 이에 대한 예제가 있습니다.

```
local p = ffi.gc(ffi.C.malloc(n), ffi.C.free)
 ...
 p = nil -- Last reference to p is gone.
 -- GC will eventually run finalizer: ffi.C.free(p)
```

이 코드에서 `ffi.C.malloc(n)`은 메모리 섹션을 요청하고, `ffi.gc`는 소멸 콜백 함수 `ffi.C.free`를 등록합니다. `ffi.C.free`는 cdata `p`가 LuaJIT에 의해 GC될 때 자동으로 호출되어 C 레벨 메모리를 해제합니다. 그리고 cdata는 LuaJIT에 의해 GC됩니다. LuaJIT는 위 코드에서 `p`를 자동으로 해제합니다.

OpenResty에서 큰 메모리 청크를 요청하려면 `ffi.new` 대신 `ffi.C.malloc`을 사용하는 것을 추천합니다. 그 이유는 명확합니다.

1. `ffi.new`는 `cdata`를 반환하며, 이는 LuaJIT가 관리하는 메모리의 일부입니다.
2. LuaJIT GC는 메모리 관리의 상한선이 있으며, OpenResty의 LuaJIT는 GC64 옵션이 활성화되어 있지 않습니다. 따라서 **단일 작업자의 메모리 상한선은 2G**입니다. LuaJIT 메모리 관리의 상한선을 초과하면 오류가 발생합니다.

**FFI를 사용할 때는 메모리 누수에 특히 주의해야 합니다**. 하지만 모두가 실수를 하며, 인간이 작성한 코드에는 항상 버그가 있습니다.

이때 OpenResty의 강력한 테스트 및 디버깅 도구 체인이 유용합니다.

먼저 테스트에 대해 이야기해보겠습니다. OpenResty 시스템에서는 Valgrind를 사용하여 메모리 누수를 감지합니다.

이전 강의에서 언급한 테스트 프레임워크 `test::nginx`는 메모리 누수 감지 모드로 단위 테스트 케이스 세트를 실행할 수 있습니다. 이 모드를 사용하려면 환경 변수 `TEST_NGINX_USE_VALGRIND=1`을 설정해야 합니다. 공식 OpenResty 프로젝트는 버전을 출시하기 전에 이 모드에서 전체 테스트를 수행하며, 나중에 테스트 섹션에서 더 자세히 다루겠습니다.

OpenResty의 CLI 도구인 resty도 `--valgrind` 옵션을 제공하며, 이를 통해 테스트 케이스를 작성하지 않았더라도 단독으로 Lua 코드를 실행할 수 있습니다.

이제 디버깅 도구를 살펴보겠습니다.

OpenResty는 [systemtap 기반 확장](https://github.com/openresty/stapxx)을 제공하여 OpenResty 프로그램의 실시간 동적 분석을 수행할 수 있습니다. 이 프로젝트의 도구 세트에서 `gc` 키워드를 검색하면 `lj-gc`와 `lj-gc-objs` 두 도구를 찾을 수 있습니다.

`core dump`와 같은 오프라인 분석을 위해 OpenResty는 [GDB 도구 세트](https://github.com/openresty/openresty-gdb-utils)를 제공하며, 여기서도 `gc`를 검색하여 `lgc`, `lgcstat`, `lgcpath` 세 도구를 찾을 수 있습니다.

이 디버깅 도구들의 구체적인 사용법은 나중에 디버깅 섹션에서 자세히 다루겠습니다. 일단은 OpenResty가 이러한 문제를 찾고 해결하는 데 도움을 주는 전용 도구 세트가 있다는 것을 기억해두세요.

## lua-resty-core

위 비교에서 볼 수 있듯이, FFI 방식은 코드가 더 깔끔할 뿐만 아니라 LuaJIT에 의해 최적화될 수 있어 더 나은 선택입니다. OpenResty는 CFunction 구현을 더 이상 사용하지 않으며, 성능이 코드베이스에서 제거되었습니다. 새로운 API들은 이제 `lua-resty-core` 저장소에서 FFI를 통해 구현됩니다.

2019년 5월에 출시된 OpenResty 1.15.8.1 이전에는 `lua-resty-core`가 기본적으로 활성화되지 않았으며, 이로 인해 성능 손실과 잠재적인 버그가 발생했습니다. 따라서 역사적인 버전을 사용 중이라면 `lua-resty-core`를 수동으로 활성화하는 것을 강력히 권장합니다. `init_by_lua` 단계에 한 줄의 코드만 추가하면 됩니다.

```
require "resty.core"
```

물론, 1.15.8.1 릴리스에서는 `lua_load_resty_core` 지시어가 추가되었으며, `lua-resty-core`가 기본적으로 활성화됩니다.

개인적으로 OpenResty가 `lua-resty-core` 활성화에 대해 너무 조심스러운 것 같습니다. 오픈소스 프로젝트는 가능한 한 빨리 이러한 기능을 기본적으로 활성화해야 합니다.

`lua-resty-core`는 `lua-nginx-module` 프로젝트의 일부 API를 재구현할 뿐만 아니라, `ngx.re.match`, `ngx.md5` 등과 같은 새로운 API도 구현했습니다. 예를 들어 `ngx.ssl`, `ngx.base64`, `ngx.errlog`, `ngx.process`, `ngx.re.split`, `ngx.resp.add_header`, `ngx.balancer`, `ngx.semaphore` 등이 있으며, 이는 나중에 OpenResty API 챕터에서 다루겠습니다.

## 결론

이 모든 것을 말씀드린 후, FFI가 좋지만 성능의 만병통치약은 아니라는 결론을 내리고 싶습니다. FFI가 효율적인 주된 이유는 JIT에 의해 추적되고 최적화될 수 있기 때문입니다. 만약 JIT되지 않고 인터프리터 모드에서 실행되어야 하는 Lua 코드를 작성한다면, FFI는 덜 효율적일 것입니다.

그렇다면 어떤 작업이 JIT될 수 있고, 어떤 작업이 JIT될 수 없을까요? JIT되지 않는 코드를 작성하지 않으려면 어떻게 해야 할까요? 이에 대해서는 다음 섹션에서 밝히겠습니다.

마지막으로, 실습 과제를 드리겠습니다: `lua-nginx-module`과 `lua-resty-core`에서 각각 하나 또는 두 개의 API를 찾아 성능 테스트를 비교해보세요. FFI의 성능 향상이 얼마나 큰지 확인할 수 있을 것입니다.

댓글을 남겨주시면, 여러분의 생각과 얻은 점을 공유하겠습니다. 이 글을 동료와 친구들과 공유하여 함께 교류하고 발전하길 바랍니다.

Share article link

Share

Related Posts

[![OpenResty FAQ | OpenResty의 실제 활용 사례](https://static.api7.ai/uploads/2023/02/12/jbv9M0zi_Hello%20World%20in%20OpenResty%20%283%29.png)](https://api7.ai/learning-center/openresty/faq-5)

## [OpenResty FAQ | OpenResty의 실제 활용 사례](https://api7.ai/learning-center/openresty/faq-5)

[![파트 3: OpenResty를 사용하여 마이크로서비스 API 게이트웨이 구축하기](https://static.api7.ai/uploads/2023/02/07/3XJXY9Jy_45.png)](https://api7.ai/learning-center/openresty/how-to-build-a-microservices-api-gateway-part-3)

## [파트 3: OpenResty를 사용하여 마이크로서비스 API 게이트웨이 구축하기](https://api7.ai/learning-center/openresty/how-to-build-a-microservices-api-gateway-part-3)

Table of Contents

- [Lua CFunction](#lua-cfunction)
- [LuaJIT FFI](#luajit-ffi)
- [LuaJIT FFI GC](#luajit-ffi-gc)
- [lua-resty-core](#lua-resty-core)
- [결론](#%EA%B2%B0%EB%A1%A0)
