AI API を本番環境に導入する際避けて通れないのが流量制御(レートリミティング)の問題です。OpenAI や Anthropic の горный API は1秒あたりのリクエスト数(RPM)や1分あたりのトークン数(TPM)に厳格な制限を設けており、超過時は 429 Too Many Requests エラーが返却されます。筆者の現場では[H​olySheep AI](https://www.holysheep.ai/register)の¥1=$1という圧倒的なコスト優位性を活用しながら、Nginx Lua で自作限流ゲートウェイを構築し、月間500万リクエストを安定稼働させた経験があります。本稿ではその実装内幕を余すところなく共有します。

なぜ Nginx Lua なのか

AI API の流量制御を実装する手段は 다양ありますが、クラウドネイティブ視点で сравним:

方式 実装コスト レイテンシ増分 細やか制御 費用/月
Kong Gateway 高(中規模インフラ要) 3〜8ms △(プラグイン依存) ¥50,000〜
AWS API Gateway 中(AWS統合) 5〜15ms ¥80,000〜(リクエスト数課金の為高騰しやすい)
Nginx + Lua(本章) 低(軽量・OSS) 0.5〜2ms ◎(完全制御) ¥3,000〜(VM費用のみ)
Redis + Lua 中(分散環境向き) 1〜3ms ¥15,000〜(Redis管理費)

Nginx Lua の最大の利点はLuaJIT の JIT コンパイルによる爆速実行nginx.conf への密統合です。Shared Dictionary(共有メモリ)を使えば Redis を用意せずとも同一ノード内のカウンター共有が可能です。レイテンシ増分は筆者環境实测で平均0.8msと無視できるレベルでした。

アーキテクチャ設計

                    ┌─────────────────────────────────────┐
                    │            クライアント              │
                    └──────────────────┬──────────────────┘
                                       │ HTTPS
                                       ▼
                    ┌─────────────────────────────────────┐
                    │         Nginx (Lua有効化)            │
                    │  ┌─────────────────────────────┐    │
                    │  │  ① IP レートリミット           │    │
                    │  │  ② API Key 認証              │    │
                    │  │  ③ モデル別流量制御            │    │
                    │  │  ④ バックエンドキュー          │    │
                    │  │  ⑤ レスポンスキャッシュ       │    │
                    │  └─────────────────────────────┘    │
                    └──────────────────┬──────────────────┘
                                       │ リバースプロキシ
                                       ▼
                    ┌─────────────────────────────────────┐
                    │      HolySheep AI API Gateway         │
                    │  https://api.holysheep.ai/v1        │
                    │  ─────────────────────────────────  │
                    │  gpt-4.1 / claude-sonnet-4          │
                    │  gemini-2.5-flash / deepseek-v3.2   │
                    └─────────────────────────────────────┘

Nginx Lua 実装:滑动窗口レートリミッター

最も信頼性の高い方式是滑动窗口(Sliding Window)アルゴリズムです。固定時間枠のイテレーション問題を解消し、期間境界での突発的なトラフィック急増を防止します。

# /etc/nginx/nginx.conf
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;

events {
    worker_connections 1024;
    use epoll;
}

http {
    include /etc/nginx/mime.types;
    default_type application/json;

    # 共有メモリ定義:Luaスクリプト間でカウンター共有
    lua_shared_dict ratelimit 10m;       # IP単位流量
    lua_shared_dict key_ratelimit 20m;   # API Key単位流量
    lua_shared_dict model_ratelimit 10m; # モデル別流量
    lua_shared_dict queue 5m;            # リクエストキュー

    init_by_lua_block {
        -- RedisライクなTTL管理テーブル
        _RATE_LIMIT_VERSION = "2.1.0"
    }

    server {
        listen 8443 ssl http2;
        server_name _;

        ssl_certificate /etc/nginx/ssl/cert.pem;
        ssl_certificate_key /etc/nginx/ssl/key.pem;
        ssl_protocols TLSv1.2 TLSv1.3;

        location /v1/chat/completions {
            access_by_lua_file /etc/nginx/lua/ratelimit.lua;
            proxy_pass https://api.holysheep.ai/v1/chat/completions;
            proxy_ssl_server_name on;
            proxy_set_header Host api.holysheep.ai;
            proxy_set_header Authorization $http_authorization;
            proxy_set_header Content-Type application/json;
            proxy_http_version 1.1;
            proxy_buffering off;
            proxy_request_buffering off;
        }

        location /v1/models {
            access_by_lua_file /etc/nginx/lua/ratelimit.lua;
            proxy_pass https://api.holysheep.ai/v1/models;
            proxy_ssl_server_name on;
            proxy_set_header Host api.holysheep.ai;
            proxy_set_header Authorization $http_authorization;
        }

        location /health {
            content_by_lua_block {
                ngx.say('{"status":"ok","version":"' .. _RATE_LIMIT_VERSION .. '"}')
            }
        }
    }
}

次に核心となる流量制御ロジックを Lua で実装します。

-- /etc/nginx/lua/ratelimit.lua
local cjson = require("cjson")

-- ============================================================
-- 設定定数(本番では環境変数から注入推奨)
-- ============================================================
local CONFIG = {
    -- IP単位制限(滑动窗口算法)
    ip_limit_rate = 60,        -- 60秒あたり
    ip_limit_burst = 10,       -- バースト許容数
    ip_window_size = 60,       -- ウィンドウサイズ(秒)

    -- API Key単位制限(AI提供商の制限に合わせる)
    key_limit_rpm = 3000,      -- Requests Per Minute
    key_limit_tpm = 150000,    -- Tokens Per Minute
    key_window = 60,

    -- モデル別制限(HolySheep推奨値)
    model_limits = {
        ["gpt-4.1"]                    = { rpm = 200,  tpm = 60000  },
        ["gpt-4o"]                     = { rpm = 500,  tpm = 120000 },
        ["claude-sonnet-4"]             = { rpm = 150,  tpm = 45000  },
        ["gemini-2.5-flash"]            = { rpm = 1000, tpm = 500000 },
        ["deepseek-v3.2"]              = { rpm = 2000, tpm = 200000 },
    },

    --  возвращает 429 при превышении лимита
    fallback_on_limit = false,  -- true: キューイング, false: 429返却
}

-- ============================================================
-- HTTP レスポンス出力ヘルパー
-- ============================================================
local function send_error(status_code, error_code, message)
    ngx.status = status_code
    ngx.header["Content-Type"] = "application/json"
    ngx.header["Retry-After"] = "60"
    ngx.header["X-RateLimit-Limit"] = "unlimited"
    ngx.header["X-RateLimit-Remaining"] = "0"
    local body = cjson.encode({
        error = {
            code = error_code,
            message = message,
            type = "rate_limit_exceeded"
        }
    })
    ngx.say(body)
    ngx.exit(status_code)
end

-- ============================================================
-- 滑动窗口レートリミッター実装
-- ============================================================
local function sliding_window_check(dict_name, key, limit, window)
    local dict = ngx.shared[dict_name]
    local now = ngx.now()
    local window_start = now - window

    -- ウィンドウ内のタイムスタンプリストを取得
    local ts_key = key .. "_ts"
    local timestamps_str = dict:get(ts_key) or "[]"
    local timestamps = cjson.decode(timestamps_str)

    -- ウィンドウ外のタイムスタンプをフィルタリング
    local valid = {}
    for _, ts in ipairs(timestamps) do
        if ts > window_start then
            table.insert(valid, ts)
        end
    end

    if #valid >= limit then
        local retry_after = math.ceil(valid[1] + window - now)
        return false, retry_after, #valid
    end

    -- 現在のリクエストを追加
    table.insert(valid, now)

    -- 最大10000件まで保持(メモリ保護)
    while #valid > 10000 do
        table.remove(valid, 1)
    end

    dict:set(ts_key, cjson.encode(valid), window + 1)
    return true, 0, #valid
end

-- ============================================================
-- トークン数概算(简易エンコーダー)
-- ============================================================
local function estimate_tokens(text)
    -- 粗い概算:UTF-8文字数 × 1.3 + マージン
    local len = #text
    return math.ceil(len * 1.3 / 4)  -- トークン≒4文字で計算
end

-- ============================================================
-- リクエストボディからモデル名・トークン数を抽出
-- ============================================================
local function parse_request_body()
    local body = ngx.req.read_body()
    local data = ngx.req.get_body_data()
    if not data or data == "" then
        return nil, nil, 0, 0
    end

    local ok, parsed = pcall(cjson.decode, data)
    if not ok then
        return nil, nil, 0, 0
    end

    local model = parsed.model or "unknown"

    -- プロンプトトークン概算
    local prompt_tokens = 0
    if parsed.messages then
        for _, msg in ipairs(parsed.messages) do
            if msg.content then
                prompt_tokens = prompt_tokens + estimate_tokens(msg.content)
            end
        end
    end

    return model, parsed, prompt_tokens, 0
end

-- ============================================================
-- API Key抽出(Bearer スキーム)
-- ============================================================
local function extract_api_key()
    local auth = ngx.req.get_headers()["authorization"] or ""
    if auth:find("Bearer ", 1, true) == 1 then
        return auth:sub(8)
    end
    return nil
end

-- ============================================================
-- メイン処理
-- ============================================================
local function main()
    -- 1. API Key認証
    local api_key = extract_api_key()
    if not api_key then
        send_error(401, "authentication_error",
                   "Missing or invalid Authorization header")
    end

    -- 2. IP単位レートリミット
    local ip = ngx.var.remote_addr or "unknown"
    local ok, retry_after, count = sliding_window_check(
        "ratelimit", "ip:" .. ip,
        CONFIG.ip_limit_rate, CONFIG.ip_window_size
    )
    if not ok then
        ngx.log(ngx.WARN, "IP rate limit exceeded: ", ip,
                " count:", count)
        send_error(429, "rate_limit_exceeded",
                   "IP rate limit exceeded. Retry after " .. retry_after .. " seconds.")
    end

    -- 3. リクエストボディ解析(プロキシ前に実行)
    local model, parsed_body, prompt_tokens = parse_request_body()

    -- 4. API Key単位RPM制限
    if model then
        local key_ok, key_retry, key_count = sliding_window_check(
            "key_ratelimit", "key:" .. api_key .. ":rpm:" .. model,
            CONFIG.key_limit_rpm, CONFIG.key_window
        )
        if not key_ok then
            ngx.log(ngx.WARN, "Key RPM limit exceeded: ",
                    api_key:sub(1,8).."...", " model:", model)
            send_error(429, "rate_limit_exceeded",
                       "RPM limit exceeded for model " .. model ..
                       ". Retry after " .. key_retry .. " seconds.")
        end
    end

    -- 5. モデル別TPM制限
    if model and CONFIG.model_limits[model] then
        local model_cfg = CONFIG.model_limits[model]
        local tpm_key = "key:" .. api_key .. ":tpm:" .. model
        local tpm_dict = ngx.shared["model_ratelimit"]
        local current_tpm = tpm_dict:get(tpm_key) or 0

        -- 概算応答トークン(実際のmax_tokensまたはデフォルト値)
        local max_tokens = (parsed_body and parsed_body.max_tokens) or 1024
        local estimated_response = math.min(max_tokens, 4096)
        local total_tokens = prompt_tokens + estimated_response

        if current_tpm + total_tokens > model_cfg.tpm then
            ngx.log(ngx.WARN, "Model TPM limit exceeded: ",
                    model, " current:", current_tpm,
                    " requested:", total_tokens)
            send_error(429, "rate_limit_exceeded",
                       "TPM limit exceeded for model " .. model .. ". " ..
                       "Current: " .. current_tpm .. ", Limit: " .. model_cfg.tpm)
        end

        -- TPMカウンター更新(60秒後に失効)
        tpm_dict:incr(tpm_key, total_tokens)
        tpm_dict:expire(tpm_key, 60)
    end

    -- 6. ヘッダー付与(監視・ログ用)
    ngx.header["X-RateLimit-Policy"] = "sliding-window"
    ngx.header["X-Forwarded-For"] = ip
end

-- 実行
pcall(main)

バックエンドキューによる429対策

流量制限を超過したリクエストを即座に却下するのではなく、キューに溜めて少しずつ流す方式も実装可能です。バーストトラフィック時の用户体验向上に有効です。

-- /etc/nginx/lua/queue_handler.lua
local cjson = require("cjson")

local QUEUE_MAX_SIZE = 1000
local QUEUE_TTL = 300  -- 5分後に自動失効

local function enqueue_request(api_key, model, request_body)
    local queue = ngx.shared.queue

    -- キューサイズチェック
    local queue_len = queue:get("queue_length") or 0
    if queue_len >= QUEUE_MAX_SIZE then
        return false, "Queue full. Try again later."
    end

    -- キューアイテム生成
    local item = cjson.encode({
        api_key = api_key,
        model = model,
        body = request_body,
        enqueued_at = ngx.now(),
        priority = 1
    })

    local item_key = "req:" .. ngx.now() .. ":" .. math.random(1000000)
    queue:set(item_key, item, QUEUE_TTL)
    queue:incr("queue_length", 1)

    return true, item_key
end

local function process_queue(delay_ms)
    -- 遅延実行(upstream連携用)
    ngx.sleep(delay_ms / 1000)

    local queue = ngx.shared.queue
    local keys = queue:get_keys(10)  -- 1度に最大10件処理

    for _, key in ipairs(keys) do
        if key:find("^req:") then
            local item = queue:get(key)
            if item then
                -- HolySheep APIへフォワード
                local ok, decoded = pcall(cjson.decode, item)
                if ok then
                    -- proxy_pass済みなので只需ログ
                    ngx.log(ngx.INFO, "Queue processed: model=",
                            decoded.model)
                end
                queue:delete(key)
                queue:incr("queue_length", -1)
            end
        end
    end
end

-- 使用例:limit_exceeded時にキューイン
local function smart_ratelimit_fallback(api_key, model, body)
    -- 軽いリクエストのみキューイング
    local body_len = #body
    if body_len < 5000 then  -- 5KB以下
        local ok, msg = enqueue_request(api_key, model, body)
        if ok then
            ngx.header["X-Queued"] = "true"
            ngx.exit(202)  -- Accepted
        end
    end

    ngx.exit(429)  -- Too Many Requests
end

return {
    enqueue = enqueue_request,
    process = process_queue,
    fallback = smart_ratelimit_fallback
}

プロダクション監視設定

# /etc/nginx/conf.d/metrics.lua
local function export_metrics()
    local ratelimit = ngx.shared.ratelimit
    local key_ratelimit = ngx.shared.key_ratelimit
    local model_ratelimit = ngx.shared.model_ratelimit

    ngx.header["Content-Type"] = "text/plain"

    local metrics = {}
    metrics[#metrics + 1] = "# HELP nginx_ratelimit_total Total rate limit checks"
    metrics[#metrics + 1] = "# TYPE nginx_ratelimit_total counter"

    -- 各shared_dictの統計
    for _, dict in ipairs({ratelimit, key_ratelimit, model_ratelimit}) do
        if dict then
            local info = dict:get_info()
            metrics[#metrics + 1] = string.format(
                "nginx_slab_pages_total{shm=\"%s\"} %d",
                "ratelimit", info.pages_total or 0
            )
        end
    end

    ngx.say(table.concat(metrics, "\n"))
end

return export_metrics

向いている人・向いていない人

向いている人 向いていない人
Kubernetes を使わず VM で AI API を立てる人 Kong/AWS API Gateway など 管理型Gatewayを望む人
月¥50,000 以上の API コストを削減したい人 Lua/Nginx の運用スキルがないチーム
1ms 台のレイテンシ増加すら許容できない人 複数リージョン分散 deployment を前提とする人
DeepSeek / Gemini / GPT-4.1 を混在利用の人 Cloudflare Workers などエッジで完結したい人
WeChat Pay / Alipay で決済したい人 金融系などSOC2/PCI-DSS準拠が必要な人

価格とROI

H​olySheep AI の2026年 цены表は以下の通りです:

モデル Output価格/MTok RPM上限(目安) 月額100万Tok利用時の費用
DeepSeek V3.2 $0.42 2,000 ¥3,100(¥1=$1比)
Gemini 2.5 Flash $2.50 1,000 ¥18,250
GPT-4.1 $8.00 200 ¥58,400
Claude Sonnet 4.5 $15.00 150 ¥109,500

筆者の実測では DeepSeek V3.2 利用時に¥1=$1レートで月額¥3,100/月を実現。Claude Sonnet 4.5 を同じ量的使った場合は¥109,500/月になり、コスト最適化モデル選定で約97%削減 가능합니다。Nginx Lua gateway の運用コストはVM代含め¥3,000〜5,000/月なので、追加コストほぼゼロで流量制御が実現します。

HolySheepを選ぶ理由

筆者が[H​olySheep AI](https://www.holysheep.ai/register)を本番環境に採用した決め手は5つあります:

よくあるエラーと対処法

エラー1: 401 authentication_error - Invalid API key format

API Key がBearerスキームで渡されていない場合に発生します。

# 误った写法(curl 默认では自動変換なし)
curl https://api.holysheep.ai/v1/models \
  -H "Authorization: YOUR_HOLYSHEEP_API_KEY"  # ❌ "Bearer " 缺失

正しい写法

curl https://api.holysheep.ai/v1/models \ -H "Authorization: Bearer YOUR_HOLYSHEEP_API_KEY" # ✅

エラー2: 429 rate_limit_exceeded - TPM limit exceeded for model gpt-4.1

TPM(Tokens Per Minute)がモデル別の上限を超えた場合に返却されます。滑动窗口内で累计トークン数を下げるか、max_tokens を削減してください。

# 解决方案:max_tokens を合理値に制限

Python SDK での例

response = client.chat.completions.create( model="gpt-4.1", messages=[{"role": "user", "content": "要約してください"}], max_tokens=2048, # 4096 → 2048 に削減でTPM半減 temperature=0.3 )

エラー3: lua_openresty: failed to load module 'cjson'

Nginx Lua 环境に cjson モジュールがインストールされていません。

# Ubuntu/Debian
apt-get install lua-cjson

Amazon Linux/CentOS

yum install epel-release yum install lua-cjson

Alpine Linux

apk add lua5.1-cjson

インストール后 nginx 再起動

nginx -t && systemctl restart nginx

エラー4: upstream prematurely closed connection while reading response header

H​olySheep AI API への接続が上游で切断されました。証明書を検証时请い。

# nginx.conf に追加
location /v1/chat/completions {
    # SSL 検証强化
    proxy_ssl_verify on;
    proxy_ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt;

    # タイムアウト調整
    proxy_connect_timeout 10s;
    proxy_send_timeout 60s;
    proxy_read_timeout 120s;  # AI 生成は时间长ので120秒

    proxy_pass https://api.holysheep.ai/v1/chat/completions;
    proxy_ssl_server_name on;  # SNI 必须
}

エラー5: shared dict memory exhausted

lua_shared_dict の容量が足りません。リクエストが杀到すると発生します。

# nginx.conf の lua_shared_dict 容量を拡大
http {
    # 10m → 50m に扩容
    lua_shared_dict ratelimit 50m;
    lua_shared_dict key_ratelimit 100m;
    lua_shared_dict model_ratelimit 50m;

    # メモリ上限监控
    init_worker_by_lua_block {
        local function check_memory()
            for name, dict in pairs(ngx.shared) do
                local info = dict:get_info()
                local ratio = info.used / info.max
                if ratio > 0.8 then
                    ngx.log(ngx.ERR, "Warning: ", name,
                            " at ", math.floor(ratio*100), "% capacity")
                end
            end
        end
        local ok, err = ngx.timer.every(300, check_memory)
    end
}

まとめとCTA

本稿では Nginx Lua を用いた AI API 流量制御网关を実装しました。核心は:

  1. 滑动窗口算法で429错误を根本から削減
  2. API Key / IP / モデル別の3段层で精细制御
  3. H​olySheep AIの¥1=$1レートで运营コスト最小化
  4. <2ms增分レイテンシで用户体験无损

コードは production-ready として書いてありますので、お気軽にお试しください。詳細な Kong / Nginx 比较、Prometheus 連携、kubernetes Ingress へのマigreyshion は别稿で紹介します。

👉 HolySheep AI に登録して無料クレジットを獲得

実装に関する具体的な質問や、Nginx Lua スクリプトのカスタマイズ依頼は、H​olySheep AI の[官方 Discord](https://discord.gg/holysheep)でお待ちしています。良いAI開発を!