Plugin performance references

Updated at:

Understanding how plugins add latency helps you choose the right plugin type and language for AI Gateway. Built-in plugins add microsecond-level overhead, while custom plugins written in WebAssembly (Wasm) incur additional memory-copy costs that vary by language.

Built-in plugin performance

Built-in plugins add microsecond-level latency per request. For example, key-auth adds approximately 20 microseconds and basic-auth adds approximately 30 microseconds.

Custom plugin performance

Custom plugins run inside a WebAssembly (Wasm) VM. Each time a plugin accesses request data — such as reading or writing request headers — the runtime copies data across the Wasm VM boundary. This memory-copy overhead is the primary source of latency for custom plugins.

The following benchmarks measure this overhead across four languages. Each plugin runs the same logic: 20 loop iterations of request header setting, retrieval, and removal.

  • C++ implementation:

    FilterHeadersStatus PluginContext::onRequestHeaders(uint32_t, bool) {
      std::string fake_header_key_prefix = "fake_key_";
      std::string fake_header_value_prefix = "fake_value_";
      // Inject 20 synthetic headers
      for (size_t i = 0; i < 20; i++) {
        addRequestHeader(fake_header_key_prefix + std::to_string(i),
                         fake_header_value_prefix + std::to_string(i));
      }
    
      // Validate header existence 20 times
      for (size_t i = 0; i < 20; i++) {
        getRequestHeader(fake_header_key_prefix + std::to_string(i));
      }
    
      // Cleanup injected headers
      for (size_t i = 0; i < 20; i++) {
        removeRequestHeader(fake_header_key_prefix + std::to_string(i));
      }
      return FilterHeadersStatus::Continue;
    }
  • Go implementation:

    func (ctx *httpContext) OnHttpRequestHeaders(numHeaders int, endOfStream bool) types.Action {
            fake_header_key_prefix := "fake_key_"
            fake_header_value_prefix := "fake_value_"
            for i := 0; i < 20; i++ {
                    proxywasm.AddHttpRequestHeader(fake_header_key_prefix+strconv.Itoa(i),
                            fake_header_value_prefix+strconv.Itoa(i))
            }
            for i := 0; i < 20; i++ {
                    proxywasm.GetHttpRequestHeader(fake_header_key_prefix + strconv.Itoa(i))
            }
            for i := 0; i < 20; i++ {
                    proxywasm.RemoveHttpRequestHeader(fake_header_key_prefix + strconv.Itoa(i))
            }
            return types.ActionContinue
    }
  • Rust implementation:

    fn on_http_request_headers(&mut self, _: usize, _: bool) -> Action {
        for i in 0..20 {
            let key = "fake_key_".to_string() + &i.to_string();
            let value = "fake_value_".to_string() + &i.to_string();
            self.set_http_request_header(&key, Some(&value));
        }
        for i in 0..20 {
            let key = "fake_key_".to_string() + &i.to_string();
            self.get_http_request_header(&key);
        }
        for i in 0..20 {
            let key = "fake_key_".to_string() + &i.to_string();
            self.set_http_request_header(&key, None);
        }
        Action::Continue
    }
  • AssemblyScript implementation:

    function onRequestHeaders(a: u32, end_of_stream: bool): FilterHeadersStatusValues {
      let fake_header_key_prefix: string = "fake_key_";
      let fake_header_value_prefix: string = "fake_value_";
      for (let i = 0; i < 20; i++) {
         stream_context.headers.request.add(fake_header_key_prefix + i.toString(), fake_header_value_prefix + i.toString())
      }
      for (let i = 0; i < 20; i++) {
         stream_context.headers.request.get(fake_header_key_prefix + i.toString())
      }
      for (let i = 0; i < 20; i++) {
         stream_context.headers.request.remove(fake_header_key_prefix + i.toString())
      }
      return FilterHeadersStatusValues.Continue;
    }

Language performance comparison

Language

Additional latency (ms)

C++

0.19

Go

0.20

Rust

0.21

AssemblyScript

0.21

The latency differences across languages are negligible. Because the Wasm VM boundary overhead dominates, the compiled language's runtime characteristics have minimal impact on the result.

Choose a language based on your team's existing expertise, the maturity of its Wasm toolchain, and the availability of debugging and observability tools.