ngx_lua徹底解説:NginxでLuaを使いこなす実践ガイド

はじめに

ngx_lua(正式には lua-nginx-module)は、Nginx の内部に Lua インタプリタを埋め込み、リクエスト処理の各フェーズで Lua を実行できるようにする拡張モジュールです。OpenResty プラットフォームの中核でもあり、高速な LuaJIT を利用することで、Nginx のイベント駆動モデルと組み合わせた高性能なアプリケーションロジックの実装が可能になります。本稿では基礎から実践、運用上の注意点まで詳しく解説します。

ngx_lua の基本概念とアーキテクチャ

ngx_lua は Nginx の各フェーズ(init、init_worker、rewrite、access、content、header_filter、body_filter、log など)に Lua スクリプトを埋め込めます。代表的な埋め込みディレクティブとして、"init_by_lua_block"、"init_worker_by_lua_block"、"rewrite_by_lua_block"、"access_by_lua_block"、"content_by_lua_block"、"header_filter_by_lua_block"、"body_filter_by_lua_block"、"log_by_lua_block" などがあります。これにより、認証、ルーティング、キャッシュ制御、プロキシ制御、レスポンス変換、ロギングなどを Nginx レイヤーで柔軟に実装できます。

主要な API と機能

ngx_lua が提供する主要 API とよく使われる機能を紹介します。

  • ngx.var — Nginx の変数($uri や $host など)を読み書きできます。ただし一部の変数は書き込み不可や制約があります。
  • ngx.req / ngx.resp — リクエスト/レスポンスヘッダやボディの読み書き。例: ngx.req.get_uri_args(), ngx.req.read_body(), ngx.req.get_post_args()。
  • ngx.shared.DICT — lua_shared_dict によるワーカ間共有メモリ。キャッシュやレート制限カウンタに多用されます。操作は原子的です。
  • cosockets(ngx.socket) — 非同期ソケット API。外部の Redis、MySQL、HTTP API 等へブロッキングせずに接続できます(イベントループを利用)。
  • サブリクエスト — ngx.location.capture* 系により内部サブリクエストを実行可能。外部 HTTP を呼ばずに Nginx 内で別ロケーションを再利用できますが、コストが発生します。
  • タイマ(ngx.timer.at) — バックグラウンド処理をワーカコンテキストで非同期に実行します。定期処理や遅延処理に使います。
  • LuaJIT と FFI — パフォーマンス向上や外部 C ライブラリ利用のために FFI を使うことが可能です(ただし安全性と互換性に注意)。

コルーチンとコスケット(非同期処理)の理解

ngx_lua の非同期処理は Lua のコルーチン(yield/resume)を利用して実装されています。cosocket や一部の API はコルーチンを yield してイベントが来るまで待つことで、従来のスレッドブロッキングを回避します。ただし「どのフェーズで yield(コルーチンの中断)が許可されるか」は重要な制約です。例えば、init_by_lua ブロックはマスタープロセスで実行され、イベントループが無いため cosocket を使えません。一方で content_by_lua や access_by_lua、init_worker_by_lua、タイマコールバックなどはコルーチンを使った非同期 I/O が可能です。header_filter_by_lua や body_filter_by_lua などのフィルタフェーズでは yield が禁止される場合があるため、そのフェーズで長時間の I/O を直接行うことは避けるべきです。

実用的なユースケース

  • 認証と認可 — JWT 検証、キー検証、外部認証サービス呼び出しを高速に実装。アクセスフェーズでの判定により不要な upstream 呼び出しを省けます。
  • API ゲートウェイ — レート制限、API キー管理、リクエスト変換、レスポンスキャッシュをエッジで実装。
  • 動的ルーティング/A/B テスト — リクエストごとのルーティング判断を Lua で柔軟に記述。
  • キャッシュ制御 — ngx.shared.DICT と ETag や TTL を組み合わせた軽量キャッシュ。
  • レスポンスの変換・フィルタ — HTML/JSON の動的変換やヘッダ操作(フィルタフェーズの制約に注意)。

