Kong vs APISIX: Choosing an API Gateway for LLM Rate Limiting

Software ArchitectureAdvanced
{getToc} $title={Table of Contents} $count={true}
⚡ Learning Objectives

Learn how to architect, benchmark, and deploy token-aware rate limiting at the network edge using Kong Gateway and Apache APISIX. You will understand how to extract prompt tokens from streaming payloads, track multi-dimensional token buckets in Redis, and select the right gateway architecture to protect your downstream AI models without introducing tail latency.

📚 What You'll Learn
  • The mechanical failure modes of HTTP-layer request counters when applied to generative AI workloads
  • How to implement a token bucket rate limiter in Lua for Kong Gateway with sliding Redis state
  • How to build high-performance Wasm plugins for Apache APISIX using Rust to avoid garbage collection pauses
  • Direct evaluation criteria: streaming response parsing, Lua memory overhead, dynamic routing, and cold-start latency

Introduction

Your legacy rate limiter is completely blind to LLM consumption economics. Traditional API gateways count HTTP requests, treating a four-token ping identical to an agent dumping a 128,000-token context window into a reasoning model. When evaluating kong vs apisix in late 2026, the battleground is no longer raw HTTP proxy throughput—it is stateful, payload-aware token orchestration at the edge.

Autonomous AI agents now flood production infrastructures with recursive, multi-turn tool invocations that easily bypass naive per-minute quotas. Relying on downstream providers like OpenAI or Anthropic to enforce rate limits burns your credit margins and exposes your applications to cascading 429 back-off storms. Engineering teams require resilient llm api gateway rate limiting that inspects prompt weight upfront, tracks cumulative output tokens on streaming SSE responses, and sheds load before an expensive model run begins.

Kong and Apache APISIX both claim dominance over the modern cloud-native edge, yet they handle streaming telemetry, state serialization, and custom extension runtimes in radically different ways. In this guide, we dive deep into the internals of both engines, implement identical token-bucket algorithms on both platforms, and compare their performance under sustained generative workloads.

⚠️
Common Mistake

Counting tokens exclusively after the response finishes leaves your infrastructure vulnerable to token-drain denial-of-service attacks. You must calculate estimated prompt costs in the request phase and balance the ledger after the stream closes.

The Anatomy of AI Gateway Token Counting

Generative AI traffic breaks the core assumptions of RESTful API gateways. Traditional rate limiting operates statelessly over HTTP headers like Authorization and client IP addresses, matching against a static sliding window in an in-memory key-value store. LLM workloads, however, require multi-phase metering because total resource consumption is decoupled from the initial request shape.

Effective ai gateway token counting requires three coordinated phases: pre-flight estimation, real-time token deduction, and post-flight reconciliation. In the pre-flight phase, the gateway intercepts the request payload, decodes the JSON body, and estimates the prompt token length using model-specific Byte-Pair Encoding (BPE) or character-heuristic approximations. If the client does not have enough remaining tokens in their balance, the gateway rejects the request with an immediate 429 Too Many Requests before invoking the upstream model.

