OpenResty における LuaJIT:動作の仕組みと luajit2 の違い
OpenResty は LuaJIT 2.1 で Lua コードを実行します。LuaJIT は各 Nginx worker プロセスに組み込まれており、リクエスト処理のコードは軽量なコルーチン上で動き、ホットパスはマシンコードにコンパイルされます。オープンソース版のリリースには、OpenResty が保守する luajit2 ブランチが、同じリリースの他のコンポーネントとともに同梱されています。簡単に言えば、LuaJIT はランタイムであり、luajit2 は OpenResty 向けの変更を加えて OpenResty が保守する LuaJIT のブランチです。luajit2 の README では、上流の変更を定期的に同期していることから、フォークではなくブランチと位置付けられています。OpenResty Inc. は、標準 LuaJIT ランタイムを直接置き換えられるプライベートな拡張ランタイム LuaJIT-plus も提供しています。本記事では、OpenResty における LuaJIT の動作の仕組みと、これらの違いが実際に何を意味するかを説明します。
OpenResty が使用する Lua ランタイム
LuaJIT は、Mike Pall によって作成された Lua 言語のインタープリター兼 Just-In-Time(JIT)コンパイラーです。Lua 5.1 を実装し、一部の Lua 5.2 互換拡張に加えて、ffi、bit、jit などの LuaJIT 固有の機能を提供します。OpenResty は LuaJIT 2.1 を使用します。lua-nginx-module v0.10.16 以降、このモジュールは LuaJIT を必須とし、標準 Lua インタープリター(PUC-Rio Lua)をサポートしていません。
OpenResty のリリースで使用される LuaJIT は、OpenResty が保守する luajit2 ブランチに由来します。このブランチは上流の変更を定期的に同期するとともに、OpenResty が独自に保守する変更も含んでいます。
各 OpenResty リリースに同梱される LuaJIT のバージョンは、対応するリリースノートやソースパッケージで確認できます。例えば、OpenResty 1.31.1.1 には LuaJIT v2.1-20260415 が同梱されています。通常は、LuaJIT、lua-nginx-module、その他の依存関係を個別に組み合わせるのではなく、リリースに同梱された組み合わせを使用してデプロイしてください。
Lua コードが Nginx worker 内で実行される仕組み
worker、仮想マシン、モジュール状態
LuaJIT 仮想マシンは Nginx worker プロセス内に存在します。HTTP サブシステムと Stream サブシステムはそれぞれ Lua 仮想マシンを使用します。1 つの worker が 1 つまたは 2 つの仮想マシンを持つかどうかは、実際にどの Lua サブシステムを有効にしているかによって決まります。異なる worker プロセス間で Lua 仮想マシンが共有されることはありません。
require で読み込まれたモジュールは、対応する仮想マシン内にキャッシュされます。モジュールレベルの変数と、それが参照する状態は、その worker が後続のリクエストを処理するときにも保持されます。一方、別の worker には独自のモジュールインスタンスと状態があります。worker 間で共有する必要があるデータには、lua_shared_dict などの共有機構を使用してください。詳しくはOpenResty でリクエスト間のデータを共有するを参照してください。
リクエストフェーズとコルーチン
Nginx はリクエスト処理を rewrite、access、content、log などのフェーズに分けています。OpenResty は rewrite_by_lua*、access_by_lua*、content_by_lua* などのディレクティブを通じて、対応するフェーズで Lua コードを実行します。
リクエスト処理中の Lua コードは、軽量な Lua コルーチン上で実行されます。cosocket を使って Redis、MySQL、または HTTP upstream にアクセスすると、ネットワーク待ちの間は現在のコルーチンが一時停止し、worker は他のイベントを処理できます。データの準備ができるとコルーチンが再開されます。この動作は OpenResty が提供するノンブロッキング API に依存します。Lua 標準ライブラリのブロッキング呼び出しが自動的にノンブロッキングになるわけではありません。OpenResty を初めて使う読者は、アプリケーション全体の流れを7 ステップの実践チュートリアルで学ぶことができます。
インタープリター実行から JIT コンパイルへ
LuaJIT はまずインタープリターでコードを実行しながら、ホットパスを監視します。コンパイル条件に達すると、JIT は実行パスを記録し、trace と呼ばれるマシンコードの生成を試みます。その trace の条件を満たす後続の実行では、生成されたマシンコードを実行できます。
Trace abort には、JIT がまだサポートしていない操作(NYI)に遭遇した場合や、記録上限に達した場合など、複数の原因があります。これはある trace の記録が中止されたことを意味するだけで、関数全体が以後ずっとインタープリター実行になるという意味ではありません。後続の実行でインタープリターを継続することも、再びコンパイルを試みることもあります。そのため、abort の数、lj_BC_* フレーム、trace#N フレームは診断の手がかりに過ぎず、サンプリング結果や適切な分析ツールと合わせて解釈する必要があります。
ベンチマークでは JIT のウォームアップも考慮する必要があります。安定稼働時のホットパス性能を測定することが目的なら、測定前に対象コードを想定した状態まで実行してください。測定方法についてはOpenResty における Lua コード実行時間の正しい測定方法を参照してください。サンプリングフレームグラフにおける trace とインタープリター実行の現れ方については、Lua CPU フレームグラフの実践を参照してください。開発中に JIT ログを確認する方法については、resty コマンドラインツールのデモを参照してください。
OpenResty の luajit2 ブランチと上流 LuaJIT の違い
ブランチの変更内容はバージョンによって変わります。以下では、luajit2 README に記載されている代表的な変更を紹介します。具体的な機能やパラメーターについては、使用中の LuaJIT のバージョンとビルドオプションを確認してください。
Lua テーブル操作 API
luajit2 には、table.isempty、table.isarray、table.nkeys、table.clone などのテーブル操作関数が用意されています。テーブルが空か、配列形式かを判定し、キーの数を数え、浅いコピーを作成するための API です。ブランチのドキュメントでは、これらの API は JIT コンパイル可能と説明されています。特に空のテーブルや疎なテーブルに対する table.isarray の定義など、使用するバージョンのドキュメントを確認してください。
大規模アプリケーション向けの JIT デフォルト値
luajit2 は、大規模な OpenResty アプリケーションに適した一部の JIT パラメーターのデフォルト値を採用しています。ブランチの README には、次の設定が記載されています。
maxtrace=8000
maxrecord=16000
minstitch=3
maxmcode=40960 -- KB
maxtrace は trace キャッシュ内の trace 数の上限、maxrecord は 1 つの trace が記録できる IR 命令数の上限、minstitch は trace の連結に必要な IR 命令数の最小値、maxmcode はマシンコード用メモリの上限を制御します。比較として、上流 LuaJIT のドキュメントに記載されたデフォルト値は、maxtrace が 1000、maxrecord が 4000、maxmcode が 2048 KB です。パラメーターはブランチのバージョンや実行時設定によって異なる場合があるため、jit.opt または対応する診断ツールでプロセス内の実際の値を確認してください。
OpenResty コアが使用する C API
このブランチには、OpenResty コアが使用する C API も追加されています。例えば、lua_resetthread は Lua スレッドをリセットして再利用できるようにします。また、lua-nginx-module の lua_thread_cache_max_entries はこの機能を利用して軽量スレッドをキャッシュします。さらに、ブランチは luajit.h に OPENRESTY_LUAJIT マクロを定義しており、C モジュールがコンパイル時に OpenResty 固有のバージョンを識別できるようにしています。
これらのインターフェースは、主に OpenResty コアや C モジュールの開発者を対象としています。上流 LuaJIT を使用する場合や依存関係を独自に組み合わせる場合は、すべてのブランチ機能が互換であると仮定せず、使用するバージョンごとにインターフェースの有無を確認してください。なお、lua-nginx-module は公式の LuaJIT 2.x リリースにも対応していますが、ドキュメントによれば性能は大幅に低下します。また、OpenResty 版ではない LuaJIT を検出すると、Nginx はアラートをログに記録します。
バイトコードツールとその他の変更
luajit2 のバイトコードダンプツールには、ソースコードの行番号を表示する -bL オプションが追加され、-bl の出力も更新されています。また、許可される upvalue の最大数が引き上げられ、上流にはまだ取り込まれていない JIT・仮想マシンの修正とデバッグ支援機能も含まれています。完全な一覧と対象バージョンについては、luajit2 READMEを参照してください。
GC64 は、ブランチの変更と混同されやすいもう 1 つの機能です。GC64 は LuaJIT のビルドモードにあたります。OpenResty では 1.15.8.1 以降、x86_64 上でデフォルトで GC64 モードを有効にしてビルドしています。このモードによって、LuaJIT の GC が管理できるアドレス空間の上限が引き上げられます。背景と制限については、LuaJIT GC64 モードを参照してください。
LuaJIT-plus:本番メモリ管理のためのランタイム拡張
ここまで説明してきたのは、オープンソース版 OpenResty のリリースにデフォルトで同梱される luajit2 です。長時間稼働時のメモリ使用量をより厳密に制御する必要がある本番サービス向けに、OpenResty Inc. は LuaJIT-plus も提供しています。これは同じ LuaJIT ランタイム層に位置し、OpenResty のデプロイ環境における標準 LuaJIT を直接置き換えることができ、OpenResty プライベートライブラリの一部です。luajit2 における別の Lua 構文モードでも、GC64 の別名でもありません。本番環境向けの拡張ランタイムです。正確なバージョン、配布方法、互換性については、プライベートライブラリのドキュメントを確認してください。
LuaJIT の GC は Lua オブジェクトを回収しますが、アロケーターが実際の物理ページを直ちに OS に返すことまで保証するものではありません。そのため、寿命の短いオブジェクトを大量に作成・解放する長時間稼働サービスでは、Lua GC の指標が正常に見えるにもかかわらず RSS が増え続けることがあります。LuaJIT-plus はこのアロケーター層の問題を対象とし、アイドル状態で断片化したメモリを能動的に管理することで、RSS を実際のワーキングセットに近づけ、RSS の過大な増加による OOM Kill のリスクを抑えます。診断方法、アロケーターの動作、実際の本番データについては、OpenResty における LuaJIT RSS メモリリークの修正を参照してください。
両者の使い分けは、次のように考えることができます。通常の OpenResty アプリケーションでは、リリースに同梱された luajit2 を使用すれば十分です。一方、RSS が増え続ける、メモリ制限を設定しにくい、または長時間稼働時により予測可能なメモリモデルが必要なサービスでは、LuaJIT-plus の導入を検討してください。LuaJIT-plus はランタイムを置き換えるため、対応するビルドまたはデプロイの手順で導入する必要があります。ただし、能動的なメモリ返却機構はサービスの稼働中に動作し、定期的な再起動によってメモリを解放する方法とは異なります。LuaJIT-plus には、付随機能として高性能 JSON ライブラリ jit.cjson も統合されています。
FFI と OpenResty API
LuaJIT には FFI が組み込まれており、Lua コードから C 関数を呼び出したり、C のデータ構造を操作したりできます。OpenResty のリリースには lua-resty-core が含まれており、ngx.* と ndk.* API の一部を FFI を使って再実装しています。これらの FFI 実装は LuaJIT の JIT コンパイラーと連携して動作できます。lua-resty-core はデフォルトで OpenResty に付属しており、互換性の観点から対応するリリースに同梱されたバージョンの使用を推奨します。
LuaJIT と luajit2 の違い:よくある質問
LuaJIT と luajit2 の違いは何ですか?
luajit2 は OpenResty が保守する LuaJIT 2.1 のブランチであり、別の言語ではありません。同じ Lua 5.1 のコードを実行でき、上流の変更も定期的に同期しています。そのうえで、table.isempty や table.nkeys などのテーブル操作 API、大規模アプリケーション向けの JIT デフォルト値、OpenResty コアが使用する C API を追加しています。具体的な違いは、各リリースに同梱されるブランチのリビジョンによって異なります。
OpenResty には上流の LuaJIT と luajit2 のどちらをインストールすべきですか?
標準的な OpenResty のデプロイでは、その OpenResty リリースに同梱された LuaJIT を使用してください。同梱版の LuaJIT、lua-nginx-module、関連コンポーネントはまとめてパッケージングされています。lua-nginx-module は公式の LuaJIT 2.x リリースにも対応していますが、ドキュメントによれば性能は大幅に低下します。また、OpenResty 版ではない LuaJIT を検出すると、Nginx はアラートをログに記録します。
luajit2 と LuaJIT-plus の違いは何ですか?
luajit2 は、オープンソース版 OpenResty のリリースに同梱される LuaJIT のブランチです。LuaJIT-plus は、本番環境のメモリ管理のために別途提供される拡張ランタイムで、Lua オブジェクトが回収された後も RSS が高止まりする原因となるアロケーターの挙動に対処します。luajit2 や GC64 の別名ではありません。
問題別の関連記事
- 起動時の Lua モジュール読み込みが遅い場合は、Lua モジュールを LuaJIT バイトコードに事前コンパイルするを参照してください。
- LuaJIT の GC が管理するメモリ上限に問題がある場合は、LuaJIT GC64 モードを参照してください。
- Lua GC の指標が正常に見えるのに RSS が増え続ける場合は、LuaJIT RSS メモリ問題と LuaJIT-plusを参照してください。
- 本番環境の Lua CPU ホットスポットを特定したい場合は、Lua CPU フレームグラフの実践を参照してください。
- JIT コンパイルログを確認したりコード実行時間を測定したりしたい場合は、
restyコマンドラインツールのデモとLua コード実行時間の測定を参照してください。
著者について
章亦春は、オープンソースの OpenResty プロジェクトの創設者であり、OpenResty Inc. の創設者兼 CEO です。