パフォーマンスとチューニング

ngx_lua を高性能に運用するためのポイント:

  • LuaJIT を利用 — LuaJIT は JIT コンパイルで高い実行速度を実現します。OpenResty は LuaJIT を標準で組み込んでいます。
  • lua_code_cache を有効に — 本番では lua_code_cache on にしてスクリプトの再ロードを抑え、パフォーマンスを安定させます。
  • 共有メモリの活用 — lua_shared_dict は Nginx の設定で十分なサイズを確保し、頻繁なメモリ確保を避けます。
  • GC とメモリ管理 — LuaJIT の GC 調整や ngx.update_time などの利用で遅延を抑制。不要な大きなテーブルの再生成を避ける。
  • コスケットの利用 — 外部サービスはできるだけ cosocket を使った非同期クライアント(lua-resty-http、lua-resty-redis など)で接続し、ワーカをブロックしない。
  • プロファイリングとベンチマーク — wrk 等で負荷試験を行い、hot path を特定。lua-profiler 等も検討する。

開発・運用上のベストプラクティス

  • フェーズ設計の明確化 — どの処理をどのフェーズで行うか(例: 認証は access、ヘッダ変更は header_filter 等)を明確にし、yield 可否に注意する。
  • ブロッキング操作の排除 — ファイル I/O や同期的な DNS 参照等は避け、非同期または別プロセスで処理する。
  • 疎結合のコード設計 — require でのモジュール設計、lua_package_path を設定してライブラリ管理。グローバル変数への依存を避ける。
  • 例外とログの扱い — pcall や xpcall でエラーを捕捉し、必要に応じて ngx.log で詳細を残す。過剰なログはパフォーマンスに影響するため注意。
  • セキュリティ — 外部入力の検証、SQL/コマンドインジェクション対策、LuaJIT FFI による危険なネイティブ呼び出しの管理。

デプロイとトラブルシューティング

デプロイ時は次を確認してください。

  • nginx.conf に必要な lua_shared_dict を定義しているか。
  • lua_code_cache の設定(開発では off にしてホットリロード、運用では on)。
  • init_by_lua と init_worker_by_lua の挙動差と依存関係。
  • ワーカの数と LuaJIT のメモリ使用。ワーカあたりのスタック/メモリ制限を考慮する。

トラブルシューティングのポイント:

  • ログ(error_log)のレベルを上げてエラー発生箇所を特定する。
  • long-running なコールやタイムアウトの調査。cosocket のタイムアウト設定を適切にする。
  • サブリクエストの多用によるオーバーヘッドをプロファイリングで確認する。

OpenResty と ngx_lua の関係

OpenResty は ngx_lua を中心に多数の Lua ライブラリ(lua-resty-core、lua-resty-lrucache、lua-resty-http など)をバンドルしたディストリビューションです。単体の lua-nginx-module を Nginx に組み込むことも可能ですが、OpenResty は依存関係の管理やビルドの手間を省き、即戦力として利用できるため多くの現場で採用されています。

まとめ

ngx_lua は Nginx の高速性と Lua の柔軟性を組み合わせ、API ゲートウェイやエッジ処理、認証、キャッシュ、レスポンス変換など数多くのユースケースで有効です。一方で、フェーズごとの制約や非同期処理の扱い、メモリや GC のチューニングなど注意点も多く、設計段階からそれらを考慮することが重要です。OpenResty を使うことでエコシステムの恩恵を受けられるため、プロジェクト要件に応じて適切に選択してください。

参考文献

lua-nginx-module (GitHub)
OpenResty 公式サイト
lua-nginx-module ドキュメント(OpenResty)
lua-resty-core (GitHub)
lua-resty-http (GitHub)
LuaJIT 公式サイト