From 7fc494f02c0f7c6b79517118b5692312c218122b Mon Sep 17 00:00:00 2001 From: spetrunin Date: Wed, 5 Nov 2025 23:45:57 +0200 Subject: [PATCH 01/11] add cache for variables normalization and remapping --- router/core/graph_server.go | 61 ++++++++++++---- router/core/operation_processor.go | 112 +++++++++++++++++++++++++++-- 2 files changed, 155 insertions(+), 18 deletions(-) diff --git a/router/core/graph_server.go b/router/core/graph_server.go index 4a16e38ad7..258696962e 100644 --- a/router/core/graph_server.go +++ b/router/core/graph_server.go @@ -506,18 +506,21 @@ func (s *graphServer) setupEngineStatistics(baseAttributes []attribute.KeyValue) } type graphMux struct { - mux *chi.Mux - planCache *ristretto.Cache[uint64, *planWithMetaData] - persistedOperationCache *ristretto.Cache[uint64, NormalizationCacheEntry] - normalizationCache *ristretto.Cache[uint64, NormalizationCacheEntry] - complexityCalculationCache *ristretto.Cache[uint64, ComplexityCacheEntry] - validationCache *ristretto.Cache[uint64, bool] - operationHashCache *ristretto.Cache[uint64, string] - accessLogsFileLogger *logging.BufferedLogger - metricStore rmetric.Store - prometheusCacheMetrics *rmetric.CacheMetrics - otelCacheMetrics *rmetric.CacheMetrics - streamMetricStore rmetric.StreamMetricStore + mux *chi.Mux + planCache *ristretto.Cache[uint64, *planWithMetaData] + persistedOperationCache *ristretto.Cache[uint64, NormalizationCacheEntry] + normalizationCache *ristretto.Cache[uint64, NormalizationCacheEntry] + complexityCalculationCache *ristretto.Cache[uint64, ComplexityCacheEntry] + variablesNormalizationCache *ristretto.Cache[uint64, VariablesNormalizationCacheEntry] + remapVariablesCache *ristretto.Cache[uint64, RemapVariablesCacheEntry] + + validationCache *ristretto.Cache[uint64, bool] + operationHashCache *ristretto.Cache[uint64, string] + accessLogsFileLogger *logging.BufferedLogger + metricStore rmetric.Store + prometheusCacheMetrics *rmetric.CacheMetrics + otelCacheMetrics *rmetric.CacheMetrics + streamMetricStore rmetric.StreamMetricStore } // buildOperationCaches creates the caches for the graph mux. @@ -572,6 +575,30 @@ func (s *graphMux) buildOperationCaches(srv *graphServer) (computeSha256 bool, e if err != nil { return computeSha256, fmt.Errorf("failed to create normalization cache: %w", err) } + + variablesNormalizationCacheConfig := &ristretto.Config[uint64, VariablesNormalizationCacheEntry]{ + Metrics: srv.metricConfig.OpenTelemetry.GraphqlCache || srv.metricConfig.Prometheus.GraphqlCache, + MaxCost: srv.engineExecutionConfiguration.NormalizationCacheSize, + NumCounters: srv.engineExecutionConfiguration.NormalizationCacheSize * 10, + IgnoreInternalCost: true, + BufferItems: 64, + } + s.variablesNormalizationCache, err = ristretto.NewCache[uint64, VariablesNormalizationCacheEntry](variablesNormalizationCacheConfig) + if err != nil { + return computeSha256, fmt.Errorf("failed to create normalization cache: %w", err) + } + + remapVariablesCacheConfig := &ristretto.Config[uint64, RemapVariablesCacheEntry]{ + Metrics: srv.metricConfig.OpenTelemetry.GraphqlCache || srv.metricConfig.Prometheus.GraphqlCache, + MaxCost: srv.engineExecutionConfiguration.NormalizationCacheSize, + NumCounters: srv.engineExecutionConfiguration.NormalizationCacheSize * 10, + IgnoreInternalCost: true, + BufferItems: 64, + } + s.remapVariablesCache, err = ristretto.NewCache[uint64, RemapVariablesCacheEntry](remapVariablesCacheConfig) + if err != nil { + return computeSha256, fmt.Errorf("failed to create normalization cache: %w", err) + } } if srv.engineExecutionConfiguration.EnableValidationCache && srv.engineExecutionConfiguration.ValidationCacheSize > 0 { @@ -723,6 +750,14 @@ func (s *graphMux) Shutdown(ctx context.Context) error { s.normalizationCache.Close() } + if s.variablesNormalizationCache != nil { + s.variablesNormalizationCache.Close() + } + + if s.remapVariablesCache != nil { + s.remapVariablesCache.Close() + } + if s.complexityCalculationCache != nil { s.complexityCalculationCache.Close() } @@ -1226,6 +1261,8 @@ func (s *graphServer) buildGraphMux( ValidationCache: gm.validationCache, QueryDepthCache: gm.complexityCalculationCache, OperationHashCache: gm.operationHashCache, + VariablesNormalizationCache: gm.variablesNormalizationCache, + RemapVariablesCache: gm.remapVariablesCache, ParseKitPoolSize: s.engineExecutionConfiguration.ParseKitPoolSize, IntrospectionEnabled: s.Config.introspection, ParserTokenizerLimits: astparser.TokenizerLimits{ diff --git a/router/core/operation_processor.go b/router/core/operation_processor.go index eb020a785f..6981e8b53b 100644 --- a/router/core/operation_processor.go +++ b/router/core/operation_processor.go @@ -107,6 +107,8 @@ type OperationProcessorOptions struct { PersistedOpsNormalizationCache *ristretto.Cache[uint64, NormalizationCacheEntry] NormalizationCache *ristretto.Cache[uint64, NormalizationCacheEntry] QueryDepthCache *ristretto.Cache[uint64, ComplexityCacheEntry] + VariablesNormalizationCache *ristretto.Cache[uint64, VariablesNormalizationCacheEntry] + RemapVariablesCache *ristretto.Cache[uint64, RemapVariablesCacheEntry] ValidationCache *ristretto.Cache[uint64, bool] OperationHashCache *ristretto.Cache[uint64, string] ParseKitPoolSize int @@ -160,6 +162,8 @@ type OperationCache struct { persistedOperationNormalizationCache *ristretto.Cache[uint64, NormalizationCacheEntry] normalizationCache *ristretto.Cache[uint64, NormalizationCacheEntry] + variablesNormalizationCache *ristretto.Cache[uint64, VariablesNormalizationCacheEntry] + remapVariablesCache *ristretto.Cache[uint64, RemapVariablesCacheEntry] complexityCache *ristretto.Cache[uint64, ComplexityCacheEntry] validationCache *ristretto.Cache[uint64, bool] operationHashCache *ristretto.Cache[uint64, string] @@ -755,6 +759,20 @@ type NormalizationCacheEntry struct { operationDefinitionRef int } +type VariablesNormalizationCacheEntry struct { + normalizedRepresentation string + uploadsMapping []uploads.UploadPathMapping + id uint64 + variables json.RawMessage + reparse bool +} + +type RemapVariablesCacheEntry struct { + internalID uint64 + normalizedRepresentation string + remapVariables map[string]string +} + type ComplexityCacheEntry struct { Depth int TotalFields int @@ -790,7 +808,7 @@ func (o *OperationKit) normalizeNonPersistedOperation() (cached bool, err error) } } - // reset with the original variables + // set variables to the normalized variables as skip inlude variables will be removed after normalization o.parsedOperation.Request.Variables = o.kit.doc.Input.Variables // Hash the normalized operation with the static operation name & original variables to avoid different IDs for the same operation @@ -840,7 +858,33 @@ func (o *OperationKit) setAndParseOperationDoc() error { return nil } +func (o *OperationKit) normalizeVariablesCacheKey() uint64 { + _, _ = o.kit.keyGen.Write(o.kit.doc.Input.Variables) + _, _ = o.kit.keyGen.WriteString(o.parsedOperation.NormalizedRepresentation) + + sum := o.kit.keyGen.Sum64() + o.kit.keyGen.Reset() + return sum +} + func (o *OperationKit) NormalizeVariables() ([]uploads.UploadPathMapping, error) { + cacheKey := o.normalizeVariablesCacheKey() + if o.cache != nil && o.cache.variablesNormalizationCache != nil { + entry, ok := o.cache.variablesNormalizationCache.Get(cacheKey) + if ok { + o.parsedOperation.NormalizedRepresentation = entry.normalizedRepresentation + o.parsedOperation.ID = entry.id + o.parsedOperation.Request.Variables = entry.variables + + if entry.reparse { + if err := o.setAndParseOperationDoc(); err != nil { + return nil, err + } + } + return entry.uploadsMapping, nil + } + } + variablesBefore := make([]byte, len(o.kit.doc.Input.Variables)) copy(variablesBefore, o.kit.doc.Input.Variables) @@ -889,6 +933,17 @@ func (o *OperationKit) NormalizeVariables() ([]uploads.UploadPathMapping, error) // If the normalized form of the operation didn't change, we don't need to print it again if bytes.Equal(o.kit.doc.Input.Variables, variablesBefore) && bytes.Equal(o.kit.doc.Input.RawBytes, operationRawBytesBefore) { + if o.cache != nil && o.cache.variablesNormalizationCache != nil { + entry := VariablesNormalizationCacheEntry{ + uploadsMapping: uploadsMapping, + id: o.parsedOperation.ID, + normalizedRepresentation: o.parsedOperation.NormalizedRepresentation, + variables: o.parsedOperation.Request.Variables, + reparse: false, + } + o.cache.variablesNormalizationCache.Set(cacheKey, entry, 1) + } + return uploadsMapping, nil } @@ -902,10 +957,43 @@ func (o *OperationKit) NormalizeVariables() ([]uploads.UploadPathMapping, error) o.parsedOperation.NormalizedRepresentation = o.kit.normalizedOperation.String() o.parsedOperation.Request.Variables = o.kit.doc.Input.Variables + if o.cache != nil && o.cache.variablesNormalizationCache != nil { + entry := VariablesNormalizationCacheEntry{ + uploadsMapping: uploadsMapping, + id: o.parsedOperation.ID, + normalizedRepresentation: o.parsedOperation.NormalizedRepresentation, + variables: o.parsedOperation.Request.Variables, + reparse: true, + } + o.cache.variablesNormalizationCache.Set(cacheKey, entry, 1) + } + return uploadsMapping, nil } +func (o *OperationKit) remapVariablesCacheKey() uint64 { + _, _ = o.kit.keyGen.WriteString(o.parsedOperation.NormalizedRepresentation) + sum := o.kit.keyGen.Sum64() + o.kit.keyGen.Reset() + return sum +} + func (o *OperationKit) RemapVariables(disabled bool) error { + cacheKey := o.remapVariablesCacheKey() + if o.cache != nil && o.cache.remapVariablesCache != nil { + entry, ok := o.cache.remapVariablesCache.Get(cacheKey) + if ok { + o.parsedOperation.NormalizedRepresentation = entry.normalizedRepresentation + o.parsedOperation.InternalID = entry.internalID + o.parsedOperation.RemapVariables = entry.remapVariables + + if err := o.setAndParseOperationDoc(); err != nil { + return err + } + return nil + } + } + report := &operationreport.Report{} // even if the variables are disabled, we still need to execute rest of the method, @@ -955,6 +1043,15 @@ func (o *OperationKit) RemapVariables(disabled bool) error { o.parsedOperation.NormalizedRepresentation = o.kit.normalizedOperation.String() + if o.cache != nil && o.cache.remapVariablesCache != nil { + entry := RemapVariablesCacheEntry{ + internalID: o.parsedOperation.InternalID, + normalizedRepresentation: o.parsedOperation.NormalizedRepresentation, + remapVariables: o.parsedOperation.RemapVariables, + } + o.cache.remapVariablesCache.Set(cacheKey, entry, 1) + } + return nil } @@ -1317,12 +1414,15 @@ func NewOperationProcessor(opts OperationProcessorOptions) *OperationProcessor { processor.parseKitSemaphore <- i processor.parseKits[i] = createParseKit(i, processor.parseKitOptions) } - if opts.NormalizationCache != nil || opts.ValidationCache != nil || opts.QueryDepthCache != nil || opts.OperationHashCache != nil || opts.EnablePersistedOperationsCache { + if opts.NormalizationCache != nil || opts.ValidationCache != nil || opts.QueryDepthCache != nil || opts.OperationHashCache != nil || opts.EnablePersistedOperationsCache || + opts.VariablesNormalizationCache != nil || opts.RemapVariablesCache != nil { processor.operationCache = &OperationCache{ - normalizationCache: opts.NormalizationCache, - validationCache: opts.ValidationCache, - complexityCache: opts.QueryDepthCache, - operationHashCache: opts.OperationHashCache, + normalizationCache: opts.NormalizationCache, + validationCache: opts.ValidationCache, + complexityCache: opts.QueryDepthCache, + operationHashCache: opts.OperationHashCache, + variablesNormalizationCache: opts.VariablesNormalizationCache, + remapVariablesCache: opts.RemapVariablesCache, } } if opts.EnablePersistedOperationsCache { From ac99a67b38796603b402f0ed7ef3dae6e706dd29 Mon Sep 17 00:00:00 2001 From: spetrunin Date: Wed, 5 Nov 2025 23:55:20 +0200 Subject: [PATCH 02/11] ensure we hit variables normalization cache: remove polluting of keygen, cleanup skip/include variables when we get normalized op from cache --- router-tests/normalization_cache_test.go | 24 +++++++++++++++++ router/core/operation_processor.go | 34 +++++++++++++++++------- 2 files changed, 48 insertions(+), 10 deletions(-) diff --git a/router-tests/normalization_cache_test.go b/router-tests/normalization_cache_test.go index b669436dfd..513b2fc34b 100644 --- a/router-tests/normalization_cache_test.go +++ b/router-tests/normalization_cache_test.go @@ -4,11 +4,35 @@ import ( "testing" "github.com/stretchr/testify/require" + "github.com/wundergraph/cosmo/router-tests/testenv" "github.com/wundergraph/cosmo/router/core" "github.com/wundergraph/cosmo/router/pkg/config" ) +func TestAdditionalNormalizationCaches(t *testing.T) { + t.Parallel() + + testenv.Run(t, &testenv.Config{}, func(t *testing.T, xEnv *testenv.Environment) { + f := func(v string) { + res, err := xEnv.MakeGraphQLRequest(testenv.GraphQLRequest{ + OperationName: []byte(`"Employee"`), + Query: `query Employee( $id: Int! = 4 $withAligators: Boolean! $withCats: Boolean! $skipDogs:Boolean! $skipMouses:Boolean! ) { employee(id: $id) { details { pets { name __typename ...AlligatorFields @include(if: $withAligators) ...CatFields @include(if: $withCats) ...DogFields @skip(if: $skipDogs) ...MouseFields @skip(if: $skipMouses) ...PonyFields @include(if: false) } } } } fragment AlligatorFields on Alligator { __typename class dangerous gender name } fragment CatFields on Cat { __typename class gender name type } fragment DogFields on Dog { __typename breed class gender name } fragment MouseFields on Mouse { __typename class gender name } fragment PonyFields on Pony { __typename class gender name }`, + Variables: []byte(`{"withAligators": true,"withCats": true,"skipDogs": false,"skipMouses": true}`), + }) + require.NoError(t, err) + require.Equal(t, v, res.Response.Header.Get(core.NormalizationCacheHeader)) + require.Equal(t, `{"data":{"employee":{"details":{"pets":[{"name":"Abby","__typename":"Dog","breed":"GOLDEN_RETRIEVER","class":"MAMMAL","gender":"FEMALE"},{"name":"Survivor","__typename":"Pony"}]}}}}`, res.Body) + } + + f("MISS") + f("HIT") + f("HIT") + f("HIT") + f("HIT") + }) +} + func TestNormalizationCache(t *testing.T) { t.Parallel() diff --git a/router/core/operation_processor.go b/router/core/operation_processor.go index 6981e8b53b..d3803a1e4e 100644 --- a/router/core/operation_processor.go +++ b/router/core/operation_processor.go @@ -753,7 +753,6 @@ func (o *OperationKit) normalizePersistedOperation(clientName string, isApq bool } type NormalizationCacheEntry struct { - operationID uint64 normalizedRepresentation string operationType string operationDefinitionRef int @@ -790,6 +789,13 @@ func (o *OperationKit) normalizeNonPersistedOperation() (cached bool, err error) o.parsedOperation.NormalizedRepresentation = entry.normalizedRepresentation o.parsedOperation.Type = entry.operationType o.parsedOperation.NormalizationCacheHit = true + + // remove skip include variables from variables + // as they were removed during normalization, but still present when we get operation from cache + for _, varName := range skipIncludeVariableNames { + o.parsedOperation.Request.Variables = jsonparser.Delete(o.parsedOperation.Request.Variables, varName) + } + err = o.setAndParseOperationDoc() if err != nil { return false, err @@ -811,12 +817,6 @@ func (o *OperationKit) normalizeNonPersistedOperation() (cached bool, err error) // set variables to the normalized variables as skip inlude variables will be removed after normalization o.parsedOperation.Request.Variables = o.kit.doc.Input.Variables - // Hash the normalized operation with the static operation name & original variables to avoid different IDs for the same operation - err = o.kit.printer.Print(o.kit.doc, o.kit.keyGen) - if err != nil { - return false, errors.WithStack(fmt.Errorf("normalizeNonPersistedOperation (uncached) failed generating operation hash: %w", err)) - } - // Print the operation with the original operation name o.kit.doc.OperationDefinitions[o.operationDefinitionRef].Name = o.originalOperationNameRef err = o.kit.printer.Print(o.kit.doc, o.kit.normalizedOperation) @@ -829,7 +829,6 @@ func (o *OperationKit) normalizeNonPersistedOperation() (cached bool, err error) if o.cache != nil && o.cache.normalizationCache != nil { entry := NormalizationCacheEntry{ - operationID: o.parsedOperation.InternalID, normalizedRepresentation: o.parsedOperation.NormalizedRepresentation, operationType: o.parsedOperation.Type, } @@ -860,8 +859,12 @@ func (o *OperationKit) setAndParseOperationDoc() error { func (o *OperationKit) normalizeVariablesCacheKey() uint64 { _, _ = o.kit.keyGen.Write(o.kit.doc.Input.Variables) + + // fmt.Println("####### NormalizeVariables: variables len:", len(o.kit.doc.Input.Variables)) + // fmt.Println("####### NormalizeVariables: variables:", string(o.kit.doc.Input.Variables)) _, _ = o.kit.keyGen.WriteString(o.parsedOperation.NormalizedRepresentation) + // fmt.Println("####### NormalizeVariables: normalizedRepresentation:", o.parsedOperation.NormalizedRepresentation) sum := o.kit.keyGen.Sum64() o.kit.keyGen.Reset() return sum @@ -869,6 +872,7 @@ func (o *OperationKit) normalizeVariablesCacheKey() uint64 { func (o *OperationKit) NormalizeVariables() ([]uploads.UploadPathMapping, error) { cacheKey := o.normalizeVariablesCacheKey() + // fmt.Println("####### NormalizeVariables: cacheKey:", cacheKey, "#######") if o.cache != nil && o.cache.variablesNormalizationCache != nil { entry, ok := o.cache.variablesNormalizationCache.Get(cacheKey) if ok { @@ -881,7 +885,12 @@ func (o *OperationKit) NormalizeVariables() ([]uploads.UploadPathMapping, error) return nil, err } } + + // fmt.Println("####### NormalizeVariables: cache hit #######") + return entry.uploadsMapping, nil + } else { + // fmt.Println("####### NormalizeVariables: cache miss #######") } } @@ -973,6 +982,7 @@ func (o *OperationKit) NormalizeVariables() ([]uploads.UploadPathMapping, error) func (o *OperationKit) remapVariablesCacheKey() uint64 { _, _ = o.kit.keyGen.WriteString(o.parsedOperation.NormalizedRepresentation) + // fmt.Println("####### RemapVariables: normalizedRepresentation:", o.parsedOperation.NormalizedRepresentation) sum := o.kit.keyGen.Sum64() o.kit.keyGen.Reset() return sum @@ -980,6 +990,7 @@ func (o *OperationKit) remapVariablesCacheKey() uint64 { func (o *OperationKit) RemapVariables(disabled bool) error { cacheKey := o.remapVariablesCacheKey() + // fmt.Println("####### RemapVariables: cacheKey:", cacheKey, "#######") if o.cache != nil && o.cache.remapVariablesCache != nil { entry, ok := o.cache.remapVariablesCache.Get(cacheKey) if ok { @@ -990,7 +1001,12 @@ func (o *OperationKit) RemapVariables(disabled bool) error { if err := o.setAndParseOperationDoc(); err != nil { return err } + + // fmt.Println("####### RemapVariables: cache hit #######") + return nil + } else { + // fmt.Println("####### RemapVariables: cache miss #######") } } @@ -1092,7 +1108,6 @@ func (o *OperationKit) handleFoundPersistedOperationEntry(entry NormalizationCac // as we skip parse for the cached persisted operations o.parsedOperation.IsPersistedOperation = true o.parsedOperation.NormalizationCacheHit = true - o.parsedOperation.InternalID = entry.operationID o.parsedOperation.NormalizedRepresentation = entry.normalizedRepresentation o.parsedOperation.Type = entry.operationType // We will always only have a single operation definition in the document @@ -1139,7 +1154,6 @@ func (o *OperationKit) persistedOperationCacheKeyHasTtl(clientName string, inclu func (o *OperationKit) savePersistedOperationToCache(clientName string, isApq bool, skipIncludeVariableNames []string) { cacheKey := o.generatePersistedOperationCacheKey(clientName, skipIncludeVariableNames, o.kit.numOperations > 1) entry := NormalizationCacheEntry{ - operationID: o.parsedOperation.InternalID, normalizedRepresentation: o.parsedOperation.NormalizedRepresentation, operationType: o.parsedOperation.Type, operationDefinitionRef: o.operationDefinitionRef, From 5096314cbbad698f591ac5b865f96e4a551d0362 Mon Sep 17 00:00:00 2001 From: spetrunin Date: Thu, 6 Nov 2025 00:03:42 +0200 Subject: [PATCH 03/11] ensure that we hit variables remap cache --- router/core/operation_processor.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/router/core/operation_processor.go b/router/core/operation_processor.go index d3803a1e4e..76ebcc65e0 100644 --- a/router/core/operation_processor.go +++ b/router/core/operation_processor.go @@ -932,13 +932,14 @@ func (o *OperationKit) NormalizeVariables() ([]uploads.UploadPathMapping, error) // Reset the doc with the original name o.kit.doc.OperationDefinitions[o.operationDefinitionRef].Name = nameRef - o.kit.keyGen.Reset() + o.kit.keyGen.Reset() // should not be needed if we properly reset after use - check do we have any remaining places where we do not reset keygen - maybe wrap into a type which will reset once we got key _, err = o.kit.keyGen.Write(o.kit.normalizedOperation.Bytes()) if err != nil { return nil, err } o.parsedOperation.ID = o.kit.keyGen.Sum64() + o.kit.keyGen.Reset() // If the normalized form of the operation didn't change, we don't need to print it again if bytes.Equal(o.kit.doc.Input.Variables, variablesBefore) && bytes.Equal(o.kit.doc.Input.RawBytes, operationRawBytesBefore) { @@ -981,6 +982,7 @@ func (o *OperationKit) NormalizeVariables() ([]uploads.UploadPathMapping, error) } func (o *OperationKit) remapVariablesCacheKey() uint64 { + // fmt.Println("####### RemapVariables: normalized representation len:", len(o.parsedOperation.NormalizedRepresentation)) _, _ = o.kit.keyGen.WriteString(o.parsedOperation.NormalizedRepresentation) // fmt.Println("####### RemapVariables: normalizedRepresentation:", o.parsedOperation.NormalizedRepresentation) sum := o.kit.keyGen.Sum64() From a2bf4a4f9a4b3bd478a24498d8ae32b05661d7c5 Mon Sep 17 00:00:00 2001 From: Yury Smolski <140245+ysmolski@users.noreply.github.com> Date: Fri, 14 Nov 2025 16:34:32 +0200 Subject: [PATCH 04/11] add proper cache hits and more tests --- router-tests/normalization_cache_test.go | 154 ++++++++++++++++++++--- router/core/cache_warmup.go | 4 +- router/core/context.go | 6 +- router/core/graph_server.go | 48 ++----- router/core/graphql_handler.go | 35 +++--- router/core/graphql_prehandler.go | 12 +- router/core/operation_processor.go | 103 ++++++++------- router/core/websocket.go | 9 +- router/pkg/otel/attributes.go | 2 + 9 files changed, 244 insertions(+), 129 deletions(-) diff --git a/router-tests/normalization_cache_test.go b/router-tests/normalization_cache_test.go index 513b2fc34b..a0f9432a83 100644 --- a/router-tests/normalization_cache_test.go +++ b/router-tests/normalization_cache_test.go @@ -1,6 +1,7 @@ package integration import ( + "fmt" "testing" "github.com/stretchr/testify/require" @@ -10,27 +11,150 @@ import ( "github.com/wundergraph/cosmo/router/pkg/config" ) +// cacheHit represents the expected cache hit/miss status for all three normalization stages. +// True values mean the cache hit. +type cacheHit struct { + normalization bool + variables bool + remapping bool +} + +// assertCacheHeaders checks all three normalization cache headers +func assertCacheHeaders(t *testing.T, res *testenv.TestResponse, expected cacheHit) { + t.Helper() + s := func(hit bool) string { + if hit { + return "HIT" + } + return "MISS" + } + + require.Equal(t, s(expected.normalization), res.Response.Header.Get(core.NormalizationCacheHeader), + "Normalization cache hit mismatch") + require.Equal(t, s(expected.variables), res.Response.Header.Get(core.VariablesNormalizationCacheHeader), + "Variables normalization cache hit mismatch") + require.Equal(t, s(expected.remapping), res.Response.Header.Get(core.VariablesRemappingCacheHeader), + "Variables remapping cache hit mismatch") +} + func TestAdditionalNormalizationCaches(t *testing.T) { t.Parallel() - testenv.Run(t, &testenv.Config{}, func(t *testing.T, xEnv *testenv.Environment) { - f := func(v string) { - res, err := xEnv.MakeGraphQLRequest(testenv.GraphQLRequest{ - OperationName: []byte(`"Employee"`), - Query: `query Employee( $id: Int! = 4 $withAligators: Boolean! $withCats: Boolean! $skipDogs:Boolean! $skipMouses:Boolean! ) { employee(id: $id) { details { pets { name __typename ...AlligatorFields @include(if: $withAligators) ...CatFields @include(if: $withCats) ...DogFields @skip(if: $skipDogs) ...MouseFields @skip(if: $skipMouses) ...PonyFields @include(if: false) } } } } fragment AlligatorFields on Alligator { __typename class dangerous gender name } fragment CatFields on Cat { __typename class gender name type } fragment DogFields on Dog { __typename breed class gender name } fragment MouseFields on Mouse { __typename class gender name } fragment PonyFields on Pony { __typename class gender name }`, - Variables: []byte(`{"withAligators": true,"withCats": true,"skipDogs": false,"skipMouses": true}`), + t.Run("Basic normalization cache with skip/include", func(t *testing.T) { + t.Parallel() + testenv.Run(t, &testenv.Config{}, func(t *testing.T, xEnv *testenv.Environment) { + f := func(expected cacheHit, skipMouse bool) { + res := xEnv.MakeGraphQLRequestOK(testenv.GraphQLRequest{ + OperationName: []byte(`"Employee"`), + Query: `query Employee( $id: Int! = 4 $withAligators: Boolean! $withCats: Boolean! $skipDogs:Boolean! $skipMouses:Boolean! ) { employee(id: $id) { details { pets { name __typename ...AlligatorFields @include(if: $withAligators) ...CatFields @include(if: $withCats) ...DogFields @skip(if: $skipDogs) ...MouseFields @skip(if: $skipMouses) ...PonyFields @include(if: false) } } } } fragment AlligatorFields on Alligator { __typename class dangerous gender name } fragment CatFields on Cat { __typename class gender name type } fragment DogFields on Dog { __typename breed class gender name } fragment MouseFields on Mouse { __typename class gender name } fragment PonyFields on Pony { __typename class gender name }`, + Variables: []byte(fmt.Sprintf(`{"withAligators": true,"withCats": true,"skipDogs": false,"skipMouses": %t}`, skipMouse)), + }) + assertCacheHeaders(t, res, expected) + require.Equal(t, `{"data":{"employee":{"details":{"pets":[{"name":"Abby","__typename":"Dog","breed":"GOLDEN_RETRIEVER","class":"MAMMAL","gender":"FEMALE"},{"name":"Survivor","__typename":"Pony"}]}}}}`, res.Body) + } + + // First request: all caches miss + f(cacheHit{false, false, false}, true) + // Second request: all caches hit + f(cacheHit{true, true, true}, true) + // Third request: all caches hit + f(cacheHit{true, true, true}, true) + // Fourth request: different skip/include value, all caches miss + f(cacheHit{false, false, false}, false) + // Fifth request: back to original skip/include value, all caches hit + f(cacheHit{true, true, true}, true) + }) + }) + + t.Run("Variables normalization cache - inline value extraction", func(t *testing.T) { + t.Parallel() + testenv.Run(t, &testenv.Config{}, func(t *testing.T, xEnv *testenv.Environment) { + // Test 1: Inline value gets extracted to variable - all caches miss + res := xEnv.MakeGraphQLRequestOK(testenv.GraphQLRequest{ + Query: `query { employee(id: 1) { id details { forename } } }`, }) - require.NoError(t, err) - require.Equal(t, v, res.Response.Header.Get(core.NormalizationCacheHeader)) - require.Equal(t, `{"data":{"employee":{"details":{"pets":[{"name":"Abby","__typename":"Dog","breed":"GOLDEN_RETRIEVER","class":"MAMMAL","gender":"FEMALE"},{"name":"Survivor","__typename":"Pony"}]}}}}`, res.Body) - } + assertCacheHeaders(t, res, cacheHit{false, false, false}) + + // Test 2: Same query - all caches hit + res = xEnv.MakeGraphQLRequestOK(testenv.GraphQLRequest{ + Query: `query { employee(id: 1) { id details { forename } } }`, + }) + assertCacheHeaders(t, res, cacheHit{true, true, true}) - f("MISS") - f("HIT") - f("HIT") - f("HIT") - f("HIT") + // Test 3: Different inline value + res = xEnv.MakeGraphQLRequestOK(testenv.GraphQLRequest{ + Query: `query { employee(id: 2) { id details { forename } } }`, + }) + assertCacheHeaders(t, res, cacheHit{false, false, true}) + }) }) + + t.Run("Variables normalization cache - query changes, but variables stay the same", func(t *testing.T) { + t.Parallel() + testenv.Run(t, &testenv.Config{}, func(t *testing.T, xEnv *testenv.Environment) { + // Test with unused variables that should be removed - all caches miss + res := xEnv.MakeGraphQLRequestOK(testenv.GraphQLRequest{ + Query: `query MyQuery($id: Int!) { employee(id: $id) { id } }`, + Variables: []byte(`{"id": 1}`), + }) + require.Equal(t, `{"data":{"employee":{"id":1}}}`, res.Body) + assertCacheHeaders(t, res, cacheHit{false, false, false}) + + // Different query with same variable value. + res = xEnv.MakeGraphQLRequestOK(testenv.GraphQLRequest{ + Query: `query MyQuery($id: Int!) { employee(id: $id) { id details { forename }} }`, + Variables: []byte(`{"id": 1}`), + }) + require.Equal(t, `{"data":{"employee":{"id":1,"details":{"forename":"Jens"}}}}`, res.Body) + assertCacheHeaders(t, res, cacheHit{false, false, false}) + }) + }) + + t.Run("Cache key isolation - different operations don't collide", func(t *testing.T) { + testenv.Run(t, &testenv.Config{}, func(t *testing.T, xEnv *testenv.Environment) { + // Test 1: Query A + res := xEnv.MakeGraphQLRequestOK(testenv.GraphQLRequest{ + Query: `query A($id: Int!) { employee(id: $id) { id } }`, + Variables: []byte(`{"id": 1}`), + }) + assertCacheHeaders(t, res, cacheHit{false, false, false}) + + // Test 2: Query B with different structure should miss + res = xEnv.MakeGraphQLRequestOK(testenv.GraphQLRequest{ + Query: `query B($id: Int!) { employee(id: $id) { id details { forename } } }`, + Variables: []byte(`{"id": 1}`), + }) + assertCacheHeaders(t, res, cacheHit{false, false, false}) + + // Test 3: Query A again should hit its own cache + res = xEnv.MakeGraphQLRequestOK(testenv.GraphQLRequest{ + Query: `query A($id: Int!) { employee(id: $id) { id } }`, + Variables: []byte(`{"id": 1}`), + }) + assertCacheHeaders(t, res, cacheHit{true, true, true}) + }) + }) + + t.Run("List coercion with variables normalization cache", func(t *testing.T) { + testenv.Run(t, &testenv.Config{}, func(t *testing.T, xEnv *testenv.Environment) { + // Test that list coercion works correctly with caching + res := xEnv.MakeGraphQLRequestOK(testenv.GraphQLRequest{ + Query: `query MyQuery($arg: [String!]!) { rootFieldWithListArg(arg: $arg) }`, + Variables: []byte(`{"arg": "single"}`), + }) + require.Equal(t, `{"data":{"rootFieldWithListArg":["single"]}}`, res.Body) + assertCacheHeaders(t, res, cacheHit{false, false, false}) + + // Same structure should hit cache even with different value + res = xEnv.MakeGraphQLRequestOK(testenv.GraphQLRequest{ + Query: `query MyQuery($arg: [String!]!) { rootFieldWithListArg(arg: $arg) }`, + Variables: []byte(`{"arg": "different"}`), + }) + require.Equal(t, `{"data":{"rootFieldWithListArg":["different"]}}`, res.Body) + assertCacheHeaders(t, res, cacheHit{true, false, true}) + }) + }) + } func TestNormalizationCache(t *testing.T) { diff --git a/router/core/cache_warmup.go b/router/core/cache_warmup.go index 2689d7c5d4..b914567291 100644 --- a/router/core/cache_warmup.go +++ b/router/core/cache_warmup.go @@ -295,12 +295,12 @@ func (c *CacheWarmupPlanningProcessor) ProcessOperation(ctx context.Context, ope return nil, err } - _, err = k.NormalizeVariables() + _, _, err = k.NormalizeVariables() if err != nil { return nil, err } - err = k.RemapVariables(c.disableVariablesRemapping) + _, err = k.RemapVariables(c.disableVariablesRemapping) if err != nil { return nil, err } diff --git a/router/core/context.go b/router/core/context.go index 4cbde8706a..5dccb19915 100644 --- a/router/core/context.go +++ b/router/core/context.go @@ -540,8 +540,10 @@ type operationContext struct { sha256Hash string protocol OperationProtocol - persistedOperationCacheHit bool - normalizationCacheHit bool + persistedOperationCacheHit bool + normalizationCacheHit bool + variablesNormalizationCacheHit bool + variablesRemappingCacheHit bool typeFieldUsageInfo graphqlschemausage.TypeFieldMetrics argumentUsageInfo []*graphqlmetrics.ArgumentUsageInfo diff --git a/router/core/graph_server.go b/router/core/graph_server.go index 587f52e2aa..e0930a7315 100644 --- a/router/core/graph_server.go +++ b/router/core/graph_server.go @@ -506,16 +506,17 @@ func (s *graphServer) setupEngineStatistics(baseAttributes []attribute.KeyValue) } type graphMux struct { - mux *chi.Mux + mux *chi.Mux + planCache *ristretto.Cache[uint64, *planWithMetaData] persistedOperationCache *ristretto.Cache[uint64, NormalizationCacheEntry] normalizationCache *ristretto.Cache[uint64, NormalizationCacheEntry] complexityCalculationCache *ristretto.Cache[uint64, ComplexityCacheEntry] variablesNormalizationCache *ristretto.Cache[uint64, VariablesNormalizationCacheEntry] remapVariablesCache *ristretto.Cache[uint64, RemapVariablesCacheEntry] + validationCache *ristretto.Cache[uint64, bool] + operationHashCache *ristretto.Cache[uint64, string] - validationCache *ristretto.Cache[uint64, bool] - operationHashCache *ristretto.Cache[uint64, string] accessLogsFileLogger *logging.BufferedLogger metricStore rmetric.Store prometheusCacheMetrics *rmetric.CacheMetrics @@ -736,39 +737,16 @@ func (s *graphMux) configureCacheMetrics(srv *graphServer, baseOtelAttributes [] } func (s *graphMux) Shutdown(ctx context.Context) error { - var err error - - if s.planCache != nil { - s.planCache.Close() - } - - if s.persistedOperationCache != nil { - s.persistedOperationCache.Close() - } - - if s.normalizationCache != nil { - s.normalizationCache.Close() - } + s.planCache.Close() + s.persistedOperationCache.Close() + s.normalizationCache.Close() + s.variablesNormalizationCache.Close() + s.remapVariablesCache.Close() + s.complexityCalculationCache.Close() + s.validationCache.Close() + s.operationHashCache.Close() - if s.variablesNormalizationCache != nil { - s.variablesNormalizationCache.Close() - } - - if s.remapVariablesCache != nil { - s.remapVariablesCache.Close() - } - - if s.complexityCalculationCache != nil { - s.complexityCalculationCache.Close() - } - - if s.validationCache != nil { - s.validationCache.Close() - } - - if s.operationHashCache != nil { - s.operationHashCache.Close() - } + var err error if s.accessLogsFileLogger != nil { if aErr := s.accessLogsFileLogger.Close(); aErr != nil { diff --git a/router/core/graphql_handler.go b/router/core/graphql_handler.go index c494fff4ce..873f2264a4 100644 --- a/router/core/graphql_handler.go +++ b/router/core/graphql_handler.go @@ -34,9 +34,11 @@ var ( ) const ( - ExecutionPlanCacheHeader = "X-WG-Execution-Plan-Cache" - PersistedOperationCacheHeader = "X-WG-Persisted-Operation-Cache" - NormalizationCacheHeader = "X-WG-Normalization-Cache" + ExecutionPlanCacheHeader = "X-WG-Execution-Plan-Cache" + PersistedOperationCacheHeader = "X-WG-Persisted-Operation-Cache" + NormalizationCacheHeader = "X-WG-Normalization-Cache" + VariablesNormalizationCacheHeader = "X-WG-Variables-Normalization-Cache" + VariablesRemappingCacheHeader = "X-WG-Variables-Remapping-Cache" ) type ReportError interface { @@ -428,25 +430,22 @@ func (h *GraphQLHandler) WriteError(ctx *resolve.Context, err error, res *resolv } func (h *GraphQLHandler) setDebugCacheHeaders(w http.ResponseWriter, opCtx *operationContext) { - if h.enableNormalizationCacheResponseHeader { - if opCtx.normalizationCacheHit { - w.Header().Set(NormalizationCacheHeader, "HIT") - } else { - w.Header().Set(NormalizationCacheHeader, "MISS") + s := func(hit bool) string { + if hit { + return "HIT" } + return "MISS" + } + + if h.enableNormalizationCacheResponseHeader { + w.Header().Set(NormalizationCacheHeader, s(opCtx.normalizationCacheHit)) + w.Header().Set(VariablesNormalizationCacheHeader, s(opCtx.variablesNormalizationCacheHit)) + w.Header().Set(VariablesRemappingCacheHeader, s(opCtx.variablesRemappingCacheHit)) } if h.enablePersistedOperationCacheResponseHeader { - if opCtx.persistedOperationCacheHit { - w.Header().Set(PersistedOperationCacheHeader, "HIT") - } else { - w.Header().Set(PersistedOperationCacheHeader, "MISS") - } + w.Header().Set(PersistedOperationCacheHeader, s(opCtx.persistedOperationCacheHit)) } if h.enableExecutionPlanCacheResponseHeader { - if opCtx.planCacheHit { - w.Header().Set(ExecutionPlanCacheHeader, "HIT") - } else { - w.Header().Set(ExecutionPlanCacheHeader, "MISS") - } + w.Header().Set(ExecutionPlanCacheHeader, s(opCtx.planCacheHit)) } } diff --git a/router/core/graphql_prehandler.go b/router/core/graphql_prehandler.go index bbc3473034..cf1a99a83f 100644 --- a/router/core/graphql_prehandler.go +++ b/router/core/graphql_prehandler.go @@ -776,9 +776,7 @@ func (h *PreHandler) handleOperation(w http.ResponseWriter, req *http.Request, v return err } - // Set the cache hit attribute on the span engineNormalizeSpan.SetAttributes(otel.WgNormalizationCacheHit.Bool(cached)) - requestContext.operation.normalizationCacheHit = operationKit.parsedOperation.NormalizationCacheHit /** @@ -791,7 +789,7 @@ func (h *PreHandler) handleOperation(w http.ResponseWriter, req *http.Request, v // OriginalUploadPath string - is a path relative to variables which have an Upload type, example "variables.f" // NewUploadPath string - if variable was used in the inline object like this `arg: {f: $f}` this field will hold the new extracted path, example "variables.a.f", if it is an empty, there was no change in the path // } - uploadsMapping, err := operationKit.NormalizeVariables() + cached, uploadsMapping, err := operationKit.NormalizeVariables() if err != nil { rtrace.AttachErrToSpan(engineNormalizeSpan, err) @@ -804,9 +802,10 @@ func (h *PreHandler) handleOperation(w http.ResponseWriter, req *http.Request, v } engineNormalizeSpan.End() - return err } + engineNormalizeSpan.SetAttributes(otel.WgVariablesNormalizationCacheHit.Bool(cached)) + requestContext.operation.variablesNormalizationCacheHit = cached // update file uploads path if they were used in nested field in the extracted variables for mapping := range slices.Values(uploadsMapping) { @@ -835,7 +834,7 @@ func (h *PreHandler) handleOperation(w http.ResponseWriter, req *http.Request, v // RemapVariables is updating and sort variables name to be able to have them in a predictable order // after remapping requestContext.operation.remapVariables map will contain new names as a keys and old names as a values - to be able to extract the old values // because it does not rename variables in a variables json - err = operationKit.RemapVariables(h.disableVariablesRemapping) + cached, err = operationKit.RemapVariables(h.disableVariablesRemapping) if err != nil { rtrace.AttachErrToSpan(engineNormalizeSpan, err) @@ -848,10 +847,11 @@ func (h *PreHandler) handleOperation(w http.ResponseWriter, req *http.Request, v } engineNormalizeSpan.End() - return err } + engineNormalizeSpan.SetAttributes(otel.WgVariablesRemappingCacheHit.Bool(cached)) + requestContext.operation.variablesRemappingCacheHit = cached requestContext.operation.hash = operationKit.parsedOperation.ID requestContext.operation.internalHash = operationKit.parsedOperation.InternalID requestContext.operation.remapVariables = operationKit.parsedOperation.RemapVariables diff --git a/router/core/operation_processor.go b/router/core/operation_processor.go index 76ebcc65e0..1d8ccd5963 100644 --- a/router/core/operation_processor.go +++ b/router/core/operation_processor.go @@ -47,27 +47,33 @@ var ( type ParsedOperation struct { // ID represents a unique-ish ID for the operation calculated by hashing - // its normalized representation + // its normalized representation. ID uint64 + // InternalID is the internal ID of the operation calculated by hashing - // its normalized representation with the original operation name and normalized variables + // its normalized representation with the original operation name and normalized variables. InternalID uint64 - // Sha256Hash is the sha256 hash of the original operation query sent by the client + + // Sha256Hash is the sha256 hash of the original operation query sent by the client. Sha256Hash string - // Type is a string representing the operation type. One of - // "query", "mutation", "subscription" + + // Type is a string representing the operation type. One of "query", "mutation", "subscription". Type string Variables *fastjson.Object RemapVariables map[string]string - // NormalizedRepresentation is the normalized representation of the operation - // as a string. This is provided for modules to be able to access the - // operation. Only available after the operation has been normalized. - NormalizedRepresentation string + + // NormalizedRepresentation is the normalized representation of the operation as a string. + // This is provided for modules to be able to access the operation. + // Only available after the operation has been normalized. + NormalizedRepresentation string + Request GraphQLRequest GraphQLRequestExtensions GraphQLRequestExtensions IsPersistedOperation bool PersistedOperationCacheHit bool - // NormalizationCacheHit is set to true if the request is a non-persisted operation and the normalized operation was loaded from cache + + // NormalizationCacheHit is set to true if the request is a non-persisted operation, + // and the normalized operation was loaded from cache. NormalizationCacheHit bool } @@ -759,17 +765,31 @@ type NormalizationCacheEntry struct { } type VariablesNormalizationCacheEntry struct { + // See ParsedOperation for the explanation of the fields below. normalizedRepresentation string - uploadsMapping []uploads.UploadPathMapping id uint64 - variables json.RawMessage - reparse bool + + // variables store JSON of the normalized variables. + variables json.RawMessage + + // The uploadsMapping slice tracks file upload variables and how their paths change during + // GraphQL operation normalization. This is specifically for handling the GraphQL multipart + // request spec for file uploads. + uploadsMapping []uploads.UploadPathMapping + + // reparse indicates whether the operation document needs to be reparsed from + // its string representation when retrieved from the cache. + reparse bool } type RemapVariablesCacheEntry struct { - internalID uint64 + // internalID is used as the cache key for validation, complexity and planner caches. + internalID uint64 + normalizedRepresentation string - remapVariables map[string]string + + // result of remapping + remapVariables map[string]string } type ComplexityCacheEntry struct { @@ -870,7 +890,7 @@ func (o *OperationKit) normalizeVariablesCacheKey() uint64 { return sum } -func (o *OperationKit) NormalizeVariables() ([]uploads.UploadPathMapping, error) { +func (o *OperationKit) NormalizeVariables() (cached bool, mapping []uploads.UploadPathMapping, err error) { cacheKey := o.normalizeVariablesCacheKey() // fmt.Println("####### NormalizeVariables: cacheKey:", cacheKey, "#######") if o.cache != nil && o.cache.variablesNormalizationCache != nil { @@ -881,16 +901,11 @@ func (o *OperationKit) NormalizeVariables() ([]uploads.UploadPathMapping, error) o.parsedOperation.Request.Variables = entry.variables if entry.reparse { - if err := o.setAndParseOperationDoc(); err != nil { - return nil, err + if err = o.setAndParseOperationDoc(); err != nil { + return false, nil, err } } - - // fmt.Println("####### NormalizeVariables: cache hit #######") - - return entry.uploadsMapping, nil - } else { - // fmt.Println("####### NormalizeVariables: cache miss #######") + return true, entry.uploadsMapping, nil } } @@ -903,9 +918,7 @@ func (o *OperationKit) NormalizeVariables() ([]uploads.UploadPathMapping, error) report := &operationreport.Report{} uploadsMapping := o.kit.variablesNormalizer.NormalizeOperation(o.kit.doc, o.operationProcessor.executor.ClientSchema, report) if report.HasErrors() { - return nil, &reportError{ - report: report, - } + return false, nil, &reportError{report: report} } // Assuming the user sends a multi-operation document @@ -925,9 +938,9 @@ func (o *OperationKit) NormalizeVariables() ([]uploads.UploadPathMapping, error) staticNameRef := o.kit.doc.Input.AppendInputBytes([]byte("")) o.kit.doc.OperationDefinitions[o.operationDefinitionRef].Name = staticNameRef - err := o.kit.printer.Print(o.kit.doc, o.kit.normalizedOperation) + err = o.kit.printer.Print(o.kit.doc, o.kit.normalizedOperation) if err != nil { - return nil, err + return false, nil, err } // Reset the doc with the original name o.kit.doc.OperationDefinitions[o.operationDefinitionRef].Name = nameRef @@ -935,7 +948,7 @@ func (o *OperationKit) NormalizeVariables() ([]uploads.UploadPathMapping, error) o.kit.keyGen.Reset() // should not be needed if we properly reset after use - check do we have any remaining places where we do not reset keygen - maybe wrap into a type which will reset once we got key _, err = o.kit.keyGen.Write(o.kit.normalizedOperation.Bytes()) if err != nil { - return nil, err + return false, nil, err } o.parsedOperation.ID = o.kit.keyGen.Sum64() @@ -954,14 +967,14 @@ func (o *OperationKit) NormalizeVariables() ([]uploads.UploadPathMapping, error) o.cache.variablesNormalizationCache.Set(cacheKey, entry, 1) } - return uploadsMapping, nil + return false, uploadsMapping, nil } o.kit.normalizedOperation.Reset() err = o.kit.printer.Print(o.kit.doc, o.kit.normalizedOperation) if err != nil { - return nil, err + return false, nil, err } o.parsedOperation.NormalizedRepresentation = o.kit.normalizedOperation.String() @@ -978,7 +991,7 @@ func (o *OperationKit) NormalizeVariables() ([]uploads.UploadPathMapping, error) o.cache.variablesNormalizationCache.Set(cacheKey, entry, 1) } - return uploadsMapping, nil + return false, uploadsMapping, nil } func (o *OperationKit) remapVariablesCacheKey() uint64 { @@ -990,7 +1003,7 @@ func (o *OperationKit) remapVariablesCacheKey() uint64 { return sum } -func (o *OperationKit) RemapVariables(disabled bool) error { +func (o *OperationKit) RemapVariables(disabled bool) (cached bool, err error) { cacheKey := o.remapVariablesCacheKey() // fmt.Println("####### RemapVariables: cacheKey:", cacheKey, "#######") if o.cache != nil && o.cache.remapVariablesCache != nil { @@ -1001,14 +1014,10 @@ func (o *OperationKit) RemapVariables(disabled bool) error { o.parsedOperation.RemapVariables = entry.remapVariables if err := o.setAndParseOperationDoc(); err != nil { - return err + return false, err } - // fmt.Println("####### RemapVariables: cache hit #######") - - return nil - } else { - // fmt.Println("####### RemapVariables: cache miss #######") + return true, nil } } @@ -1019,9 +1028,7 @@ func (o *OperationKit) RemapVariables(disabled bool) error { if !disabled { variablesMap := o.kit.variablesRemapper.NormalizeOperation(o.kit.doc, o.operationProcessor.executor.ClientSchema, report) if report.HasErrors() { - return &reportError{ - report: report, - } + return false, &reportError{report: report} } o.parsedOperation.RemapVariables = variablesMap } @@ -1036,9 +1043,9 @@ func (o *OperationKit) RemapVariables(disabled bool) error { staticNameRef := o.kit.doc.Input.AppendInputBytes([]byte("")) o.kit.doc.OperationDefinitions[o.operationDefinitionRef].Name = staticNameRef - err := o.kit.printer.Print(o.kit.doc, o.kit.normalizedOperation) + err = o.kit.printer.Print(o.kit.doc, o.kit.normalizedOperation) if err != nil { - return errors.WithStack(fmt.Errorf("RemapVariables failed generating operation hash: %w", err)) + return false, errors.WithStack(fmt.Errorf("RemapVariables failed generating operation hash: %w", err)) } // Reset the doc with the original name o.kit.doc.OperationDefinitions[o.operationDefinitionRef].Name = nameRef @@ -1046,7 +1053,7 @@ func (o *OperationKit) RemapVariables(disabled bool) error { o.kit.keyGen.Reset() _, err = o.kit.keyGen.Write(o.kit.normalizedOperation.Bytes()) if err != nil { - return err + return false, err } // Generate the operation ID @@ -1056,7 +1063,7 @@ func (o *OperationKit) RemapVariables(disabled bool) error { o.kit.normalizedOperation.Reset() err = o.kit.printer.Print(o.kit.doc, o.kit.normalizedOperation) if err != nil { - return err + return false, err } o.parsedOperation.NormalizedRepresentation = o.kit.normalizedOperation.String() @@ -1070,7 +1077,7 @@ func (o *OperationKit) RemapVariables(disabled bool) error { o.cache.remapVariablesCache.Set(cacheKey, entry, 1) } - return nil + return false, nil } func (o *OperationKit) loadPersistedOperationFromCache(clientName string) (ok bool, includeOpName bool, err error) { diff --git a/router/core/websocket.go b/router/core/websocket.go index 06ee4adc55..c662fa1ba0 100644 --- a/router/core/websocket.go +++ b/router/core/websocket.go @@ -903,18 +903,21 @@ func (h *WebSocketConnectionHandler) parseAndPlan(registration *SubscriptionRegi opContext.normalizationTime = time.Since(startNormalization) return nil, nil, err } - opContext.normalizationCacheHit = operationKit.parsedOperation.NormalizationCacheHit - if _, err := operationKit.NormalizeVariables(); err != nil { + cached, _, err := operationKit.NormalizeVariables() + if err != nil { opContext.normalizationTime = time.Since(startNormalization) return nil, nil, err } + opContext.variablesNormalizationCacheHit = cached - if err := operationKit.RemapVariables(h.disableVariablesRemapping); err != nil { + cached, err = operationKit.RemapVariables(h.disableVariablesRemapping) + if err != nil { opContext.normalizationTime = time.Since(startNormalization) return nil, nil, err } + opContext.variablesRemappingCacheHit = cached opContext.hash = operationKit.parsedOperation.ID opContext.internalHash = operationKit.parsedOperation.InternalID diff --git a/router/pkg/otel/attributes.go b/router/pkg/otel/attributes.go index 1a1a0d5baf..37807fbf75 100644 --- a/router/pkg/otel/attributes.go +++ b/router/pkg/otel/attributes.go @@ -36,6 +36,8 @@ const ( WgFeatureFlag = attribute.Key("wg.feature_flag") WgAcquireResolverWaitTimeMs = attribute.Key("wg.engine.resolver.wait_time_ms") WgNormalizationCacheHit = attribute.Key("wg.engine.normalization_cache_hit") + WgVariablesNormalizationCacheHit = attribute.Key("wg.engine.variables_normalization_cache_hit") + WgVariablesRemappingCacheHit = attribute.Key("wg.engine.variables_remapping_cache_hit") WgValidationCacheHit = attribute.Key("wg.engine.validation_cache_hit") WgVariablesValidationSkipped = attribute.Key("wg.engine.variables_validation_skipped") WgQueryDepth = attribute.Key("wg.operation.complexity.query_depth") From dcbf4419b826b02d31a2893147d895e7e50be182 Mon Sep 17 00:00:00 2001 From: Yury Smolski <140245+ysmolski@users.noreply.github.com> Date: Fri, 14 Nov 2025 16:36:53 +0200 Subject: [PATCH 05/11] remove debug comments --- router/core/operation_processor.go | 8 -------- 1 file changed, 8 deletions(-) diff --git a/router/core/operation_processor.go b/router/core/operation_processor.go index 1d8ccd5963..a003ea27ac 100644 --- a/router/core/operation_processor.go +++ b/router/core/operation_processor.go @@ -879,12 +879,8 @@ func (o *OperationKit) setAndParseOperationDoc() error { func (o *OperationKit) normalizeVariablesCacheKey() uint64 { _, _ = o.kit.keyGen.Write(o.kit.doc.Input.Variables) - - // fmt.Println("####### NormalizeVariables: variables len:", len(o.kit.doc.Input.Variables)) - // fmt.Println("####### NormalizeVariables: variables:", string(o.kit.doc.Input.Variables)) _, _ = o.kit.keyGen.WriteString(o.parsedOperation.NormalizedRepresentation) - // fmt.Println("####### NormalizeVariables: normalizedRepresentation:", o.parsedOperation.NormalizedRepresentation) sum := o.kit.keyGen.Sum64() o.kit.keyGen.Reset() return sum @@ -892,7 +888,6 @@ func (o *OperationKit) normalizeVariablesCacheKey() uint64 { func (o *OperationKit) NormalizeVariables() (cached bool, mapping []uploads.UploadPathMapping, err error) { cacheKey := o.normalizeVariablesCacheKey() - // fmt.Println("####### NormalizeVariables: cacheKey:", cacheKey, "#######") if o.cache != nil && o.cache.variablesNormalizationCache != nil { entry, ok := o.cache.variablesNormalizationCache.Get(cacheKey) if ok { @@ -995,9 +990,7 @@ func (o *OperationKit) NormalizeVariables() (cached bool, mapping []uploads.Uplo } func (o *OperationKit) remapVariablesCacheKey() uint64 { - // fmt.Println("####### RemapVariables: normalized representation len:", len(o.parsedOperation.NormalizedRepresentation)) _, _ = o.kit.keyGen.WriteString(o.parsedOperation.NormalizedRepresentation) - // fmt.Println("####### RemapVariables: normalizedRepresentation:", o.parsedOperation.NormalizedRepresentation) sum := o.kit.keyGen.Sum64() o.kit.keyGen.Reset() return sum @@ -1005,7 +998,6 @@ func (o *OperationKit) remapVariablesCacheKey() uint64 { func (o *OperationKit) RemapVariables(disabled bool) (cached bool, err error) { cacheKey := o.remapVariablesCacheKey() - // fmt.Println("####### RemapVariables: cacheKey:", cacheKey, "#######") if o.cache != nil && o.cache.remapVariablesCache != nil { entry, ok := o.cache.remapVariablesCache.Get(cacheKey) if ok { From 11e611687f59b165b98bb95a1bb24a760687ad01 Mon Sep 17 00:00:00 2001 From: Yury Smolski <140245+ysmolski@users.noreply.github.com> Date: Wed, 19 Nov 2025 13:11:13 +0200 Subject: [PATCH 06/11] edit comments --- router/core/graphql_prehandler.go | 33 +++++++++++++++--------------- router/core/operation_processor.go | 2 ++ 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/router/core/graphql_prehandler.go b/router/core/graphql_prehandler.go index cf1a99a83f..5a38941b6a 100644 --- a/router/core/graphql_prehandler.go +++ b/router/core/graphql_prehandler.go @@ -783,12 +783,6 @@ func (h *PreHandler) handleOperation(w http.ResponseWriter, req *http.Request, v * Normalize the variables */ - // Normalize the variables returns list of uploads mapping if there are any of them present in a query - // type UploadPathMapping struct { - // VariableName string - is a variable name holding the direct or nested value of type Upload, example "f" - // OriginalUploadPath string - is a path relative to variables which have an Upload type, example "variables.f" - // NewUploadPath string - if variable was used in the inline object like this `arg: {f: $f}` this field will hold the new extracted path, example "variables.a.f", if it is an empty, there was no change in the path - // } cached, uploadsMapping, err := operationKit.NormalizeVariables() if err != nil { rtrace.AttachErrToSpan(engineNormalizeSpan, err) @@ -807,17 +801,19 @@ func (h *PreHandler) handleOperation(w http.ResponseWriter, req *http.Request, v engineNormalizeSpan.SetAttributes(otel.WgVariablesNormalizationCacheHit.Bool(cached)) requestContext.operation.variablesNormalizationCacheHit = cached - // update file uploads path if they were used in nested field in the extracted variables + // Update file upload paths if they were used in the nested field of the extracted variables. for mapping := range slices.Values(uploadsMapping) { - // if the NewUploadPath is empty it means that there was no change in the path - e.g. upload was directly passed to the argument - // e.g. field(fileArgument: $file) will result in []UploadPathMapping{ {VariableName: "file", OriginalUploadPath: "variables.file", NewUploadPath: ""} } + // If the NewUploadPath is empty, there was no change in the path: + // upload was directly passed to the argument. For example, "field(fileArgument: $file)" + // will result in uploadsMapping containing such an item: + // {VariableName: "file", OriginalUploadPath: "variables.file", NewUploadPath: ""} if mapping.NewUploadPath == "" { continue } - // look for the corresponding file which was used in the nested argument - // we are matching original upload path passed via uploads map with the mapping items + // Look for the corresponding file that was used in the nested argument. idx := slices.IndexFunc(requestContext.operation.files, func(file *httpclient.FileUpload) bool { + // Match upload path passed via slice of FileUpload with the mapping items. return file.VariablePath() == mapping.OriginalUploadPath }) @@ -825,15 +821,18 @@ func (h *PreHandler) handleOperation(w http.ResponseWriter, req *http.Request, v continue } - // if NewUploadPath is not empty the file argument was used in the nested object, and we need to update the path - // e.g. field(arg: {file: $file}) normalized to field(arg: $a) will result in []UploadPathMapping{ {VariableName: "file", OriginalUploadPath: "variables.file", NewUploadPath: "variables.a.file"} } - // so "variables.file" should be updated to "variables.a.file" + // If NewUploadPath is not empty, the file argument was used in the nested object, + // and we need to update the path. + // For example, "field(arg: {file: $file})" normalized to "field(arg: $a)" will result in + // uploadsMapping containing such an item: + // {VariableName: "file", OriginalUploadPath: "variables.file", NewUploadPath: "variables.a.file"} + // In short, "variables.file" should be updated to "variables.a.file". requestContext.operation.files[idx].SetVariablePath(uploadsMapping[idx].NewUploadPath) } - // RemapVariables is updating and sort variables name to be able to have them in a predictable order - // after remapping requestContext.operation.remapVariables map will contain new names as a keys and old names as a values - to be able to extract the old values - // because it does not rename variables in a variables json + // requestContext.operation.remapVariables map will contain new names as keys and + // old names as values - to be able to extract the old values. + // It does not rename variables in variables JSON. cached, err = operationKit.RemapVariables(h.disableVariablesRemapping) if err != nil { rtrace.AttachErrToSpan(engineNormalizeSpan, err) diff --git a/router/core/operation_processor.go b/router/core/operation_processor.go index a003ea27ac..7cc135290f 100644 --- a/router/core/operation_processor.go +++ b/router/core/operation_processor.go @@ -886,6 +886,7 @@ func (o *OperationKit) normalizeVariablesCacheKey() uint64 { return sum } +// NormalizeVariables returns a slice of upload mappings if there are any of them present in a query. func (o *OperationKit) NormalizeVariables() (cached bool, mapping []uploads.UploadPathMapping, err error) { cacheKey := o.normalizeVariablesCacheKey() if o.cache != nil && o.cache.variablesNormalizationCache != nil { @@ -996,6 +997,7 @@ func (o *OperationKit) remapVariablesCacheKey() uint64 { return sum } +// RemapVariables updates and sorts variable names to have them in a predictable order. func (o *OperationKit) RemapVariables(disabled bool) (cached bool, err error) { cacheKey := o.remapVariablesCacheKey() if o.cache != nil && o.cache.remapVariablesCache != nil { From ca894b48cd85991ede240766d4eec96f481fd3a7 Mon Sep 17 00:00:00 2001 From: Yury Smolski <140245+ysmolski@users.noreply.github.com> Date: Wed, 19 Nov 2025 13:16:05 +0200 Subject: [PATCH 07/11] fix comments in tests --- router-tests/normalization_cache_test.go | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/router-tests/normalization_cache_test.go b/router-tests/normalization_cache_test.go index a0f9432a83..4cb796bebe 100644 --- a/router-tests/normalization_cache_test.go +++ b/router-tests/normalization_cache_test.go @@ -37,7 +37,7 @@ func assertCacheHeaders(t *testing.T, res *testenv.TestResponse, expected cacheH "Variables remapping cache hit mismatch") } -func TestAdditionalNormalizationCaches(t *testing.T) { +func TestVarsNormalizationRemappingCaches(t *testing.T) { t.Parallel() t.Run("Basic normalization cache with skip/include", func(t *testing.T) { @@ -53,15 +53,10 @@ func TestAdditionalNormalizationCaches(t *testing.T) { require.Equal(t, `{"data":{"employee":{"details":{"pets":[{"name":"Abby","__typename":"Dog","breed":"GOLDEN_RETRIEVER","class":"MAMMAL","gender":"FEMALE"},{"name":"Survivor","__typename":"Pony"}]}}}}`, res.Body) } - // First request: all caches miss f(cacheHit{false, false, false}, true) - // Second request: all caches hit f(cacheHit{true, true, true}, true) - // Third request: all caches hit f(cacheHit{true, true, true}, true) - // Fourth request: different skip/include value, all caches miss f(cacheHit{false, false, false}, false) - // Fifth request: back to original skip/include value, all caches hit f(cacheHit{true, true, true}, true) }) }) @@ -69,19 +64,19 @@ func TestAdditionalNormalizationCaches(t *testing.T) { t.Run("Variables normalization cache - inline value extraction", func(t *testing.T) { t.Parallel() testenv.Run(t, &testenv.Config{}, func(t *testing.T, xEnv *testenv.Environment) { - // Test 1: Inline value gets extracted to variable - all caches miss + // Inline value gets extracted to variable res := xEnv.MakeGraphQLRequestOK(testenv.GraphQLRequest{ Query: `query { employee(id: 1) { id details { forename } } }`, }) assertCacheHeaders(t, res, cacheHit{false, false, false}) - // Test 2: Same query - all caches hit + // Same query res = xEnv.MakeGraphQLRequestOK(testenv.GraphQLRequest{ Query: `query { employee(id: 1) { id details { forename } } }`, }) assertCacheHeaders(t, res, cacheHit{true, true, true}) - // Test 3: Different inline value + // Different inline value res = xEnv.MakeGraphQLRequestOK(testenv.GraphQLRequest{ Query: `query { employee(id: 2) { id details { forename } } }`, }) @@ -92,7 +87,7 @@ func TestAdditionalNormalizationCaches(t *testing.T) { t.Run("Variables normalization cache - query changes, but variables stay the same", func(t *testing.T) { t.Parallel() testenv.Run(t, &testenv.Config{}, func(t *testing.T, xEnv *testenv.Environment) { - // Test with unused variables that should be removed - all caches miss + // Test with unused variables that should be removed res := xEnv.MakeGraphQLRequestOK(testenv.GraphQLRequest{ Query: `query MyQuery($id: Int!) { employee(id: $id) { id } }`, Variables: []byte(`{"id": 1}`), @@ -100,7 +95,7 @@ func TestAdditionalNormalizationCaches(t *testing.T) { require.Equal(t, `{"data":{"employee":{"id":1}}}`, res.Body) assertCacheHeaders(t, res, cacheHit{false, false, false}) - // Different query with same variable value. + // Different query with the same variable value. res = xEnv.MakeGraphQLRequestOK(testenv.GraphQLRequest{ Query: `query MyQuery($id: Int!) { employee(id: $id) { id details { forename }} }`, Variables: []byte(`{"id": 1}`), @@ -112,21 +107,21 @@ func TestAdditionalNormalizationCaches(t *testing.T) { t.Run("Cache key isolation - different operations don't collide", func(t *testing.T) { testenv.Run(t, &testenv.Config{}, func(t *testing.T, xEnv *testenv.Environment) { - // Test 1: Query A + // Query A res := xEnv.MakeGraphQLRequestOK(testenv.GraphQLRequest{ Query: `query A($id: Int!) { employee(id: $id) { id } }`, Variables: []byte(`{"id": 1}`), }) assertCacheHeaders(t, res, cacheHit{false, false, false}) - // Test 2: Query B with different structure should miss + // Query B with different structure should miss res = xEnv.MakeGraphQLRequestOK(testenv.GraphQLRequest{ Query: `query B($id: Int!) { employee(id: $id) { id details { forename } } }`, Variables: []byte(`{"id": 1}`), }) assertCacheHeaders(t, res, cacheHit{false, false, false}) - // Test 3: Query A again should hit its own cache + // Query A again should hit its own cache res = xEnv.MakeGraphQLRequestOK(testenv.GraphQLRequest{ Query: `query A($id: Int!) { employee(id: $id) { id } }`, Variables: []byte(`{"id": 1}`), From 47893d83fec8148dfa7f51c304760b8dbec1adba Mon Sep 17 00:00:00 2001 From: Yury Smolski <140245+ysmolski@users.noreply.github.com> Date: Wed, 19 Nov 2025 13:27:04 +0200 Subject: [PATCH 08/11] remove unneeded keygen reset --- router-tests/normalization_cache_test.go | 8 ++++++-- router/core/operation_processor.go | 4 ---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/router-tests/normalization_cache_test.go b/router-tests/normalization_cache_test.go index 4cb796bebe..efa9fd7680 100644 --- a/router-tests/normalization_cache_test.go +++ b/router-tests/normalization_cache_test.go @@ -12,7 +12,7 @@ import ( ) // cacheHit represents the expected cache hit/miss status for all three normalization stages. -// True values mean the cache hit. +// True value means the cache was hit. type cacheHit struct { normalization bool variables bool @@ -87,7 +87,6 @@ func TestVarsNormalizationRemappingCaches(t *testing.T) { t.Run("Variables normalization cache - query changes, but variables stay the same", func(t *testing.T) { t.Parallel() testenv.Run(t, &testenv.Config{}, func(t *testing.T, xEnv *testenv.Environment) { - // Test with unused variables that should be removed res := xEnv.MakeGraphQLRequestOK(testenv.GraphQLRequest{ Query: `query MyQuery($id: Int!) { employee(id: $id) { id } }`, Variables: []byte(`{"id": 1}`), @@ -106,6 +105,7 @@ func TestVarsNormalizationRemappingCaches(t *testing.T) { }) t.Run("Cache key isolation - different operations don't collide", func(t *testing.T) { + t.Parallel() testenv.Run(t, &testenv.Config{}, func(t *testing.T, xEnv *testenv.Environment) { // Query A res := xEnv.MakeGraphQLRequestOK(testenv.GraphQLRequest{ @@ -131,6 +131,7 @@ func TestVarsNormalizationRemappingCaches(t *testing.T) { }) t.Run("List coercion with variables normalization cache", func(t *testing.T) { + t.Parallel() testenv.Run(t, &testenv.Config{}, func(t *testing.T, xEnv *testenv.Environment) { // Test that list coercion works correctly with caching res := xEnv.MakeGraphQLRequestOK(testenv.GraphQLRequest{ @@ -146,6 +147,9 @@ func TestVarsNormalizationRemappingCaches(t *testing.T) { Variables: []byte(`{"arg": "different"}`), }) require.Equal(t, `{"data":{"rootFieldWithListArg":["different"]}}`, res.Body) + // Normalization hits because the query structure is unchanged, + // variables misses because the value differs, + // and remapping hits because the structure remains the same. assertCacheHeaders(t, res, cacheHit{true, false, true}) }) }) diff --git a/router/core/operation_processor.go b/router/core/operation_processor.go index 7cc135290f..51410c359d 100644 --- a/router/core/operation_processor.go +++ b/router/core/operation_processor.go @@ -941,12 +941,10 @@ func (o *OperationKit) NormalizeVariables() (cached bool, mapping []uploads.Uplo // Reset the doc with the original name o.kit.doc.OperationDefinitions[o.operationDefinitionRef].Name = nameRef - o.kit.keyGen.Reset() // should not be needed if we properly reset after use - check do we have any remaining places where we do not reset keygen - maybe wrap into a type which will reset once we got key _, err = o.kit.keyGen.Write(o.kit.normalizedOperation.Bytes()) if err != nil { return false, nil, err } - o.parsedOperation.ID = o.kit.keyGen.Sum64() o.kit.keyGen.Reset() @@ -1044,12 +1042,10 @@ func (o *OperationKit) RemapVariables(disabled bool) (cached bool, err error) { // Reset the doc with the original name o.kit.doc.OperationDefinitions[o.operationDefinitionRef].Name = nameRef - o.kit.keyGen.Reset() _, err = o.kit.keyGen.Write(o.kit.normalizedOperation.Bytes()) if err != nil { return false, err } - // Generate the operation ID o.parsedOperation.InternalID = o.kit.keyGen.Sum64() o.kit.keyGen.Reset() From 2be06a9465cfe7385c941ebf76d3563b47294509 Mon Sep 17 00:00:00 2001 From: Yury Smolski <140245+ysmolski@users.noreply.github.com> Date: Wed, 19 Nov 2025 15:12:31 +0200 Subject: [PATCH 09/11] add new caches to metricInfos --- router/core/graph_server.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/router/core/graph_server.go b/router/core/graph_server.go index e0930a7315..348a0d00de 100644 --- a/router/core/graph_server.go +++ b/router/core/graph_server.go @@ -709,6 +709,14 @@ func (s *graphMux) configureCacheMetrics(srv *graphServer, baseOtelAttributes [] metricInfos = append(metricInfos, rmetric.NewCacheMetricInfo("query_normalization", srv.engineExecutionConfiguration.NormalizationCacheSize, s.normalizationCache.Metrics)) } + if s.variablesNormalizationCache != nil { + metricInfos = append(metricInfos, rmetric.NewCacheMetricInfo("variables_normalization", srv.engineExecutionConfiguration.NormalizationCacheSize, s.variablesNormalizationCache.Metrics)) + } + + if s.remapVariablesCache != nil { + metricInfos = append(metricInfos, rmetric.NewCacheMetricInfo("remap_variables", srv.engineExecutionConfiguration.NormalizationCacheSize, s.remapVariablesCache.Metrics)) + } + if s.persistedOperationCache != nil { metricInfos = append(metricInfos, rmetric.NewCacheMetricInfo("persisted_query_normalization", 1024, s.persistedOperationCache.Metrics)) } @@ -1237,11 +1245,11 @@ func (s *graphServer) buildGraphMux( EnablePersistedOperationsCache: s.engineExecutionConfiguration.EnablePersistedOperationsCache, PersistedOpsNormalizationCache: gm.persistedOperationCache, NormalizationCache: gm.normalizationCache, + VariablesNormalizationCache: gm.variablesNormalizationCache, + RemapVariablesCache: gm.remapVariablesCache, ValidationCache: gm.validationCache, QueryDepthCache: gm.complexityCalculationCache, OperationHashCache: gm.operationHashCache, - VariablesNormalizationCache: gm.variablesNormalizationCache, - RemapVariablesCache: gm.remapVariablesCache, ParseKitPoolSize: s.engineExecutionConfiguration.ParseKitPoolSize, IntrospectionEnabled: s.Config.introspection, ParserTokenizerLimits: astparser.TokenizerLimits{ From 9e0a7ac453bedf4319034a1990e9c3c4604ee07e Mon Sep 17 00:00:00 2001 From: Yury Smolski <140245+ysmolski@users.noreply.github.com> Date: Wed, 19 Nov 2025 18:36:40 +0200 Subject: [PATCH 10/11] fix telemetry tests --- router-tests/telemetry/telemetry_test.go | 816 ++++++++++++++++++++++- 1 file changed, 806 insertions(+), 10 deletions(-) diff --git a/router-tests/telemetry/telemetry_test.go b/router-tests/telemetry/telemetry_test.go index 7ebe7cc1fa..a3453b07cc 100644 --- a/router-tests/telemetry/telemetry_test.go +++ b/router-tests/telemetry/telemetry_test.go @@ -578,6 +578,38 @@ func TestFlakyOperationCacheTelemetry(t *testing.T) { )...), Value: 2, }, + { + Attributes: attribute.NewSet(append( + baseAttributes, + attribute.String("cache_type", "variables_normalization"), + attribute.String("type", "hits"), + )...), + Value: 1, + }, + { + Attributes: attribute.NewSet(append( + baseAttributes, + attribute.String("cache_type", "variables_normalization"), + attribute.String("type", "misses"), + )...), + Value: 2, + }, + { + Attributes: attribute.NewSet(append( + baseAttributes, + attribute.String("cache_type", "remap_variables"), + attribute.String("type", "hits"), + )...), + Value: 1, + }, + { + Attributes: attribute.NewSet(append( + baseAttributes, + attribute.String("cache_type", "remap_variables"), + attribute.String("type", "misses"), + )...), + Value: 2, + }, { Attributes: attribute.NewSet(append( baseAttributes, @@ -669,6 +701,52 @@ func TestFlakyOperationCacheTelemetry(t *testing.T) { )...), Value: 0, }, + { + Attributes: attribute.NewSet(append(baseAttributes, + attribute.String("cache_type", "variables_normalization"), + attribute.String("operation", "added"), + )...), + Value: 2, + }, + { + Attributes: attribute.NewSet(append( + baseAttributes, + attribute.String("cache_type", "variables_normalization"), + attribute.String("operation", "evicted"), + )...), + Value: 0, + }, + { + Attributes: attribute.NewSet(append( + baseAttributes, + attribute.String("cache_type", "variables_normalization"), + attribute.String("operation", "updated"), + )...), + Value: 0, + }, + { + Attributes: attribute.NewSet(append(baseAttributes, + attribute.String("cache_type", "remap_variables"), + attribute.String("operation", "added"), + )...), + Value: 2, + }, + { + Attributes: attribute.NewSet(append( + baseAttributes, + attribute.String("cache_type", "remap_variables"), + attribute.String("operation", "evicted"), + )...), + Value: 0, + }, + { + Attributes: attribute.NewSet(append( + baseAttributes, + attribute.String("cache_type", "remap_variables"), + attribute.String("operation", "updated"), + )...), + Value: 0, + }, { Attributes: attribute.NewSet(append(baseAttributes, attribute.String("cache_type", "validation"), @@ -758,6 +836,36 @@ func TestFlakyOperationCacheTelemetry(t *testing.T) { )...), Value: 0, }, + { + Attributes: attribute.NewSet(append(baseAttributes, + attribute.String("cache_type", "variables_normalization"), + attribute.String("operation", "added"), + )...), + Value: baseCost * 2, + }, + { + Attributes: attribute.NewSet(append( + baseAttributes, + attribute.String("cache_type", "variables_normalization"), + attribute.String("operation", "evicted"), + )...), + Value: 0, + }, + { + Attributes: attribute.NewSet(append(baseAttributes, + attribute.String("cache_type", "remap_variables"), + attribute.String("operation", "added"), + )...), + Value: baseCost * 2, + }, + { + Attributes: attribute.NewSet(append( + baseAttributes, + attribute.String("cache_type", "remap_variables"), + attribute.String("operation", "evicted"), + )...), + Value: 0, + }, { Attributes: attribute.NewSet(append(baseAttributes, attribute.String("cache_type", "validation"), @@ -812,6 +920,20 @@ func TestFlakyOperationCacheTelemetry(t *testing.T) { )...), Value: 1024, }, + { + Attributes: attribute.NewSet(append( + baseAttributes, + attribute.String("cache_type", "variables_normalization"), + )...), + Value: 1024, + }, + { + Attributes: attribute.NewSet(append( + baseAttributes, + attribute.String("cache_type", "remap_variables"), + )...), + Value: 1024, + }, { Attributes: attribute.NewSet(append(baseAttributes, attribute.String("cache_type", "validation"), @@ -956,6 +1078,38 @@ func TestFlakyOperationCacheTelemetry(t *testing.T) { )...), Value: 2, }, + { + Attributes: attribute.NewSet(append( + baseAttributes, + attribute.String("cache_type", "variables_normalization"), + attribute.String("type", "hits"), + )...), + Value: 2, + }, + { + Attributes: attribute.NewSet(append( + baseAttributes, + attribute.String("cache_type", "variables_normalization"), + attribute.String("type", "misses"), + )...), + Value: 4, + }, + { + Attributes: attribute.NewSet(append( + baseAttributes, + attribute.String("cache_type", "remap_variables"), + attribute.String("type", "hits"), + )...), + Value: 2, + }, + { + Attributes: attribute.NewSet(append( + baseAttributes, + attribute.String("cache_type", "remap_variables"), + attribute.String("type", "misses"), + )...), + Value: 4, + }, { Attributes: attribute.NewSet(append( baseAttributes, @@ -1047,6 +1201,52 @@ func TestFlakyOperationCacheTelemetry(t *testing.T) { )...), Value: 0, }, + { + Attributes: attribute.NewSet(append(baseAttributes, + attribute.String("cache_type", "variables_normalization"), + attribute.String("operation", "added"), + )...), + Value: 4, + }, + { + Attributes: attribute.NewSet(append( + baseAttributes, + attribute.String("cache_type", "variables_normalization"), + attribute.String("operation", "evicted"), + )...), + Value: 0, + }, + { + Attributes: attribute.NewSet(append( + baseAttributes, + attribute.String("cache_type", "variables_normalization"), + attribute.String("operation", "updated"), + )...), + Value: 0, + }, + { + Attributes: attribute.NewSet(append(baseAttributes, + attribute.String("cache_type", "remap_variables"), + attribute.String("operation", "added"), + )...), + Value: 4, + }, + { + Attributes: attribute.NewSet(append( + baseAttributes, + attribute.String("cache_type", "remap_variables"), + attribute.String("operation", "evicted"), + )...), + Value: 0, + }, + { + Attributes: attribute.NewSet(append( + baseAttributes, + attribute.String("cache_type", "remap_variables"), + attribute.String("operation", "updated"), + )...), + Value: 0, + }, { Attributes: attribute.NewSet(append(baseAttributes, attribute.String("cache_type", "validation"), @@ -1136,6 +1336,36 @@ func TestFlakyOperationCacheTelemetry(t *testing.T) { )...), Value: 0, }, + { + Attributes: attribute.NewSet(append(baseAttributes, + attribute.String("cache_type", "variables_normalization"), + attribute.String("operation", "added"), + )...), + Value: baseCost * 4, + }, + { + Attributes: attribute.NewSet(append( + baseAttributes, + attribute.String("cache_type", "variables_normalization"), + attribute.String("operation", "evicted"), + )...), + Value: 0, + }, + { + Attributes: attribute.NewSet(append(baseAttributes, + attribute.String("cache_type", "remap_variables"), + attribute.String("operation", "added"), + )...), + Value: baseCost * 4, + }, + { + Attributes: attribute.NewSet(append( + baseAttributes, + attribute.String("cache_type", "remap_variables"), + attribute.String("operation", "evicted"), + )...), + Value: 0, + }, { Attributes: attribute.NewSet(append(baseAttributes, attribute.String("cache_type", "validation"), @@ -1190,6 +1420,20 @@ func TestFlakyOperationCacheTelemetry(t *testing.T) { )...), Value: 1024, }, + { + Attributes: attribute.NewSet(append( + baseAttributes, + attribute.String("cache_type", "variables_normalization"), + )...), + Value: 1024, + }, + { + Attributes: attribute.NewSet(append( + baseAttributes, + attribute.String("cache_type", "remap_variables"), + )...), + Value: 1024, + }, { Attributes: attribute.NewSet(append(baseAttributes, attribute.String("cache_type", "validation"), @@ -1298,6 +1542,38 @@ func TestFlakyOperationCacheTelemetry(t *testing.T) { )...), Value: 2, }, + { + Attributes: attribute.NewSet(append( + baseAttributes, + attribute.String("cache_type", "variables_normalization"), + attribute.String("type", "hits"), + )...), + Value: 1, + }, + { + Attributes: attribute.NewSet(append( + baseAttributes, + attribute.String("cache_type", "variables_normalization"), + attribute.String("type", "misses"), + )...), + Value: 2, + }, + { + Attributes: attribute.NewSet(append( + baseAttributes, + attribute.String("cache_type", "remap_variables"), + attribute.String("type", "hits"), + )...), + Value: 1, + }, + { + Attributes: attribute.NewSet(append( + baseAttributes, + attribute.String("cache_type", "remap_variables"), + attribute.String("type", "misses"), + )...), + Value: 2, + }, { Attributes: attribute.NewSet(append( baseAttributes, @@ -1389,6 +1665,52 @@ func TestFlakyOperationCacheTelemetry(t *testing.T) { )...), Value: 0, }, + { + Attributes: attribute.NewSet(append(baseAttributes, + attribute.String("cache_type", "variables_normalization"), + attribute.String("operation", "added"), + )...), + Value: 2, + }, + { + Attributes: attribute.NewSet(append( + baseAttributes, + attribute.String("cache_type", "variables_normalization"), + attribute.String("operation", "evicted"), + )...), + Value: 0, + }, + { + Attributes: attribute.NewSet(append( + baseAttributes, + attribute.String("cache_type", "variables_normalization"), + attribute.String("operation", "updated"), + )...), + Value: 0, + }, + { + Attributes: attribute.NewSet(append(baseAttributes, + attribute.String("cache_type", "remap_variables"), + attribute.String("operation", "added"), + )...), + Value: 2, + }, + { + Attributes: attribute.NewSet(append( + baseAttributes, + attribute.String("cache_type", "remap_variables"), + attribute.String("operation", "evicted"), + )...), + Value: 0, + }, + { + Attributes: attribute.NewSet(append( + baseAttributes, + attribute.String("cache_type", "remap_variables"), + attribute.String("operation", "updated"), + )...), + Value: 0, + }, { Attributes: attribute.NewSet(append(baseAttributes, attribute.String("cache_type", "validation"), @@ -1478,6 +1800,36 @@ func TestFlakyOperationCacheTelemetry(t *testing.T) { )...), Value: 0, }, + { + Attributes: attribute.NewSet(append(baseAttributes, + attribute.String("cache_type", "variables_normalization"), + attribute.String("operation", "added"), + )...), + Value: baseCost * 2, + }, + { + Attributes: attribute.NewSet(append( + baseAttributes, + attribute.String("cache_type", "variables_normalization"), + attribute.String("operation", "evicted"), + )...), + Value: 0, + }, + { + Attributes: attribute.NewSet(append(baseAttributes, + attribute.String("cache_type", "remap_variables"), + attribute.String("operation", "added"), + )...), + Value: baseCost * 2, + }, + { + Attributes: attribute.NewSet(append( + baseAttributes, + attribute.String("cache_type", "remap_variables"), + attribute.String("operation", "evicted"), + )...), + Value: 0, + }, { Attributes: attribute.NewSet(append(baseAttributes, attribute.String("cache_type", "validation"), @@ -1532,6 +1884,20 @@ func TestFlakyOperationCacheTelemetry(t *testing.T) { )...), Value: 1024, }, + { + Attributes: attribute.NewSet(append( + baseAttributes, + attribute.String("cache_type", "variables_normalization"), + )...), + Value: 1024, + }, + { + Attributes: attribute.NewSet(append( + baseAttributes, + attribute.String("cache_type", "remap_variables"), + )...), + Value: 1024, + }, { Attributes: attribute.NewSet(append(baseAttributes, attribute.String("cache_type", "validation"), @@ -1642,6 +2008,38 @@ func TestFlakyOperationCacheTelemetry(t *testing.T) { )...), Value: 2, }, + { + Attributes: attribute.NewSet(append( + baseAttributes, + attribute.String("cache_type", "variables_normalization"), + attribute.String("type", "hits"), + )...), + Value: 1, + }, + { + Attributes: attribute.NewSet(append( + baseAttributes, + attribute.String("cache_type", "variables_normalization"), + attribute.String("type", "misses"), + )...), + Value: 2, + }, + { + Attributes: attribute.NewSet(append( + baseAttributes, + attribute.String("cache_type", "remap_variables"), + attribute.String("type", "hits"), + )...), + Value: 1, + }, + { + Attributes: attribute.NewSet(append( + baseAttributes, + attribute.String("cache_type", "remap_variables"), + attribute.String("type", "misses"), + )...), + Value: 2, + }, { Attributes: attribute.NewSet(append( baseAttributes, @@ -1733,6 +2131,52 @@ func TestFlakyOperationCacheTelemetry(t *testing.T) { )...), Value: 0, }, + { + Attributes: attribute.NewSet(append(baseAttributes, + attribute.String("cache_type", "variables_normalization"), + attribute.String("operation", "added"), + )...), + Value: 2, + }, + { + Attributes: attribute.NewSet(append( + baseAttributes, + attribute.String("cache_type", "variables_normalization"), + attribute.String("operation", "evicted"), + )...), + Value: 0, + }, + { + Attributes: attribute.NewSet(append( + baseAttributes, + attribute.String("cache_type", "variables_normalization"), + attribute.String("operation", "updated"), + )...), + Value: 0, + }, + { + Attributes: attribute.NewSet(append(baseAttributes, + attribute.String("cache_type", "remap_variables"), + attribute.String("operation", "added"), + )...), + Value: 2, + }, + { + Attributes: attribute.NewSet(append( + baseAttributes, + attribute.String("cache_type", "remap_variables"), + attribute.String("operation", "evicted"), + )...), + Value: 0, + }, + { + Attributes: attribute.NewSet(append( + baseAttributes, + attribute.String("cache_type", "remap_variables"), + attribute.String("operation", "updated"), + )...), + Value: 0, + }, { Attributes: attribute.NewSet(append(baseAttributes, attribute.String("cache_type", "validation"), @@ -1822,6 +2266,36 @@ func TestFlakyOperationCacheTelemetry(t *testing.T) { )...), Value: 0, }, + { + Attributes: attribute.NewSet(append(baseAttributes, + attribute.String("cache_type", "variables_normalization"), + attribute.String("operation", "added"), + )...), + Value: baseCost * 2, + }, + { + Attributes: attribute.NewSet(append( + baseAttributes, + attribute.String("cache_type", "variables_normalization"), + attribute.String("operation", "evicted"), + )...), + Value: 0, + }, + { + Attributes: attribute.NewSet(append(baseAttributes, + attribute.String("cache_type", "remap_variables"), + attribute.String("operation", "added"), + )...), + Value: baseCost * 2, + }, + { + Attributes: attribute.NewSet(append( + baseAttributes, + attribute.String("cache_type", "remap_variables"), + attribute.String("operation", "evicted"), + )...), + Value: 0, + }, { Attributes: attribute.NewSet(append(baseAttributes, attribute.String("cache_type", "validation"), @@ -1876,6 +2350,20 @@ func TestFlakyOperationCacheTelemetry(t *testing.T) { )...), Value: 1024, }, + { + Attributes: attribute.NewSet(append( + baseAttributes, + attribute.String("cache_type", "variables_normalization"), + )...), + Value: 1024, + }, + { + Attributes: attribute.NewSet(append( + baseAttributes, + attribute.String("cache_type", "remap_variables"), + )...), + Value: 1024, + }, { Attributes: attribute.NewSet(append(baseAttributes, attribute.String("cache_type", "validation"), @@ -1987,14 +2475,46 @@ func TestFlakyOperationCacheTelemetry(t *testing.T) { { Attributes: attribute.NewSet(append( mainAttributes, - attribute.String("cache_type", "plan"), + attribute.String("cache_type", "plan"), + attribute.String("type", "hits"), + )...), + Value: 1, + }, + { + Attributes: attribute.NewSet(append(mainAttributes, + attribute.String("cache_type", "plan"), + attribute.String("type", "misses"), + )...), + Value: 2, + }, + { + Attributes: attribute.NewSet(append( + mainAttributes, + attribute.String("cache_type", "query_normalization"), + attribute.String("type", "hits"), + )...), + Value: 1, + }, + { + Attributes: attribute.NewSet(append( + mainAttributes, + attribute.String("cache_type", "query_normalization"), + attribute.String("type", "misses"), + )...), + Value: 2, + }, + { + Attributes: attribute.NewSet(append( + mainAttributes, + attribute.String("cache_type", "variables_normalization"), attribute.String("type", "hits"), )...), Value: 1, }, { - Attributes: attribute.NewSet(append(mainAttributes, - attribute.String("cache_type", "plan"), + Attributes: attribute.NewSet(append( + mainAttributes, + attribute.String("cache_type", "variables_normalization"), attribute.String("type", "misses"), )...), Value: 2, @@ -2002,7 +2522,7 @@ func TestFlakyOperationCacheTelemetry(t *testing.T) { { Attributes: attribute.NewSet(append( mainAttributes, - attribute.String("cache_type", "query_normalization"), + attribute.String("cache_type", "remap_variables"), attribute.String("type", "hits"), )...), Value: 1, @@ -2010,7 +2530,7 @@ func TestFlakyOperationCacheTelemetry(t *testing.T) { { Attributes: attribute.NewSet(append( mainAttributes, - attribute.String("cache_type", "query_normalization"), + attribute.String("cache_type", "remap_variables"), attribute.String("type", "misses"), )...), Value: 2, @@ -2080,6 +2600,38 @@ func TestFlakyOperationCacheTelemetry(t *testing.T) { )...), Value: 2, }, + { + Attributes: attribute.NewSet(append( + featureFlagAttributes, + attribute.String("cache_type", "variables_normalization"), + attribute.String("type", "hits"), + )...), + Value: 1, + }, + { + Attributes: attribute.NewSet(append( + featureFlagAttributes, + attribute.String("cache_type", "variables_normalization"), + attribute.String("type", "misses"), + )...), + Value: 2, + }, + { + Attributes: attribute.NewSet(append( + featureFlagAttributes, + attribute.String("cache_type", "remap_variables"), + attribute.String("type", "hits"), + )...), + Value: 1, + }, + { + Attributes: attribute.NewSet(append( + featureFlagAttributes, + attribute.String("cache_type", "remap_variables"), + attribute.String("type", "misses"), + )...), + Value: 2, + }, { Attributes: attribute.NewSet(append( featureFlagAttributes, @@ -2173,6 +2725,54 @@ func TestFlakyOperationCacheTelemetry(t *testing.T) { )...), Value: 0, }, + { + Attributes: attribute.NewSet(append( + mainAttributes, + attribute.String("cache_type", "variables_normalization"), + attribute.String("operation", "added"), + )...), + Value: 2, + }, + { + Attributes: attribute.NewSet(append( + mainAttributes, + attribute.String("cache_type", "variables_normalization"), + attribute.String("operation", "evicted"), + )...), + Value: 0, + }, + { + Attributes: attribute.NewSet(append( + mainAttributes, + attribute.String("cache_type", "variables_normalization"), + attribute.String("operation", "updated"), + )...), + Value: 0, + }, + { + Attributes: attribute.NewSet(append( + mainAttributes, + attribute.String("cache_type", "remap_variables"), + attribute.String("operation", "added"), + )...), + Value: 2, + }, + { + Attributes: attribute.NewSet(append( + mainAttributes, + attribute.String("cache_type", "remap_variables"), + attribute.String("operation", "evicted"), + )...), + Value: 0, + }, + { + Attributes: attribute.NewSet(append( + mainAttributes, + attribute.String("cache_type", "remap_variables"), + attribute.String("operation", "updated"), + )...), + Value: 0, + }, { Attributes: attribute.NewSet(append( mainAttributes, @@ -2271,6 +2871,54 @@ func TestFlakyOperationCacheTelemetry(t *testing.T) { )...), Value: 0, }, + { + Attributes: attribute.NewSet(append( + featureFlagAttributes, + attribute.String("cache_type", "variables_normalization"), + attribute.String("operation", "added"), + )...), + Value: 2, + }, + { + Attributes: attribute.NewSet(append( + featureFlagAttributes, + attribute.String("cache_type", "variables_normalization"), + attribute.String("operation", "evicted"), + )...), + Value: 0, + }, + { + Attributes: attribute.NewSet(append( + featureFlagAttributes, + attribute.String("cache_type", "variables_normalization"), + attribute.String("operation", "updated"), + )...), + Value: 0, + }, + { + Attributes: attribute.NewSet(append( + featureFlagAttributes, + attribute.String("cache_type", "remap_variables"), + attribute.String("operation", "added"), + )...), + Value: 2, + }, + { + Attributes: attribute.NewSet(append( + featureFlagAttributes, + attribute.String("cache_type", "remap_variables"), + attribute.String("operation", "evicted"), + )...), + Value: 0, + }, + { + Attributes: attribute.NewSet(append( + featureFlagAttributes, + attribute.String("cache_type", "remap_variables"), + attribute.String("operation", "updated"), + )...), + Value: 0, + }, { Attributes: attribute.NewSet(append( featureFlagAttributes, @@ -2364,6 +3012,38 @@ func TestFlakyOperationCacheTelemetry(t *testing.T) { )...), Value: 0, }, + { + Attributes: attribute.NewSet(append( + mainAttributes, + attribute.String("cache_type", "variables_normalization"), + attribute.String("operation", "added"), + )...), + Value: baseCost * 2, + }, + { + Attributes: attribute.NewSet(append( + mainAttributes, + attribute.String("cache_type", "variables_normalization"), + attribute.String("operation", "evicted"), + )...), + Value: 0, + }, + { + Attributes: attribute.NewSet(append( + mainAttributes, + attribute.String("cache_type", "remap_variables"), + attribute.String("operation", "added"), + )...), + Value: baseCost * 2, + }, + { + Attributes: attribute.NewSet(append( + mainAttributes, + attribute.String("cache_type", "remap_variables"), + attribute.String("operation", "evicted"), + )...), + Value: 0, + }, { Attributes: attribute.NewSet(append( mainAttributes, @@ -2429,6 +3109,38 @@ func TestFlakyOperationCacheTelemetry(t *testing.T) { )...), Value: 0, }, + { + Attributes: attribute.NewSet(append( + featureFlagAttributes, + attribute.String("cache_type", "variables_normalization"), + attribute.String("operation", "added"), + )...), + Value: baseCost * 2, + }, + { + Attributes: attribute.NewSet(append( + featureFlagAttributes, + attribute.String("cache_type", "variables_normalization"), + attribute.String("operation", "evicted"), + )...), + Value: 0, + }, + { + Attributes: attribute.NewSet(append( + featureFlagAttributes, + attribute.String("cache_type", "remap_variables"), + attribute.String("operation", "added"), + )...), + Value: baseCost * 2, + }, + { + Attributes: attribute.NewSet(append( + featureFlagAttributes, + attribute.String("cache_type", "remap_variables"), + attribute.String("operation", "evicted"), + )...), + Value: 0, + }, { Attributes: attribute.NewSet(append( featureFlagAttributes, @@ -2486,6 +3198,20 @@ func TestFlakyOperationCacheTelemetry(t *testing.T) { )...), Value: 1024, }, + { + Attributes: attribute.NewSet(append( + mainAttributes, + attribute.String("cache_type", "variables_normalization"), + )...), + Value: 1024, + }, + { + Attributes: attribute.NewSet(append( + mainAttributes, + attribute.String("cache_type", "remap_variables"), + )...), + Value: 1024, + }, { Attributes: attribute.NewSet(append( mainAttributes, @@ -2515,6 +3241,20 @@ func TestFlakyOperationCacheTelemetry(t *testing.T) { )...), Value: 1024, }, + { + Attributes: attribute.NewSet(append( + featureFlagAttributes, + attribute.String("cache_type", "variables_normalization"), + )...), + Value: 1024, + }, + { + Attributes: attribute.NewSet(append( + featureFlagAttributes, + attribute.String("cache_type", "remap_variables"), + )...), + Value: 1024, + }, { Attributes: attribute.NewSet(append( featureFlagAttributes, @@ -3015,7 +3755,7 @@ func TestFlakyTelemetry(t *testing.T) { // Span attributes - require.Len(t, sn[2].Attributes(), 11) + require.Len(t, sn[2].Attributes(), 13) require.Contains(t, sn[2].Attributes(), otel.WgRouterVersion.String("dev")) require.Contains(t, sn[2].Attributes(), otel.WgRouterClusterName.String("")) @@ -3737,7 +4477,7 @@ func TestFlakyTelemetry(t *testing.T) { // Span attributes - require.Len(t, sn[2].Attributes(), 11) + require.Len(t, sn[2].Attributes(), 13) require.Contains(t, sn[2].Attributes(), otel.WgRouterVersion.String("dev")) require.Contains(t, sn[2].Attributes(), otel.WgRouterClusterName.String("")) @@ -4192,7 +4932,7 @@ func TestFlakyTelemetry(t *testing.T) { // Span attributes - require.Len(t, sn[2].Attributes(), 11) + require.Len(t, sn[2].Attributes(), 13) require.Contains(t, sn[2].Attributes(), otel.WgRouterVersion.String("dev")) require.Contains(t, sn[2].Attributes(), otel.WgRouterClusterName.String("")) @@ -6354,7 +7094,7 @@ func TestFlakyTelemetry(t *testing.T) { require.Contains(t, sn[1].Attributes(), otel.WgFeatureFlag.String("myff")) require.Equal(t, "Operation - Normalize", sn[2].Name()) - require.Len(t, sn[2].Attributes(), 12) + require.Len(t, sn[2].Attributes(), 14) require.Contains(t, sn[2].Attributes(), otel.WgRouterConfigVersion.String(xEnv.RouterConfigVersionMyFF())) require.Contains(t, sn[2].Attributes(), otel.WgFeatureFlag.String("myff")) @@ -8811,7 +9551,7 @@ func TestFlakyTelemetry(t *testing.T) { require.Equal(t, "Operation - Normalize", sn[2].Name()) require.Len(t, sn[2].Resource().Attributes(), 9) - require.Len(t, sn[2].Attributes(), 11) + require.Len(t, sn[2].Attributes(), 13) require.Equal(t, "Operation - Validate", sn[3].Name()) require.Len(t, sn[3].Resource().Attributes(), 9) @@ -10961,6 +11701,34 @@ func TestExcludeAttributesWithCustomExporter(t *testing.T) { attribute.String("type", "misses"), )...), }, + { + Attributes: attribute.NewSet(append( + mainAttributes, + attribute.String("cache_type", "variables_normalization"), + attribute.String("type", "hits"), + )...), + }, + { + Attributes: attribute.NewSet(append( + mainAttributes, + attribute.String("cache_type", "variables_normalization"), + attribute.String("type", "misses"), + )...), + }, + { + Attributes: attribute.NewSet(append( + mainAttributes, + attribute.String("cache_type", "remap_variables"), + attribute.String("type", "hits"), + )...), + }, + { + Attributes: attribute.NewSet(append( + mainAttributes, + attribute.String("cache_type", "remap_variables"), + attribute.String("type", "misses"), + )...), + }, { Attributes: attribute.NewSet(append( mainAttributes, @@ -11018,6 +11786,34 @@ func TestExcludeAttributesWithCustomExporter(t *testing.T) { attribute.String("type", "misses"), )...), }, + { + Attributes: attribute.NewSet(append( + featureFlagAttributes, + attribute.String("cache_type", "variables_normalization"), + attribute.String("type", "hits"), + )...), + }, + { + Attributes: attribute.NewSet(append( + featureFlagAttributes, + attribute.String("cache_type", "variables_normalization"), + attribute.String("type", "misses"), + )...), + }, + { + Attributes: attribute.NewSet(append( + featureFlagAttributes, + attribute.String("cache_type", "remap_variables"), + attribute.String("type", "hits"), + )...), + }, + { + Attributes: attribute.NewSet(append( + featureFlagAttributes, + attribute.String("cache_type", "remap_variables"), + attribute.String("type", "misses"), + )...), + }, { Attributes: attribute.NewSet(append( featureFlagAttributes, From 91215811e489170141c937dc7b695a60806205a3 Mon Sep 17 00:00:00 2001 From: Yury Smolski <140245+ysmolski@users.noreply.github.com> Date: Thu, 20 Nov 2025 11:11:11 +0200 Subject: [PATCH 11/11] fix small bug --- router/core/operation_processor_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/router/core/operation_processor_test.go b/router/core/operation_processor_test.go index ba63cfe899..589fcdf50c 100644 --- a/router/core/operation_processor_test.go +++ b/router/core/operation_processor_test.go @@ -261,7 +261,7 @@ func TestNormalizeVariablesOperationProcessor(t *testing.T) { _, err = kit.NormalizeOperation("test", false) require.NoError(t, err) - _, err = kit.NormalizeVariables() + _, _, err = kit.NormalizeVariables() require.NoError(t, err) assert.Equal(t, tc.ExpectedNormalizedRepresentation, kit.parsedOperation.NormalizedRepresentation)