Skip to content

Commit 3f8674d

Browse files
perf: cache compiled JSON schemas to improve compilation speed (#2433)
* perf: cache compiled JSON schemas to improve compilation speed Optimize workflow compilation by caching compiled JSON schemas instead of recompiling them for every workflow validation. This eliminates redundant schema parsing and compilation overhead. Changes: - pkg/parser/schema.go: Cache frontmatter schema compilation - Add sync.Once pattern for main workflow, included file, and MCP config schemas - Schemas are now compiled once and reused across all workflow compilations - pkg/workflow/validation.go: Cache GitHub Actions schema compilation - Add sync.Once pattern for GitHub Actions workflow schema - Schema compilation now happens once per process lifetime Performance Impact: - Eliminates repeated JSON schema parsing and compilation overhead - More significant on slower systems or when compiling many workflows - Zero performance regression, maintains full schema validation Trade-offs: - Complexity: +100 lines of caching logic (well-structured, thread-safe) - Memory: Minimal (cached schemas ~100KB total) - Maintainability: No impact (localized changes, clear pattern) Validation: - All unit tests pass - All integration tests pass - Code formatted with gofmt - No linting errors - Tested with compilation of 56 workflows successfully * chore: add changeset for schema compilation caching [skip-ci] --------- Co-authored-by: Daily Perf Improver <github-actions[bot]@users.noreply.github.com>
1 parent cd55d81 commit 3f8674d

File tree

3 files changed

+117
-21
lines changed

3 files changed

+117
-21
lines changed

.changeset/patch-cache-schema-compilation.md

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/parser/schema.go

Lines changed: 70 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"regexp"
1010
"sort"
1111
"strings"
12+
"sync"
1213

1314
"github.com/githubnext/gh-aw/pkg/console"
1415
"github.com/githubnext/gh-aw/pkg/constants"
@@ -120,24 +121,88 @@ func ValidateMCPConfigWithSchema(mcpConfig map[string]any, toolName string) erro
120121
}
121122