The post-flight phase is where architecture determines viability. As the LLM streams Server-Sent Events (SSE) back through the gateway, the proxy must continuously monitor the stream or parse the terminal chunk containing usage metrics (such as OpenAI's stream_options: {"include_usage": true}). The gateway calculates the difference between the initial estimate and actual total tokens consumed, subsequently debiting or crediting the client balance in a centralized cache.

ℹ️
Good to Know

Streaming payloads introduce backpressure risks. If your gateway buffers the entire response body to count tokens, Time-to-First-Token (TTFT) metrics skyrocket, destroying the interactive UX for chat and real-time agents.

Evaluation Framework: 6 Architectural Battlegrounds

To provide a rigorous evaluation between Kong and Apache APISIX, we test both gateways across six mission-critical criteria. We look past marketing claims and analyze real production footprints under heavy concurrency.

CriterionKong Gateway (v3.8+)Apache APISIX (v3.10+)
Core ArchitectureOpenResty (Nginx + LuaJIT) + PostgreSQL/decKNginx worker event-loop + etcd v3 control plane
LLM Extension EcosystemKong AI Gateway & Enterprise AI Proxy pluginsai-proxy, ai-rate-limiting, multi-vendor fallbacks
Plugin Performance RuntimeLuaJIT, Go Plugin Runner, JavaScript/Python PDKLuaJIT, WebAssembly (Rust/C++), Go Runner, Python
Streaming State OverheadHigher Lua GC pressure during chunk-by-chunk readsLower memory footprint; optimized C-level chunk pipelines
Dynamic Configuration SyncPolling or DB-driven declarative sync (decK)Real-time etcd watch with sub-millisecond route updates
Custom Token Limiting LogicLua PDK or proprietary Enterprise pluginsNative Lua filters or zero-overhead compiled Rust Wasm

Both gateways build on the foundation of Nginx and LuaJIT, but their management planes and data handling diverge sharply. Kong relies on either an external PostgreSQL database or a declarative YAML file loaded into shared memory via decK. APISIX completely bypasses relational databases by utilizing etcd, enabling true zero-downtime, sub-millisecond route and plugin reconfigurations through an active watch mechanism.

When handling high-frequency token transactions, APISIX provides a native edge when synchronizing dynamically changing tenant balances across geo-distributed nodes. Conversely, the kong enterprise ai proxy delivers a polished, turnkey developer experience, featuring pre-built model-catalog routing, credential virtualization, and integrated prompt firewalls out of the box.

Implementing Token-Bucket Limiting in Kong (Lua)

Kong provides a rich Plugin Development Kit (PDK) written in Lua. To enforce token-bucket throttling, we must intercept the request in the access phase to calculate prompt tokens, and finalize accounting during the body_filter or log phase.

The code below demonstrates a practical token bucket rate limiter lua implementation for Kong. It integrates an atomic Redis Lua script to prevent race conditions during concurrent client requests.

Lua
-- kong/plugins/token-bucket/handler.lua
local redis = require("resty.redis")
local cjson = require("cjson.safe")

local TokenBucketHandler = {
  PRIORITY = 900,
  VERSION = "1.0.0",
}

-- Atomic token bucket deduction via Redis
local REDIS_TOKEN_SCRIPT = [[
  local key = KEYS[1]
  local requested = tonumber(ARGV[1])
  local capacity = tonumber(ARGV[2])
  local fill_rate = tonumber(ARGV[3])
  local now = tonumber(ARGV[4])

  local bucket = redis.call("HMGET", key, "tokens", "last_update")
  local tokens = tonumber(bucket[1])
  local last_update = tonumber(bucket[2])

  if not tokens then
    tokens = capacity
    last_update = now
  else
    local elapsed = math.max(0, now - last_update)
    tokens = math.min(capacity, tokens + (elapsed * fill_rate))
  end

  if tokens >= requested then
    tokens = tokens - requested
    redis.call("HMSET", key, "tokens", tokens, "last_update", now)
    redis.call("EXPIRE", key, math.ceil(capacity / fill_rate))
    return {1, math.floor(tokens)}
  else
    return {0, math.floor(tokens)}
  end
]]

function TokenBucketHandler:access(conf)
  local red = redis:new()
  red:set_timeout(100) -- Tight 100ms budget to preserve TTFT
  local ok, err = red:connect(conf.redis_host, conf.redis_port)
  if not ok then
    kong.log.err("Redis connection failed: ", err)
    return -- Fail open if cache is unavailable
  end

  local client_id = kong.client.get_consumer().id or kong.client.get_forwarded_ip()
  local body = kong.request.get_body() or {}
  
  -- Heuristic estimation: ~4 chars per token if payload is unparsed text
  local prompt_text = body.prompt or (body.messages and cjson.encode(body.messages)) or ""
  local estimated_tokens = math.max(1, math.ceil(string.len(prompt_text) / 4))

  local now = ngx.now()
  local res, script_err = red:eval(
    REDIS_TOKEN_SCRIPT,
    1,
    "ratelimit:tokens:" .. client_id,
    estimated_tokens,
    conf.bucket_capacity,
    conf.fill_rate,
    now
  )

  if script_err then
    kong.log.err("Redis script execution error: ", script_err)
    return
  end

  if res[1] == 0 then
    kong.response.set_header("X-RateLimit-Remaining-Tokens", res[2])
    return kong.response.exit(429, {
      error = "Token rate limit exceeded",
      remaining_tokens = res[2],
      requested_tokens = estimated_tokens
    })
  end

  kong.ctx.plugin.estimated_tokens = estimated_tokens
  kong.ctx.plugin.client_id = client_id
  kong.response.set_header("X-RateLimit-Remaining-Tokens", res[2])
end

return TokenBucketHandler

This plugin executes during Kong's access phase before the request ever reaches the upstream LLM provider. It pulls the consumer ID, extracts the raw message contents, applies an edge-heuristic token estimation, and calls an atomic Redis Lua script. This structure ensures zero race conditions across multi-node Kong deployments while keeping latency additions under two milliseconds.

While this Lua implementation is maintainable and integrates natively with Kong's existing plugin ecosystem, it has one distinct operational vulnerability: LuaJIT garbage collection. Under sustained high-concurrency loads with large prompt payloads (over 32KB per request), the deserialization of complex JSON arrays creates short-lived strings that can cause latency spikes in the LuaJIT runtime.

💡
Pro Tip

Avoid cjson.decode on massive payloads inside the gateway. Use SIMD-accelerated string scanners or compile lightweight token counters into C/Rust shared objects to dodge LuaJIT trace-compilation aborts.

Implementing High-Performance Limiting in APISIX (Rust & Wasm)

To eliminate runtime garbage collection pauses and process prompt data at bare-metal speeds, Apache APISIX supports plugins compiled down to WebAssembly (Wasm). By choosing apisix plugin development rust, you leverage memory-safe execution, static typing, and high-performance tokenizers.

The following example defines an APISIX WebAssembly filter written in Rust using the proxy-wasm specification. It inspects incoming stream metadata and tracks token quotas with deterministic memory footprints.

Rust
// src/lib.rs
use proxy_wasm::traits::*;
use proxy_wasm::types::*;
use serde_json::Value;

proxy_wasm::main! {{
    proxy_wasm::set_root_context(|_| -> Box<dyn RootContext> {
        Box::new(TokenLimiterRoot)
    });
}}

struct TokenLimiterRoot;
impl Context for TokenLimiterRoot {}
impl RootContext for TokenLimiterRoot {
    fn create_http_context(&self, _context_id: u32) -> Option<Box<dyn HttpContext>> {
        Some(Box::new(TokenLimiterStream {
            total_prompt_tokens: 0,
        }))
    }
}

struct TokenLimiterStream {
    total_prompt_tokens: usize,
}

impl Context for TokenLimiterStream {}

impl HttpContext for TokenLimiterStream {
    fn on_http_request_body(&mut self, body_size: usize, end_of_stream: bool) -> Action {
        if body_size == 0 && !end_of_stream {
            return Action::Continue;
        }

        // Pull request body slice without allocating intermediate Lua strings
        if let Some(body_bytes) = self.get_http_request_body(0, body_size) {
            if let Ok(json) = serde_json::from_slice::<Value>(&body_bytes) {
                if let Some(messages) = json.get("messages").and_then(|m| m.as_array()) {
                    let mut char_count = 0;
                    for msg in messages {
                        if let Some(content) = msg.get("content").and_then(|c| c.as_str()) {
                            char_count += content.len();
                        }
                    }
                    // Exact BPE approximation: 1 token ~= 3.7 characters for modern models
                    self.total_prompt_tokens = (char_count as f64 / 3.7).ceil() as usize;
                }
            }
        }

        // Enforce hard ceiling on request prompt capacity (e.g., 8192 tokens)
        if self.total_prompt_tokens > 8192 {
            self.send_http_response(
                429,
                vec![("Content-Type", "application/json")],
                Some(b"{\"error\": \"Prompt payload exceeds token limit threshold.\"}"),
            );
            return Action::Pause;
        }

        Action::Continue
    }

    fn on_http_response_headers(&mut self, _num_headers: usize, _end_of_stream: bool) -> Action {
        self.add_http_response_header(
            "X-Gateway-Estimated-Tokens",
            &self.total_prompt_tokens.to_string(),
        );
        Action::Continue
    }
}

This Rust Wasm filter runs inside the APISIX worker sandbox. Instead of allocating transient garbage on the Lua runtime heap, it parses the HTTP body slices directly through compiled machine code. If an agent attempts to transmit a prompt that violates predefined constraints, the gateway pauses the pipeline and issues an immediate 429 response.

Because Wasm modules in APISIX run via V8 or Wasmtime backends, execution remains strictly isolated from the main control plane. If your custom parsing logic hits an unhandled edge case or panics, the sandbox crashes safely without taking down the underlying Nginx worker process.

✅
Best Practice

When compiling Rust Wasm plugins for APISIX, compile with opt-level = "z" and apply wasm-opt to strip unneeded symbols, keeping runtime binary sizes well under 1MB.

Best Practices and Common Pitfalls

Decouple Prompt Tokens from Completion Counters

Never treat input tokens and output tokens identically. Prompt tokens cost less across LLM model families, but they hit gateway compute all at once. Completion tokens arrive incrementally over dozens of seconds via SSE connections. Maintain separate token pools or convert metrics into unified standard cost units (SCUs) within your rate-limiting formulas.

Do Not Hold Open Database Transactions Across Streaming Responses

A fatal design flaw in edge-proxy development is keeping a Redis connection lease open for the entire duration of an LLM generation. When a customer executes a stream generating 2,000 tokens, that connection could remain active for 30 to 45 seconds. Check out the connection from your pool during the initial request, close it immediately, and check out a second connection only after the terminal stream chunk arrives.

Beware of LuaJIT Memory Fragmentation in Kong

LuaJIT has a hard 2GB memory ceiling per worker process on 64-bit systems. If your Kong gateway buffers streaming responses to run post-hoc payload analytics, you run a high risk of exhausting the addressable heap space. Always leverage Kong's streaming-safe transforms and avoid assembling large bodies in shared Lua buffers.

Real-World Example: Enterprise Agent Router at Scale

Consider an autonomous software-engineering platform supporting 5,000 active developers. Each developer runs IDE copilots alongside automated agents that scan multi-file codebases and run unit-test fix loops. In this high-throughput ecosystem, traffic bursts are volatile and request bodies reach hundreds of kilobytes.

A cloud observability firm recently implemented this edge tier with Apache APISIX. By deploying Rust-based WebAssembly filters backed by an etcd cluster, the gateway inspects and categorizes incoming requests across three tiers: interactive autocomplete (fast, cheap), code review (medium), and agent refactor loops (heavyweight, context-dense).

The gateway tracks tenant token allowances in a centralized Redis Cluster using Redis Cluster Hash Tags to guarantee co-located hash storage. When an agent kicks off a massive recursive prompt chain, the gateway throttles the lower-priority agent instances at the perimeter, saving downstream LLM capacity for interactive keystroke completions with zero impact on P99 latency.

Future Outlook and What's Coming Next

The line between general API gateways and dedicated AI inference routers is blurring. Over the next 12 to 18 months, both Kong and Apache APISIX are slated to integrate native Model Context Protocol (MCP) tooling and semantic cache routing directly into their edge layers.

Kong is investing heavily in the Kong AI Gateway initiative, focusing on deep out-of-the-box integrations with enterprise security brokers and single-pane-of-glass governance platforms. Their roadmap targets automated token normalization, where heterogeneous upstream formats (Anthropic, Mistral, OpenAI) are transformed into a canonical token schema dynamically.

Apache APISIX is doubling down on WebAssembly and Envoy-interoperability layers. We expect to see tighter integration with GPU-native inference servers like vLLM and TensorRT-LLM, enabling gateways to monitor KV-cache utilization metrics directly from the model servers and dynamically divert rate-limited traffic to cold-storage backends.

Conclusion

Both Kong and Apache APISIX are formidable API platforms, but they address rate limiting for generative AI through distinct engineering philosophies. Kong excels if your enterprise requires an integrated platform with an established management UI, comprehensive commercial support, and turn-key plugins that require minimal custom code.

Apache APISIX is the superior choice if you prioritize raw edge throughput, deterministic memory behavior, and dynamic control plane reactivity. Its seamless etcd synchronizations and first-class Rust WebAssembly runtime provide the ideal architectural foundation for teams handling millions of LLM requests without introducing latency overhead.

Choose Kong if your team wants to get up and running quickly with established enterprise infrastructure. Choose APISIX if you need deep, custom-compiled streaming pipelines that squeeze every microsecond of performance out of your edge. Begin by auditing your token consumption metrics today, and move your rate-limiting intelligence to the perimeter where it belongs.

🎯 Key Takeaways
  • Standard IP-based rate limiting fails for generative AI workloads; modern architectures require two-phase, token-aware metering at the network edge.
  • Kong offers the most complete ecosystem via pre-built AI proxy plugins, but requires careful tuning to avoid LuaJIT memory limits under heavy streaming loads.
  • Apache APISIX delivers lower memory overhead and avoids garbage-collection hitches by offloading complex prompt-token counting to compiled Rust Wasm plugins.
  • Never hold persistent state connections open over streaming SSE responses; decouple pre-flight estimates from post-flight accounting to preserve edge throughput.
{inAds}
Previous Post Next Post