From 61a1a991123cd1e7374bc0681600b7f3838ed6a0 Mon Sep 17 00:00:00 2001 From: Ben Vincent Date: Thu, 2 Jul 2026 20:20:00 +1000 Subject: [PATCH] perf: compile remote match patterns once instead of per-request (#88) Fixes #73 ## Why `Classifier.Classify` runs on every proxied request and recompiled the Blocklist/Patterns/Immutable/Mutable regex lists each time. Regex compilation is expensive and fully redundant. ## Changes - Memoise compilation in a `sync.Map` keyed by pattern text (`compileCached`); each distinct pattern compiles once and is reused. Patterns that fail to compile are cached as a typed nil so they are not retried. No invalidation needed since the pattern text is the key. ## Validation - `go test ./internal/proxy/` and `make e2e` pass. Reviewed-on: https://git.unkin.net/unkin/artifactapi/pulls/88 Co-authored-by: Ben Vincent Co-committed-by: Ben Vincent --- internal/proxy/classifier.go | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/internal/proxy/classifier.go b/internal/proxy/classifier.go index b8bc686..82b889c 100644 --- a/internal/proxy/classifier.go +++ b/internal/proxy/classifier.go @@ -2,6 +2,7 @@ package proxy import ( "regexp" + "sync" "git.unkin.net/unkin/artifactapi/internal/provider" "git.unkin.net/unkin/artifactapi/pkg/models" @@ -60,10 +61,29 @@ func (c *Classifier) Classify(remote models.Remote, path string) Classification return ClassImmutable } +// patternCache memoises regex compilation. Classify runs on every proxied +// request and previously recompiled each remote's pattern lists every time; +// keying by the pattern string lets each distinct pattern compile once and +// then be reused, with no invalidation needed (the pattern text is the key). +// A pattern that fails to compile is cached as a typed nil so we don't retry. +var patternCache sync.Map // map[string]*regexp.Regexp + +func compileCached(pattern string) *regexp.Regexp { + if v, ok := patternCache.Load(pattern); ok { + return v.(*regexp.Regexp) + } + re, err := regexp.Compile(pattern) + if err != nil { + re = nil + } + patternCache.Store(pattern, re) + return re +} + func compilePatterns(patterns []string) []*regexp.Regexp { compiled := make([]*regexp.Regexp, 0, len(patterns)) for _, p := range patterns { - if re, err := regexp.Compile(p); err == nil { + if re := compileCached(p); re != nil { compiled = append(compiled, re) } }