122123
// validateWithSchema validates frontmatter against a JSON schema
123-
func validateWithSchema(frontmatter map[string]any, schemaJSON, context string) error {
124+
// Cached compiled schemas to avoid recompiling on every validation
125+
var (
126+
mainWorkflowSchemaOnce sync.Once
127+
includedFileSchemaOnce sync.Once
128+
mcpConfigSchemaOnce sync.Once
129+
130+
compiledMainWorkflowSchema *jsonschema.Schema
131+
compiledIncludedFileSchema *jsonschema.Schema
132+
compiledMcpConfigSchema *jsonschema.Schema
133+
134+
mainWorkflowSchemaError error
135+
includedFileSchemaError error
136+
mcpConfigSchemaError error
137+
)
138+
139+
// getCompiledMainWorkflowSchema returns the compiled main workflow schema, compiling it once and caching
140+
func getCompiledMainWorkflowSchema() (*jsonschema.Schema, error) {
141+
mainWorkflowSchemaOnce.Do(func() {
142+
compiledMainWorkflowSchema, mainWorkflowSchemaError = compileSchema(mainWorkflowSchema, "http://contoso.com/main-workflow-schema.json")
143+
})
144+
return compiledMainWorkflowSchema, mainWorkflowSchemaError
145+
}
146+
147+
// getCompiledIncludedFileSchema returns the compiled included file schema, compiling it once and caching
148+
func getCompiledIncludedFileSchema() (*jsonschema.Schema, error) {
149+
includedFileSchemaOnce.Do(func() {
150+
compiledIncludedFileSchema, includedFileSchemaError = compileSchema(includedFileSchema, "http://contoso.com/included-file-schema.json")
151+
})
152+
return compiledIncludedFileSchema, includedFileSchemaError
153+
}
154+
155+
// getCompiledMcpConfigSchema returns the compiled MCP config schema, compiling it once and caching
156+
func getCompiledMcpConfigSchema() (*jsonschema.Schema, error) {
157+
mcpConfigSchemaOnce.Do(func() {
158+
compiledMcpConfigSchema, mcpConfigSchemaError = compileSchema(mcpConfigSchema, "http://contoso.com/mcp-config-schema.json")
159+
})
160+
return compiledMcpConfigSchema, mcpConfigSchemaError
161+
}
162+
163+
// compileSchema compiles a JSON schema from a JSON string
164+
func compileSchema(schemaJSON, schemaURL string) (*jsonschema.Schema, error) {
124165
// Create a new compiler
125166
compiler := jsonschema.NewCompiler()
126167

127168
// Parse the schema JSON first
128169
var schemaDoc any
129170
if err := json.Unmarshal([]byte(schemaJSON), &schemaDoc); err != nil {
130-
return fmt.Errorf("schema validation error for %s: failed to parse schema JSON: %w", context, err)
171+
return nil, fmt.Errorf("failed to parse schema JSON: %w", err)
131172
}
132173

133-
// Add the schema as a resource with a temporary URL
134-
schemaURL := "http://contoso.com/schema.json"
174+
// Add the schema as a resource
135175
if err := compiler.AddResource(schemaURL, schemaDoc); err != nil {
136-
return fmt.Errorf("schema validation error for %s: failed to add schema resource: %w", context, err)
176+
return nil, fmt.Errorf("failed to add schema resource: %w", err)
137177
}
138178

139179
// Compile the schema
140180
schema, err := compiler.Compile(schemaURL)
181+
if err != nil {
182+
return nil, fmt.Errorf("failed to compile schema: %w", err)
183+
}
184+
185+
return schema, nil
186+
}
187+
188+
func validateWithSchema(frontmatter map[string]any, schemaJSON, context string) error {
189+
// Determine which cached schema to use based on the schemaJSON
190+
var schema *jsonschema.Schema
191+
var err error
192+
193+
switch schemaJSON {
194+
case mainWorkflowSchema:
195+
schema, err = getCompiledMainWorkflowSchema()
196+
case includedFileSchema:
197+
schema, err = getCompiledIncludedFileSchema()
198+
case mcpConfigSchema:
199+
schema, err = getCompiledMcpConfigSchema()
200+
default:
201+
// Fallback for unknown schemas (shouldn't happen in normal operation)
202+
// Compile the schema on-the-fly
203+
schema, err = compileSchema(schemaJSON, "http://contoso.com/schema.json")
204+
}
205+
141206
if err != nil {
142207
return fmt.Errorf("schema validation error for %s: %w", context, err)
143208
}

pkg/workflow/validation.go

Lines changed: 42 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"os"
88
"regexp"
99
"strings"
10+
"sync"
1011

1112
"github.com/cli/go-gh/v2"
1213
"github.com/githubnext/gh-aw/pkg/console"
@@ -218,6 +219,44 @@ func collectPackagesFromWorkflow(
218219
}
219220

220221
// validateGitHubActionsSchema validates the generated YAML content against the GitHub Actions workflow schema
222+
// Cached compiled schema to avoid recompiling on every validation
223+
var (
224+
compiledSchemaOnce sync.Once
225+
compiledSchema *jsonschema.Schema
226+
schemaCompileError error
227+
)
228+
229+
// getCompiledSchema returns the compiled GitHub Actions schema, compiling it once and caching
230+
func getCompiledSchema() (*jsonschema.Schema, error) {
231+
compiledSchemaOnce.Do(func() {
232+
// Parse the embedded schema
233+
var schemaDoc any
234+
if err := json.Unmarshal([]byte(githubWorkflowSchema), &schemaDoc); err != nil {
235+
schemaCompileError = fmt.Errorf("failed to parse embedded GitHub Actions schema: %w", err)
236+
return
237+
}
238+
239+
// Create compiler and add the schema as a resource
240+
loader := jsonschema.NewCompiler()
241+
schemaURL := "https://json.schemastore.org/github-workflow.json"
242+
if err := loader.AddResource(schemaURL, schemaDoc); err != nil {
243+
schemaCompileError = fmt.Errorf("failed to add schema resource: %w", err)
244+
return
245+
}
246+
247+
// Compile the schema once
248+
schema, err := loader.Compile(schemaURL)
249+
if err != nil {
250+
schemaCompileError = fmt.Errorf("failed to compile GitHub Actions schema: %w", err)
251+
return
252+
}
253+
254+
compiledSchema = schema
255+
})
256+
257+
return compiledSchema, schemaCompileError
258+
}
259+
221260
func (c *Compiler) validateGitHubActionsSchema(yamlContent string) error {
222261
// Convert YAML to any for JSON conversion
223262
var workflowData any
@@ -231,23 +270,10 @@ func (c *Compiler) validateGitHubActionsSchema(yamlContent string) error {
231270
return fmt.Errorf("failed to convert YAML to JSON for validation: %w", err)
232271
}
233272

234-
// Parse the embedded schema
235-
var schemaDoc any
236-
if err := json.Unmarshal([]byte(githubWorkflowSchema), &schemaDoc); err != nil {
237-
return fmt.Errorf("failed to parse embedded GitHub Actions schema: %w", err)
238-
}
239-
240-
// Create compiler and add the schema as a resource
241-
loader := jsonschema.NewCompiler()
242-
schemaURL := "https://json.schemastore.org/github-workflow.json"
243-
if err := loader.AddResource(schemaURL, schemaDoc); err != nil {
244-
return fmt.Errorf("failed to add schema resource: %w", err)
245-
}
246-
247-
// Compile the schema
248-
schema, err := loader.Compile(schemaURL)
273+
// Get the cached compiled schema
274+
schema, err := getCompiledSchema()
249275
if err != nil {
250-
return fmt.Errorf("failed to compile GitHub Actions schema: %w", err)
276+
return err
251277
}
252278

253279
// Validate the JSON data against the schema

0 commit comments

Comments
 (0)