Skip to content

Commit f9f15e3

Browse files
authored
fix: improve query planning time (#2303)
1 parent 83e0ed6 commit f9f15e3

File tree

11 files changed

+174
-45
lines changed

11 files changed

+174
-45
lines changed

router-tests/go.mod

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,11 @@ require (
2323
github.com/twmb/franz-go v1.16.1
2424
github.com/twmb/franz-go/pkg/kadm v1.11.0
2525
github.com/wundergraph/astjson v0.0.0-20250106123708-be463c97e083
26-
github.com/wundergraph/cosmo/demo v0.0.0-20250912064154-106e871ee32e
26+
github.com/wundergraph/cosmo/demo v0.0.0-20251029114720-2b84bc4e4e77
2727
github.com/wundergraph/cosmo/demo/pkg/subgraphs/projects v0.0.0-20250715110703-10f2e5f9c79e
28-
github.com/wundergraph/cosmo/router v0.0.0-20250912064154-106e871ee32e
28+
github.com/wundergraph/cosmo/router v0.0.0-20251029114720-2b84bc4e4e77
2929
github.com/wundergraph/cosmo/router-plugin v0.0.0-20250808194725-de123ba1c65e
30-
github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.232
30+
github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.235
3131
go.opentelemetry.io/otel v1.36.0
3232
go.opentelemetry.io/otel/sdk v1.36.0
3333
go.opentelemetry.io/otel/sdk/metric v1.36.0

router-tests/go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -354,8 +354,8 @@ github.com/wundergraph/astjson v0.0.0-20250106123708-be463c97e083 h1:8/D7f8gKxTB
354354
github.com/wundergraph/astjson v0.0.0-20250106123708-be463c97e083/go.mod h1:eOTL6acwctsN4F3b7YE+eE2t8zcJ/doLm9sZzsxxxrE=
355355
github.com/wundergraph/consul/sdk v0.0.0-20250204115147-ed842a8fd301 h1:EzfKHQoTjFDDcgaECCCR2aTePqMu9QBmPbyhqIYOhV0=
356356
github.com/wundergraph/consul/sdk v0.0.0-20250204115147-ed842a8fd301/go.mod h1:wxI0Nak5dI5RvJuzGyiEK4nZj0O9X+Aw6U0tC1wPKq0=
357-
github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.232 h1:G04FDSXlEaQZS9cBrKlP8djbzquoQElB7w7i/d4sAHg=
358-
github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.232/go.mod h1:ErOQH1ki2+SZB8JjpTyGVnoBpg5picIyjvuWQJP4abg=
357+
github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.235 h1:GiqY9zm5OR6SElIohw/rzfqejm3R1HjlSbMhbtZ4zWM=
358+
github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.235/go.mod h1:ErOQH1ki2+SZB8JjpTyGVnoBpg5picIyjvuWQJP4abg=
359359
github.com/xrash/smetrics v0.0.0-20250705151800-55b8f293f342 h1:FnBeRrxr7OU4VvAzt5X7s6266i6cSVkkFPS0TuXWbIg=
360360
github.com/xrash/smetrics v0.0.0-20250705151800-55b8f293f342/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
361361
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=

router/core/plan_generator.go

Lines changed: 27 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -74,28 +74,14 @@ func NewPlanner(planConfiguration *plan.Configuration, definition *ast.Document,
7474
}, nil
7575
}
7676

77+
// PlanOperation creates a query plan from an operation file in a pretty-printed text or JSON format
7778
func (pl *Planner) PlanOperation(operationFilePath string, outputFormat PlanOutputFormat) (string, error) {
78-
operation, err := pl.parseOperation(operationFilePath)
79-
if err != nil {
80-
return "", &PlannerOperationValidationError{err: err}
81-
}
82-
83-
operationName := findOperationName(operation)
84-
if operationName == nil {
85-
return "", &PlannerOperationValidationError{err: errors.New("operation name not found")}
86-
}
87-
88-
err = pl.normalizeOperation(operation, operationName)
89-
if err != nil {
90-
return "", &PlannerOperationValidationError{err: err}
91-
}
92-
93-
err = pl.validateOperation(operation)
79+
operation, err := pl.ParseAndPrepareOperation(operationFilePath)
9480
if err != nil {
95-
return "", &PlannerOperationValidationError{err: err}
81+
return "", err
9682
}
9783

98-
rawPlan, err := pl.planOperation(operation)
84+
rawPlan, err := pl.PlanPreparedOperation(operation)
9985
if err != nil {
10086
return "", fmt.Errorf("failed to plan operation: %w", err)
10187
}
@@ -111,31 +97,35 @@ func (pl *Planner) PlanOperation(operationFilePath string, outputFormat PlanOutp
11197
return string(marshal), nil
11298
}
11399

114-
return "", fmt.Errorf("invalid type specified: %q", outputFormat)
100+
return "", fmt.Errorf("invalid outputFormat specified: %q", outputFormat)
115101
}
116102

117-
func (pl *Planner) PlanParsedOperation(operation *ast.Document) (*resolve.FetchTreeQueryPlanNode, error) {
118-
operationName := findOperationName(operation)
119-
if operationName == nil {
120-
return nil, errors.New("operation name not found")
103+
// ParseAndPrepareOperation parses, normalizes and validates the operation
104+
func (pl *Planner) ParseAndPrepareOperation(operationFilePath string) (*ast.Document, error) {
105+
operation, err := pl.parseOperation(operationFilePath)
106+
if err != nil {
107+
return nil, &PlannerOperationValidationError{err: err}
121108
}
122109

123-
err := pl.normalizeOperation(operation, operationName)
124-
if err != nil {
125-
return nil, fmt.Errorf("failed to normalize operation: %w", err)
110+
return pl.PrepareOperation(operation)
111+
}
112+
113+
// PrepareOperation normalizes and validates the operation
114+
func (pl *Planner) PrepareOperation(operation *ast.Document) (*ast.Document, error) {
115+
operationName := findOperationName(operation)
116+
if operationName == nil {
117+
return nil, &PlannerOperationValidationError{err: errors.New("operation name not found")}
126118
}
127119

128-
err = pl.validateOperation(operation)
129-
if err != nil {
120+
if err := pl.normalizeOperation(operation, operationName); err != nil {
130121
return nil, &PlannerOperationValidationError{err: err}
131122
}
132123

133-
rawPlan, err := pl.planOperation(operation)
134-
if err != nil {
135-
return nil, fmt.Errorf("failed to plan operation: %w", err)
124+
if err := pl.validateOperation(operation); err != nil {
125+
return nil, &PlannerOperationValidationError{err: err}
136126
}
137127

138-
return rawPlan, nil
128+
return operation, nil
139129
}
140130

141131
func (pl *Planner) normalizeOperation(operation *ast.Document, operationName []byte) (err error) {
@@ -169,7 +159,8 @@ func (pl *Planner) normalizeOperation(operation *ast.Document, operationName []b
169159
return nil
170160
}
171161

172-
func (pl *Planner) planOperation(operation *ast.Document) (planNode *resolve.FetchTreeQueryPlanNode, err error) {
162+
// PlanPreparedOperation creates a query plan from a normalized and validated operation
163+
func (pl *Planner) PlanPreparedOperation(operation *ast.Document) (planNode *resolve.FetchTreeQueryPlanNode, err error) {
173164
defer func() {
174165
if r := recover(); r != nil {
175166
err = fmt.Errorf("panic during plan generation: %v", r)
@@ -358,8 +349,8 @@ func (pg *PlanGenerator) loadConfiguration(routerConfig *nodev1.RouterConfig, lo
358349
// we need to merge the base schema, it contains the __schema and __type queries
359350
// these are not usually part of a regular GraphQL schema
360351
// the engine needs to have them defined, otherwise it cannot resolve such fields
361-
err = asttransform.MergeDefinitionWithBaseSchema(&definition)
362-
if err != nil {
352+
353+
if err := asttransform.MergeDefinitionWithBaseSchema(&definition); err != nil {
363354
return fmt.Errorf("failed to merge graphql schema with base schema: %w", err)
364355
}
365356

@@ -410,5 +401,6 @@ func findOperationName(operation *ast.Document) (operationName []byte) {
410401
return operation.OperationDefinitionNameBytes(operation.RootNodes[i].Ref)
411402
}
412403
}
404+
// TODO: assign static operation name if we have single anonymous operation
413405
return nil
414406
}

router/core/plan_generator_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ func TestPlanOperationPanic(t *testing.T) {
2929
}
3030

3131
assert.NotPanics(t, func() {
32-
_, err = planner.planOperation(invalidOperation)
32+
_, err = planner.PlanPreparedOperation(invalidOperation)
3333
assert.Error(t, err)
3434
})
3535
}

router/go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ require (
3131
github.com/tidwall/gjson v1.18.0
3232
github.com/tidwall/sjson v1.2.5
3333
github.com/twmb/franz-go v1.16.1
34-
github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.232
34+
github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.235
3535
// Do not upgrade, it renames attributes we rely on
3636
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0
3737
go.opentelemetry.io/contrib/propagators/b3 v1.23.0
@@ -194,4 +194,4 @@ replace (
194194
// Remember you can use Go workspaces to avoid using replace directives in multiple go.mod files
195195
// Use what is best for your personal workflow. See CONTRIBUTING.md for more information
196196

197-
// replace github.com/wundergraph/graphql-go-tools/v2 => ../../graphql-go-tools/v2
197+
//replace github.com/wundergraph/graphql-go-tools/v2 => ../../graphql-go-tools/v2

router/go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -322,8 +322,8 @@ github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/
322322
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
323323
github.com/wundergraph/astjson v0.0.0-20250106123708-be463c97e083 h1:8/D7f8gKxTBjW+SZK4mhxTTBVpxcqeBgWF1Rfmltbfk=
324324
github.com/wundergraph/astjson v0.0.0-20250106123708-be463c97e083/go.mod h1:eOTL6acwctsN4F3b7YE+eE2t8zcJ/doLm9sZzsxxxrE=
325-
github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.232 h1:G04FDSXlEaQZS9cBrKlP8djbzquoQElB7w7i/d4sAHg=
326-
github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.232/go.mod h1:ErOQH1ki2+SZB8JjpTyGVnoBpg5picIyjvuWQJP4abg=
325+
github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.235 h1:GiqY9zm5OR6SElIohw/rzfqejm3R1HjlSbMhbtZ4zWM=
326+
github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.235/go.mod h1:ErOQH1ki2+SZB8JjpTyGVnoBpg5picIyjvuWQJP4abg=
327327
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
328328
github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
329329
github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M=
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
*.json
2+
*.txt
3+
*.out
4+
*.test
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Planning Benchmark
2+
3+
Allows to benchmark query planning performance of a single graphql query
4+
5+
## Prerequisites
6+
7+
- Compose subgraphs to get execution config.json
8+
- Create `benchmark_config.json` file in the `router/internal/planningbenchmark` directory with the following content:
9+
10+
```json
11+
{
12+
"executionConfigPath": "<path-to-composed-config.json>",
13+
"operationPath": "<path-to-graphql-query-file>.graphql",
14+
}
15+
```
16+
17+
## Running the Benchmark
18+
19+
Run `BenchmarkPlanning` benchmark from benchmark_test.go
20+
21+
## Running benchmark using taskfile
22+
23+
- Modify step name in `router/internal/planningbenchmark/Taskfile.yml` if needed
24+
- Run `task bench-cpu` from `router/internal/planningbenchmark` directory
25+
- Profile will be generated in file `<step>_cpu.out`
26+
27+
You could use different profiles or combine profile. See Taskfile.yml for more details.
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
version: '3'
2+
3+
vars:
4+
step: bench_planning_01
5+
6+
tasks:
7+
bench:
8+
go test -benchtime 60s -bench=. -benchmem -memprofile {{.step}}_mem.out -cpuprofile {{.step}}_cpu.out > {{.step}}_benchmark.txt
9+
10+
bench-cpu:
11+
go test -benchtime 60s -bench=. -cpuprofile {{.step}}_cpu.out
12+
13+
bench-mem:
14+
go test -benchtime 60s -bench=. -memprofile {{.step}}_mem.out
15+
16+
profile-cpu-web:
17+
go tool pprof -http=localhost:8080 {{.step}}_cpu.out
18+
19+
profile-cpu:
20+
go tool pprof {{.step}}_cpu.out
21+
22+
profile-mem-web:
23+
go tool pprof -http=localhost:8080 {{.step}}_mem.out
24+
25+
profile-mem:
26+
go tool pprof {{.step}}_mem.out
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"executionConfigPath": "",
3+
"operationPath": ""
4+
}

0 commit comments

Comments
 (0)