@@ -24,6 +24,7 @@ import (
2424 "fmt"
2525 "io"
2626 "log/slog"
27+ "sync"
2728 "time"
2829
2930 core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
@@ -55,6 +56,11 @@ type ExternalProcessorServer struct {
5556 kernel * Kernel
5657 executor * executor.ChainExecutor
5758 tracer trace.Tracer
59+
60+ // routeMetadataCache caches parsed route metadata by raw metadata string.
61+ // This avoids expensive prototext.Unmarshal on every request (~11% CPU savings).
62+ // Key: raw metadata string from Envoy, Value: parsed RouteMetadata
63+ routeMetadataCache sync.Map
5864}
5965
6066// NewExternalProcessorServer creates a new ExternalProcessorServer
@@ -398,7 +404,9 @@ type RouteMetadata struct {
398404 ProjectID string
399405}
400406
401- // extractRouteMetadata extracts the route metadata from Envoy metadata
407+ // extractRouteMetadata extracts the route metadata from Envoy metadata.
408+ // Uses a cache keyed by the raw metadata string to avoid repeated prototext.Unmarshal
409+ // calls which account for ~11% CPU usage according to profiling.
402410func (s * ExternalProcessorServer ) extractRouteMetadata (req * extprocv3.ProcessingRequest ) RouteMetadata {
403411 metadata := RouteMetadata {}
404412
@@ -421,43 +429,61 @@ func (s *ExternalProcessorServer) extractRouteMetadata(req *extprocv3.Processing
421429 // Extract API metadata from xds.route_metadata
422430 if routeMetadataValue , ok := extProcAttrs .Fields ["xds.route_metadata" ]; ok {
423431 if metadataStr := routeMetadataValue .GetStringValue (); metadataStr != "" {
424- // Parse the protobuf text format string using prototext
425- var envoyMetadata core.Metadata
426- if err := prototext .Unmarshal ([]byte (metadataStr ), & envoyMetadata ); err != nil {
427- slog .Warn ("Failed to unmarshal route metadata" , "error" , err )
432+ // Check cache first - routes with the same metadata string get cache hits
433+ if cached , ok := s .routeMetadataCache .Load (metadataStr ); ok {
434+ cachedMeta := cached .(RouteMetadata )
435+ // Copy API fields from cache, keep the route name from this request
436+ metadata .APIId = cachedMeta .APIId
437+ metadata .APIName = cachedMeta .APIName
438+ metadata .APIVersion = cachedMeta .APIVersion
439+ metadata .Context = cachedMeta .Context
440+ metadata .OperationPath = cachedMeta .OperationPath
441+ metadata .Vhost = cachedMeta .Vhost
442+ metadata .APIKind = cachedMeta .APIKind
443+ metadata .TemplateHandle = cachedMeta .TemplateHandle
444+ metadata .ProviderName = cachedMeta .ProviderName
445+ metadata .ProjectID = cachedMeta .ProjectID
428446 } else {
429- // Extract fields from "wso2.route" filter metadata
430- if routeStruct , ok := envoyMetadata .FilterMetadata ["wso2.route" ]; ok && routeStruct .Fields != nil {
431- if apiIdValue , ok := routeStruct .Fields ["api_id" ]; ok {
432- metadata .APIId = apiIdValue .GetStringValue ()
433- }
434- if apiNameValue , ok := routeStruct .Fields ["api_name" ]; ok {
435- metadata .APIName = apiNameValue .GetStringValue ()
436- }
437- if apiVersionValue , ok := routeStruct .Fields ["api_version" ]; ok {
438- metadata .APIVersion = apiVersionValue .GetStringValue ()
439- }
440- if apiContextValue , ok := routeStruct .Fields ["api_context" ]; ok {
441- metadata .Context = apiContextValue .GetStringValue ()
442- }
443- if operationPath , ok := routeStruct .Fields ["path" ]; ok {
444- metadata .OperationPath = operationPath .GetStringValue ()
445- }
446- if vhostValue , ok := routeStruct .Fields ["vhost" ]; ok {
447- metadata .Vhost = vhostValue .GetStringValue ()
448- }
449- if originalAPIKindValue , ok := routeStruct .Fields ["api_kind" ]; ok {
450- metadata .APIKind = originalAPIKindValue .GetStringValue ()
451- }
452- if templateHandleValue , ok := routeStruct .Fields ["template_handle" ]; ok {
453- metadata .TemplateHandle = templateHandleValue .GetStringValue ()
454- }
455- if providerNameValue , ok := routeStruct .Fields ["provider_name" ]; ok {
456- metadata .ProviderName = providerNameValue .GetStringValue ()
457- }
458- if projectIDValue , ok := routeStruct .Fields ["project_id" ]; ok {
459- metadata .ProjectID = projectIDValue .GetStringValue ()
447+ // Cache miss - parse the protobuf text format string
448+ var envoyMetadata core.Metadata
449+ if err := prototext .Unmarshal ([]byte (metadataStr ), & envoyMetadata ); err != nil {
450+ slog .Warn ("Failed to unmarshal route metadata" , "error" , err )
451+ } else {
452+ // Extract fields from "wso2.route" filter metadata
453+ if routeStruct , ok := envoyMetadata .FilterMetadata ["wso2.route" ]; ok && routeStruct .Fields != nil {
454+ if apiIdValue , ok := routeStruct .Fields ["api_id" ]; ok {
455+ metadata .APIId = apiIdValue .GetStringValue ()
456+ }
457+ if apiNameValue , ok := routeStruct .Fields ["api_name" ]; ok {
458+ metadata .APIName = apiNameValue .GetStringValue ()
459+ }
460+ if apiVersionValue , ok := routeStruct .Fields ["api_version" ]; ok {
461+ metadata .APIVersion = apiVersionValue .GetStringValue ()
462+ }
463+ if apiContextValue , ok := routeStruct .Fields ["api_context" ]; ok {
464+ metadata .Context = apiContextValue .GetStringValue ()
465+ }
466+ if operationPath , ok := routeStruct .Fields ["path" ]; ok {
467+ metadata .OperationPath = operationPath .GetStringValue ()
468+ }
469+ if vhostValue , ok := routeStruct .Fields ["vhost" ]; ok {
470+ metadata .Vhost = vhostValue .GetStringValue ()
471+ }
472+ if originalAPIKindValue , ok := routeStruct .Fields ["api_kind" ]; ok {
473+ metadata .APIKind = originalAPIKindValue .GetStringValue ()
474+ }
475+ if templateHandleValue , ok := routeStruct .Fields ["template_handle" ]; ok {
476+ metadata .TemplateHandle = templateHandleValue .GetStringValue ()
477+ }
478+ if providerNameValue , ok := routeStruct .Fields ["provider_name" ]; ok {
479+ metadata .ProviderName = providerNameValue .GetStringValue ()
480+ }
481+ if projectIDValue , ok := routeStruct .Fields ["project_id" ]; ok {
482+ metadata .ProjectID = projectIDValue .GetStringValue ()
483+ }
460484 }
485+ // Cache the parsed metadata for future requests
486+ s .routeMetadataCache .Store (metadataStr , metadata )
461487 }
462488 }
463489 }
@@ -471,6 +497,16 @@ func (s *ExternalProcessorServer) extractRouteMetadata(req *extprocv3.Processing
471497 return metadata
472498}
473499
500+ // ClearRouteMetadataCache clears the route metadata cache.
501+ // Call this when routes are updated via xDS to ensure stale metadata is not used.
502+ func (s * ExternalProcessorServer ) ClearRouteMetadataCache () {
503+ s .routeMetadataCache .Range (func (key , value interface {}) bool {
504+ s .routeMetadataCache .Delete (key )
505+ return true
506+ })
507+ slog .Info ("Route metadata cache cleared" )
508+ }
509+
474510// generateRequestID generates a unique request identifier
475511func (s * ExternalProcessorServer ) generateRequestID () string {
476512 return uuid .New ().String ()
0 commit comments