Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 56 additions & 15 deletions router-tests/cache_warmup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,21 @@ import (
"time"

"github.com/stretchr/testify/require"
"go.uber.org/zap"

"github.com/wundergraph/cosmo/router-tests/testenv"
"github.com/wundergraph/cosmo/router/core"
"github.com/wundergraph/cosmo/router/pkg/config"
"go.uber.org/zap"
)

func TestCacheWarmup(t *testing.T) {
t.Parallel()

t.Run("cache warmup tests for filesystem", func(t *testing.T) {
t.Parallel()

const employeeWarmedQueryCount = 1

t.Run("cache warmup disabled", func(t *testing.T) {
t.Parallel()
testenv.Run(t, &testenv.Config{}, func(t *testing.T, xEnv *testenv.Environment) {
Expand All @@ -28,6 +32,9 @@ func TestCacheWarmup(t *testing.T) {
})
t.Run("cache warmup enabled", func(t *testing.T) {
t.Parallel()

const employeeQueryCount = 2

testenv.Run(t, &testenv.Config{
RouterOptions: []core.Option{
core.WithCacheWarmupConfig(&config.CacheWarmupConfiguration{
Expand All @@ -41,12 +48,12 @@ func TestCacheWarmup(t *testing.T) {
},
AssertCacheMetrics: &testenv.CacheMetricsAssertions{
BaseGraphAssertions: testenv.CacheMetricsAssertion{
QueryNormalizationMisses: 3,
QueryNormalizationMisses: 3 + employeeWarmedQueryCount + employeeQueryCount,
QueryNormalizationHits: 4,
ValidationMisses: 3,
ValidationHits: 4,
PlanMisses: 3,
PlanHits: 4,
ValidationMisses: 3 + employeeWarmedQueryCount,
ValidationHits: 4 + employeeQueryCount,
PlanMisses: 3 + employeeWarmedQueryCount,
PlanHits: 4 + employeeQueryCount,
},
},
}, func(t *testing.T, xEnv *testenv.Environment) {
Expand All @@ -66,6 +73,19 @@ func TestCacheWarmup(t *testing.T) {
Query: `query { employees { id details { forename surname } } }`,
})
require.Equal(t, `{"data":{"employees":[{"id":1,"details":{"forename":"Jens","surname":"Neuse"}},{"id":2,"details":{"forename":"Dustin","surname":"Deus"}},{"id":3,"details":{"forename":"Stefan","surname":"Avram"}},{"id":4,"details":{"forename":"Björn","surname":"Schwenzer"}},{"id":5,"details":{"forename":"Sergiy","surname":"Petrunin"}},{"id":7,"details":{"forename":"Suvij","surname":"Surya"}},{"id":8,"details":{"forename":"Nithin","surname":"Kumar"}},{"id":10,"details":{"forename":"Eelco","surname":"Wiersma"}},{"id":11,"details":{"forename":"Alexandra","surname":"Neuse"}},{"id":12,"details":{"forename":"David","surname":"Stutt"}}]}}`, res.Body)

res = xEnv.MakeGraphQLRequestOK(testenv.GraphQLRequest{
Query: `query m($id: Int!){ employee(id: $id) { id details { forename surname } } }`,
Variables: []byte(`{"id": 1}`),
})
require.Equal(t, "HIT", res.Response.Header.Get("x-wg-execution-plan-cache"))
require.Equal(t, `{"data":{"employee":{"id":1,"details":{"forename":"Jens","surname":"Neuse"}}}}`, res.Body)

res = xEnv.MakeGraphQLRequestOK(testenv.GraphQLRequest{
Query: `query { employee(id: 2) { id details { forename surname } } }`,
})
require.Equal(t, "HIT", res.Response.Header.Get("x-wg-execution-plan-cache"))
require.Equal(t, `{"data":{"employee":{"id":2,"details":{"forename":"Dustin","surname":"Deus"}}}}`, res.Body)
})
})
t.Run("cache warmup invalid files", func(t *testing.T) {
Expand Down Expand Up @@ -337,13 +357,13 @@ func TestCacheWarmup(t *testing.T) {
},
AssertCacheMetrics: &testenv.CacheMetricsAssertions{
BaseGraphAssertions: testenv.CacheMetricsAssertion{
QueryNormalizationMisses: 3,
QueryNormalizationMisses: 3 + employeeWarmedQueryCount,
QueryNormalizationHits: 2,
ValidationMisses: 3,
ValidationMisses: 3 + employeeWarmedQueryCount,
ValidationHits: 2,
QueryHashMisses: 3,
QueryHashMisses: 3 + employeeWarmedQueryCount,
QueryHashHits: 2,
PlanMisses: 3,
PlanMisses: 3 + employeeWarmedQueryCount,
PlanHits: 2,
},
},
Expand All @@ -363,8 +383,8 @@ func TestCacheWarmup(t *testing.T) {
t.Run("cache warmup tests for cdn", func(t *testing.T) {
t.Parallel()

// keep in sync with testdata/cache_warmup/cdn/operation.json
cdnOperationCount := int64(4)
// keep in sync with testenv/testdata/cache_warmup/cdn/operation.json
cdnOperationCount := int64(5)
cdnPOCount := int64(1)
featureOperationCount := int64(1)
invalidOperationCount := int64(1)
Expand Down Expand Up @@ -397,6 +417,7 @@ func TestCacheWarmup(t *testing.T) {

t.Run("should correctly warm the cache with data from the operation.json file", func(t *testing.T) {
t.Parallel()
const employeeQueryCount = 2
testenv.Run(t, &testenv.Config{
RouterOptions: []core.Option{
core.WithCacheWarmupConfig(&config.CacheWarmupConfiguration{
Expand All @@ -405,14 +426,16 @@ func TestCacheWarmup(t *testing.T) {
},
AssertCacheMetrics: &testenv.CacheMetricsAssertions{
BaseGraphAssertions: testenv.CacheMetricsAssertion{
QueryNormalizationMisses: cdnOperationCount + featureOperationCount + invalidOperationCount,
// we have additional 2 misses for the employeeQueryCount - because their content differs from what we have in cdn
// this will be possible to solve only by having operation variants populated
QueryNormalizationMisses: cdnOperationCount + featureOperationCount + invalidOperationCount + employeeQueryCount,
QueryNormalizationHits: 3,
PersistedQueryNormalizationMisses: cdnPOCount,
PersistedQueryNormalizationHits: 0,
ValidationMisses: cdnOperationCount + cdnPOCount + featureOperationCount + invalidOperationCount,
ValidationHits: 3,
ValidationHits: 3 + employeeQueryCount,
PlanMisses: cdnOperationCount + cdnPOCount,
PlanHits: 3,
PlanHits: 3 + employeeQueryCount,
},
},
}, func(t *testing.T, xEnv *testenv.Environment) {
Expand All @@ -428,6 +451,24 @@ func TestCacheWarmup(t *testing.T) {
Query: `query { employees { id details { forename surname } } }`,
})
require.Equal(t, `{"data":{"employees":[{"id":1,"details":{"forename":"Jens","surname":"Neuse"}},{"id":2,"details":{"forename":"Dustin","surname":"Deus"}},{"id":3,"details":{"forename":"Stefan","surname":"Avram"}},{"id":4,"details":{"forename":"Björn","surname":"Schwenzer"}},{"id":5,"details":{"forename":"Sergiy","surname":"Petrunin"}},{"id":7,"details":{"forename":"Suvij","surname":"Surya"}},{"id":8,"details":{"forename":"Nithin","surname":"Kumar"}},{"id":10,"details":{"forename":"Eelco","surname":"Wiersma"}},{"id":11,"details":{"forename":"Alexandra","surname":"Neuse"}},{"id":12,"details":{"forename":"David","surname":"Stutt"}}]}}`, res.Body)

// For the next 2 queries below we will:
// - miss normalization cache
// - hit validation cache
// - hit plan cache

res = xEnv.MakeGraphQLRequestOK(testenv.GraphQLRequest{
Comment thread
devsergiy marked this conversation as resolved.
Query: `query m($id: Int!){ employee(id: $id) { id details { forename surname } } }`,
Variables: []byte(`{"id": 1}`),
})
require.Equal(t, "HIT", res.Response.Header.Get("x-wg-execution-plan-cache"))
require.Equal(t, `{"data":{"employee":{"id":1,"details":{"forename":"Jens","surname":"Neuse"}}}}`, res.Body)

res = xEnv.MakeGraphQLRequestOK(testenv.GraphQLRequest{
Query: `query { employee(id: 2) { id details { forename surname } } }`,
})
require.Equal(t, "HIT", res.Response.Header.Get("x-wg-execution-plan-cache"))
require.Equal(t, `{"data":{"employee":{"id":2,"details":{"forename":"Dustin","surname":"Deus"}}}}`, res.Body)
})
})

Expand Down
4 changes: 2 additions & 2 deletions router-tests/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,14 @@ require (
github.com/twmb/franz-go/pkg/kadm v1.11.0
github.com/wundergraph/cosmo/demo v0.0.0-20250107115408-cdd3d47d6424
github.com/wundergraph/cosmo/router v0.0.0-20250107115408-cdd3d47d6424
github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.139
github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.139.0.20250119131122-682a31fa0e4d
go.opentelemetry.io/otel v1.28.0
go.opentelemetry.io/otel/sdk v1.28.0
go.opentelemetry.io/otel/sdk/metric v1.28.0
go.opentelemetry.io/otel/trace v1.28.0
go.uber.org/atomic v1.11.0
go.uber.org/zap v1.27.0
golang.org/x/net v0.33.0
google.golang.org/protobuf v1.34.2
gopkg.in/yaml.v3 v3.0.1
)
Expand Down Expand Up @@ -159,7 +160,6 @@ require (
golang.org/x/crypto v0.31.0 // indirect
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect
golang.org/x/mod v0.18.0 // indirect
golang.org/x/net v0.33.0 // indirect
golang.org/x/sync v0.10.0 // indirect
golang.org/x/sys v0.29.0 // indirect
golang.org/x/text v0.21.0 // indirect
Expand Down
4 changes: 2 additions & 2 deletions router-tests/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -358,8 +358,8 @@ github.com/vektah/gqlparser/v2 v2.5.16 h1:1gcmLTvs3JLKXckwCwlUagVn/IlV2bwqle0vJ0
github.com/vektah/gqlparser/v2 v2.5.16/go.mod h1:1lz1OeCqgQbQepsGxPVywrjdBHW2T08PUS3pJqepRww=
github.com/wundergraph/astjson v0.0.0-20250106123708-be463c97e083 h1:8/D7f8gKxTBjW+SZK4mhxTTBVpxcqeBgWF1Rfmltbfk=
github.com/wundergraph/astjson v0.0.0-20250106123708-be463c97e083/go.mod h1:eOTL6acwctsN4F3b7YE+eE2t8zcJ/doLm9sZzsxxxrE=
github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.139 h1:ZxdsKeD3igrOpJtpyUk+Y9jC+///mj2MN/t9mDeX/7E=
github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.139/go.mod h1:B7eV0Qh8Lop9QzIOQcsvKp3S0ejfC6mgyWoJnI917yQ=
github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.139.0.20250119131122-682a31fa0e4d h1:o7oP0spr6QR5A+vz5OZZ/SJJAnVBL71kNHm+D/sbD9M=
github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.139.0.20250119131122-682a31fa0e4d/go.mod h1:B7eV0Qh8Lop9QzIOQcsvKp3S0ejfC6mgyWoJnI917yQ=
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 h1:+qGGcbkzsfDQNPPe9UDgpxAWQrhbbBXOYJFQDq/dtJw=
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913/go.mod h1:4aEEwZQutDLsQv2Deui4iYQ6DWTxR14g6m8Wv88+Xqk=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
query { employee(id: 2) { id details { forename surname } } }
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@
"request": {
"query": "query { employees { id } nonExistentField }"
}
},
{
"hint": "This is normalized representation of an employee query",
"request": {
"query": "query($a: Int!){employee(id: $a){id details {forename surname}}}"
}
}
]
}
19 changes: 13 additions & 6 deletions router/core/cache_warmup.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,18 @@ package core
import (
"context"
"errors"
"google.golang.org/protobuf/encoding/protojson"
"time"

"go.uber.org/ratelimit"
"go.uber.org/zap"
"google.golang.org/protobuf/encoding/protojson"

"github.com/wundergraph/astjson"
nodev1 "github.com/wundergraph/cosmo/router/gen/proto/wg/cosmo/node/v1"
"github.com/wundergraph/cosmo/router/pkg/config"
"github.com/wundergraph/graphql-go-tools/v2/pkg/ast"
"github.com/wundergraph/graphql-go-tools/v2/pkg/engine/resolve"
"go.uber.org/ratelimit"
"go.uber.org/zap"

nodev1 "github.com/wundergraph/cosmo/router/gen/proto/wg/cosmo/node/v1"
"github.com/wundergraph/cosmo/router/pkg/config"
)

type CacheWarmupItem struct {
Expand Down Expand Up @@ -276,7 +278,12 @@ func (c *CacheWarmupPlanningProcessor) ProcessOperation(ctx context.Context, ope
return err
}

_, err = k.Validate(true)
err = k.RemapVariables()
if err != nil {
return err
}

_, err = k.Validate(true, k.parsedOperation.RemapVariables)
if err != nil {
return err
}
Expand Down
17 changes: 9 additions & 8 deletions router/core/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,18 @@ import (
"time"

"github.com/expr-lang/expr/vm"
"github.com/wundergraph/cosmo/router/internal/expr"
"go.opentelemetry.io/otel/attribute"
"go.uber.org/zap"

"github.com/wundergraph/astjson"
graphqlmetrics "github.com/wundergraph/cosmo/router/gen/proto/wg/cosmo/graphqlmetrics/v1"
"github.com/wundergraph/cosmo/router/pkg/config"
"github.com/wundergraph/graphql-go-tools/v2/pkg/engine/datasource/httpclient"
"go.opentelemetry.io/otel/attribute"
"github.com/wundergraph/graphql-go-tools/v2/pkg/engine/resolve"

graphqlmetrics "github.com/wundergraph/cosmo/router/gen/proto/wg/cosmo/graphqlmetrics/v1"
"github.com/wundergraph/cosmo/router/internal/expr"
"github.com/wundergraph/cosmo/router/pkg/authentication"
"github.com/wundergraph/cosmo/router/pkg/config"
ctrace "github.com/wundergraph/cosmo/router/pkg/trace"

"github.com/wundergraph/graphql-go-tools/v2/pkg/engine/resolve"
"go.uber.org/zap"
)

type contextKey int
Expand Down Expand Up @@ -472,9 +471,11 @@ type operationContext struct {
opType OperationType
// hash is the hash of the operation with the normalized content and variables. Used for analytics.
hash uint64
// internalHash is the hash of the operation with normalized content. Used for engine / executor caching.
// internalHash is the hash of the operation with the fully normalized content. Used for engine / executor caching.
// we can't use the hash for this due to engine limitations in handling variables with the normalized representation
internalHash uint64
// remapVariables is a map of variables that have been remapped to the new names
remapVariables map[string]string
// Content is the content of the operation
content string
variables *astjson.Value
Expand Down
5 changes: 3 additions & 2 deletions router/core/graphql_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,9 @@ func (h *GraphQLHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
defer graphqlExecutionSpan.End()

ctx := &resolve.Context{
Variables: requestContext.operation.variables,
Files: requestContext.operation.files,
Variables: requestContext.operation.variables,
RemapVariables: requestContext.operation.remapVariables,
Files: requestContext.operation.files,
Request: resolve.Request{
Header: r.Header,
},
Expand Down
37 changes: 26 additions & 11 deletions router/core/graphql_prehandler.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,26 +11,24 @@ import (
"sync"
"time"

"github.com/wundergraph/cosmo/router/internal/expr"

"github.com/wundergraph/cosmo/router/pkg/config"
"github.com/go-chi/chi/v5/middleware"
"github.com/golang-jwt/jwt/v5"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
otelmetric "go.opentelemetry.io/otel/metric"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
"go.opentelemetry.io/otel/trace"
"go.uber.org/zap"

"github.com/wundergraph/astjson"

"github.com/go-chi/chi/v5/middleware"
"github.com/golang-jwt/jwt/v5"
"github.com/wundergraph/graphql-go-tools/v2/pkg/engine/datasource/httpclient"
"github.com/wundergraph/graphql-go-tools/v2/pkg/engine/plan"
"github.com/wundergraph/graphql-go-tools/v2/pkg/engine/resolve"
"github.com/wundergraph/graphql-go-tools/v2/pkg/graphqlerrors"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
"go.opentelemetry.io/otel/trace"
"go.uber.org/zap"

"github.com/wundergraph/cosmo/router/internal/expr"
"github.com/wundergraph/cosmo/router/pkg/art"
"github.com/wundergraph/cosmo/router/pkg/config"
"github.com/wundergraph/cosmo/router/pkg/otel"
rtrace "github.com/wundergraph/cosmo/router/pkg/trace"
)
Expand Down Expand Up @@ -569,7 +567,6 @@ func (h *PreHandler) handleOperation(req *http.Request, variablesParser *astjson
engineNormalizeSpan.SetAttributes(otel.WgNormalizationCacheHit.Bool(cached))

requestContext.operation.normalizationCacheHit = operationKit.parsedOperation.NormalizationCacheHit
requestContext.operation.internalHash = operationKit.parsedOperation.InternalID

/**
* Normalize the variables
Expand All @@ -590,7 +587,25 @@ func (h *PreHandler) handleOperation(req *http.Request, variablesParser *astjson
return err
}

err = operationKit.RemapVariables()
if err != nil {
rtrace.AttachErrToSpan(engineNormalizeSpan, err)

requestContext.operation.normalizationTime = time.Since(startNormalization)

if !requestContext.operation.traceOptions.ExcludeNormalizeStats {
httpOperation.traceTimings.EndNormalize()
}

engineNormalizeSpan.End()

return err
}

requestContext.operation.hash = operationKit.parsedOperation.ID
requestContext.operation.internalHash = operationKit.parsedOperation.InternalID
requestContext.operation.remapVariables = operationKit.parsedOperation.RemapVariables

operationHashString := strconv.FormatUint(operationKit.parsedOperation.ID, 10)

operationHashAttribute := otel.WgOperationHash.String(operationHashString)
Expand Down Expand Up @@ -642,7 +657,7 @@ func (h *PreHandler) handleOperation(req *http.Request, variablesParser *astjson
trace.WithSpanKind(trace.SpanKindInternal),
trace.WithAttributes(requestContext.telemetry.traceAttrs...),
)
validationCached, err := operationKit.Validate(requestContext.operation.executionOptions.SkipLoader)
validationCached, err := operationKit.Validate(requestContext.operation.executionOptions.SkipLoader, requestContext.operation.remapVariables)
if err != nil {
rtrace.AttachErrToSpan(engineValidateSpan, err)

Expand Down
5 changes: 3 additions & 2 deletions router/core/operation_planner.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,16 @@ import (
"errors"
"strconv"

graphqlmetricsv1 "github.com/wundergraph/cosmo/router/gen/proto/wg/cosmo/graphqlmetrics/v1"
"github.com/wundergraph/cosmo/router/pkg/graphqlschemausage"
"golang.org/x/sync/singleflight"

"github.com/wundergraph/graphql-go-tools/v2/pkg/ast"
"github.com/wundergraph/graphql-go-tools/v2/pkg/astparser"
"github.com/wundergraph/graphql-go-tools/v2/pkg/engine/plan"
"github.com/wundergraph/graphql-go-tools/v2/pkg/engine/postprocess"
"github.com/wundergraph/graphql-go-tools/v2/pkg/engine/resolve"

graphqlmetricsv1 "github.com/wundergraph/cosmo/router/gen/proto/wg/cosmo/graphqlmetrics/v1"
"github.com/wundergraph/cosmo/router/pkg/graphqlschemausage"
)

type planWithMetaData struct {
Expand Down
Loading