Skip to content

Commit b2974ab

Browse files
committed
use string matching instead of regex for surrogate keys
1 parent f029298 commit b2974ab

File tree

5 files changed

+139
-33
lines changed

5 files changed

+139
-33
lines changed

pkg/surrogate/providers/common.go

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -162,11 +162,37 @@ func (s *baseStorage) init(config configurationtypes.AbstractConfigurationInterf
162162
s.duration = storageToInfiniteTTLMap[s.Storage.Name()]
163163
}
164164

165-
func (s *baseStorage) storeTag(tag string, cacheKey string, re *regexp.Regexp) {
165+
// containsCacheKey checks if the cacheKey already exists in the comma-separated currentValue.
166+
// This is much faster than regex matching, especially for long strings.
167+
func containsCacheKey(currentValue, cacheKey string) bool {
168+
if currentValue == "" {
169+
return false
170+
}
171+
// Check for exact match at various positions:
172+
// 1. Exact match of entire string
173+
if currentValue == cacheKey {
174+
return true
175+
}
176+
// 2. At the beginning: "cacheKey,"
177+
if strings.HasPrefix(currentValue, cacheKey+souinStorageSeparator) {
178+
return true
179+
}
180+
// 3. At the end: ",cacheKey"
181+
if strings.HasSuffix(currentValue, souinStorageSeparator+cacheKey) {
182+
return true
183+
}
184+
// 4. In the middle: ",cacheKey,"
185+
if strings.Contains(currentValue, souinStorageSeparator+cacheKey+souinStorageSeparator) {
186+
return true
187+
}
188+
return false
189+
}
190+
191+
func (s *baseStorage) storeTag(tag string, cacheKey string) {
166192
defer s.mu.Unlock()
167193
s.mu.Lock()
168194
currentValue := string(s.Storage.Get(surrogatePrefix + tag))
169-
if !re.MatchString(currentValue) {
195+
if !containsCacheKey(currentValue, cacheKey) {
170196
s.logger.Debugf("Store the tag %s", tag)
171197
_ = s.Storage.Set(surrogatePrefix+tag, []byte(currentValue+souinStorageSeparator+cacheKey), s.duration)
172198
}
@@ -222,29 +248,28 @@ func (s *baseStorage) Store(response *http.Response, cacheKey, uri string) error
222248

223249
cacheKey = url.QueryEscape(cacheKey)
224250

225-
urlRegexp := regexp.MustCompile("(^|" + regexp.QuoteMeta(souinStorageSeparator) + ")" + regexp.QuoteMeta(cacheKey) + "(" + regexp.QuoteMeta(souinStorageSeparator) + "|$)")
226251
keys := s.ParseHeaders(s.parent.getSurrogateKey(h))
227252

228253
for _, key := range keys {
229254
_, v := s.parent.GetSurrogateControl(h)
230255
if controls := s.ParseHeaders(v); len(controls) != 0 {
231256
if len(controls) == 1 && controls[0] == "" {
232-
s.storeTag(key, cacheKey, urlRegexp)
233-
s.storeTag(uri, cacheKey, urlRegexp)
257+
s.storeTag(key, cacheKey)
258+
s.storeTag(uri, cacheKey)
234259

235260
continue
236261
}
237262
for _, control := range controls {
238263
if s.parent.candidateStore(control) {
239-
s.storeTag(key, cacheKey, urlRegexp)
240-
s.storeTag(uri, cacheKey, urlRegexp)
264+
s.storeTag(key, cacheKey)
265+
s.storeTag(uri, cacheKey)
241266

242267
break
243268
}
244269
}
245270
} else {
246-
s.storeTag(key, cacheKey, urlRegexp)
247-
s.storeTag(uri, cacheKey, urlRegexp)
271+
s.storeTag(key, cacheKey)
272+
s.storeTag(uri, cacheKey)
248273
}
249274
}
250275

pkg/surrogate/providers/common_test.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,3 +172,39 @@ func TestBaseStorage_Store_Load(t *testing.T) {
172172
// // t.Errorf("The surrogate storage should contain %d stored elements, %d given.", length+1, len(strings.Split(string(v), ",")))
173173
// }
174174
}
175+
176+
func TestContainsCacheKey(t *testing.T) {
177+
testCases := []struct {
178+
name string
179+
currentValue string
180+
cacheKey string
181+
expected bool
182+
}{
183+
{"empty current value", "", "key1", false},
184+
{"exact match single key", "key1", "key1", true},
185+
{"key at beginning", "key1,key2,key3", "key1", true},
186+
{"key at end", "key1,key2,key3", "key3", true},
187+
{"key in middle", "key1,key2,key3", "key2", true},
188+
{"key not present", "key1,key2,key3", "key4", false},
189+
{"partial match should not match", "key1,key2,key3", "key", false},
190+
{"partial match at start should not match", "key12,key2,key3", "key1", false},
191+
{"partial match at end should not match", "key1,key2,key34", "key3", false},
192+
{"url encoded key present", "%2Fapi%2Fusers,other", "%2Fapi%2Fusers", true},
193+
{"url encoded key not present", "%2Fapi%2Fusers,other", "%2Fapi%2Fposts", false},
194+
{"key with special chars", "a%2Fb,c%2Fd", "a%2Fb", true},
195+
{"empty key in empty value", "", "", false},
196+
{"empty key in non-empty value", "key1,key2", "", false},
197+
{"similar keys should not match", "product-123,product-1234", "product-12", false},
198+
{"leading comma in stored value", ",key1,key2", "key1", true},
199+
}
200+
201+
for _, tc := range testCases {
202+
t.Run(tc.name, func(t *testing.T) {
203+
result := containsCacheKey(tc.currentValue, tc.cacheKey)
204+
if result != tc.expected {
205+
t.Errorf("containsCacheKey(%q, %q) = %v, expected %v",
206+
tc.currentValue, tc.cacheKey, result, tc.expected)
207+
}
208+
})
209+
}
210+
}

pkg/surrogate/providers/types.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package providers
22

33
import (
44
"net/http"
5-
"regexp"
65
)
76

87
// SurrogateInterface represents the interface to implement to be part
@@ -17,7 +16,7 @@ type SurrogateInterface interface {
1716
Invalidate(method string, h http.Header)
1817
purgeTag(string) []string
1918
Store(*http.Response, string, string) error
20-
storeTag(string, string, *regexp.Regexp)
19+
storeTag(string, string)
2120
ParseHeaders(string) []string
2221
List() map[string]string
2322
candidateStore(string) bool

plugins/traefik/override/surrogate/providers/common.go

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -120,11 +120,37 @@ func (s *baseStorage) init(config configurationtypes.AbstractConfigurationInterf
120120
s.duration = storageToInfiniteTTLMap[s.Storage.Name()]
121121
}
122122

123-
func (s *baseStorage) storeTag(tag string, cacheKey string, re *regexp.Regexp) {
123+
// containsCacheKey checks if the cacheKey already exists in the comma-separated currentValue.
124+
// This is much faster than regex matching, especially for long strings.
125+
func containsCacheKey(currentValue, cacheKey string) bool {
126+
if currentValue == "" {
127+
return false
128+
}
129+
// Check for exact match at various positions:
130+
// 1. Exact match of entire string
131+
if currentValue == cacheKey {
132+
return true
133+
}
134+
// 2. At the beginning: "cacheKey,"
135+
if strings.HasPrefix(currentValue, cacheKey+souinStorageSeparator) {
136+
return true
137+
}
138+
// 3. At the end: ",cacheKey"
139+
if strings.HasSuffix(currentValue, souinStorageSeparator+cacheKey) {
140+
return true
141+
}
142+
// 4. In the middle: ",cacheKey,"
143+
if strings.Contains(currentValue, souinStorageSeparator+cacheKey+souinStorageSeparator) {
144+
return true
145+
}
146+
return false
147+
}
148+
149+
func (s *baseStorage) storeTag(tag string, cacheKey string) {
124150
defer s.mu.Unlock()
125151
s.mu.Lock()
126152
currentValue := string(s.Storage.Get(surrogatePrefix + tag))
127-
if !re.MatchString(currentValue) {
153+
if !containsCacheKey(currentValue, cacheKey) {
128154
fmt.Printf("Store the tag %s", tag)
129155
_ = s.Storage.Set(surrogatePrefix+tag, []byte(currentValue+souinStorageSeparator+cacheKey), -1)
130156
}
@@ -195,29 +221,26 @@ func (s *baseStorage) Store(response *http.Response, cacheKey, uri string) error
195221
cacheKey = url.QueryEscape(cacheKey)
196222
staleKey := stalePrefix + cacheKey
197223

198-
urlRegexp := regexp.MustCompile("(^|" + regexp.QuoteMeta(souinStorageSeparator) + ")" + regexp.QuoteMeta(cacheKey) + "(" + regexp.QuoteMeta(souinStorageSeparator) + "|$)")
199-
staleUrlRegexp := regexp.MustCompile("(^|" + regexp.QuoteMeta(souinStorageSeparator) + ")" + regexp.QuoteMeta(staleKey) + "(" + regexp.QuoteMeta(souinStorageSeparator) + "|$)")
200-
201224
keys := s.ParseHeaders(s.parent.getSurrogateKey(h))
202225

203226
for _, key := range keys {
204227
_, v := s.parent.GetSurrogateControl(h)
205228
if controls := s.ParseHeaders(v); len(controls) != 0 {
206229
if len(controls) == 1 && controls[0] == "" {
207-
s.storeTag(key, cacheKey, urlRegexp)
208-
s.storeTag(stalePrefix+key, staleKey, staleUrlRegexp)
230+
s.storeTag(key, cacheKey)
231+
s.storeTag(stalePrefix+key, staleKey)
209232

210233
continue
211234
}
212235
for _, control := range controls {
213236
if s.parent.candidateStore(control) {
214-
s.storeTag(key, cacheKey, urlRegexp)
215-
s.storeTag(stalePrefix+key, staleKey, staleUrlRegexp)
237+
s.storeTag(key, cacheKey)
238+
s.storeTag(stalePrefix+key, staleKey)
216239
}
217240
}
218241
} else {
219-
s.storeTag(key, cacheKey, urlRegexp)
220-
s.storeTag(stalePrefix+key, staleKey, staleUrlRegexp)
242+
s.storeTag(key, cacheKey)
243+
s.storeTag(stalePrefix+key, staleKey)
221244
}
222245
}
223246

plugins/tyk/override/cache/surrogate/providers/common.go

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,33 @@ func (s *baseStorage) init(config configurationtypes.AbstractConfigurationInterf
106106
s.mu = sync.Mutex{}
107107
}
108108

109-
func (s *baseStorage) storeTag(tag string, cacheKey string, re *regexp.Regexp) {
109+
// containsCacheKey checks if the cacheKey already exists in the comma-separated currentValue.
110+
// This is much faster than regex matching, especially for long strings.
111+
func containsCacheKey(currentValue, cacheKey string) bool {
112+
if currentValue == "" {
113+
return false
114+
}
115+
// Check for exact match at various positions:
116+
// 1. Exact match of entire string
117+
if currentValue == cacheKey {
118+
return true
119+
}
120+
// 2. At the beginning: "cacheKey,"
121+
if strings.HasPrefix(currentValue, cacheKey+souinStorageSeparator) {
122+
return true
123+
}
124+
// 3. At the end: ",cacheKey"
125+
if strings.HasSuffix(currentValue, souinStorageSeparator+cacheKey) {
126+
return true
127+
}
128+
// 4. In the middle: ",cacheKey,"
129+
if strings.Contains(currentValue, souinStorageSeparator+cacheKey+souinStorageSeparator) {
130+
return true
131+
}
132+
return false
133+
}
134+
135+
func (s *baseStorage) storeTag(tag string, cacheKey string) {
110136
defer s.mu.Unlock()
111137
s.mu.Lock()
112138
currentValue, b := s.Storage.Load(tag)
@@ -115,7 +141,7 @@ func (s *baseStorage) storeTag(tag string, cacheKey string, re *regexp.Regexp) {
115141
b = s.dynamic
116142
}
117143
if s.dynamic || b {
118-
if !re.MatchString(currentValue.(string)) {
144+
if !containsCacheKey(currentValue.(string), cacheKey) {
119145
s.logger.Debugf("Store the tag %s", tag)
120146
s.Storage.Store(tag, currentValue.(string)+souinStorageSeparator+cacheKey)
121147
}
@@ -177,28 +203,25 @@ func (s *baseStorage) Store(response *http.Response, cacheKey string) error {
177203
cacheKey = url.QueryEscape(cacheKey)
178204
staleKey := stalePrefix + cacheKey
179205

180-
urlRegexp := regexp.MustCompile("(^|" + regexp.QuoteMeta(souinStorageSeparator) + ")" + regexp.QuoteMeta(cacheKey) + "(" + regexp.QuoteMeta(souinStorageSeparator) + "|$)")
181-
staleUrlRegexp := regexp.MustCompile("(^|" + regexp.QuoteMeta(souinStorageSeparator) + ")" + regexp.QuoteMeta(staleKey) + "(" + regexp.QuoteMeta(souinStorageSeparator) + "|$)")
182-
183206
keys := s.ParseHeaders(s.parent.getSurrogateKey(h))
184207

185208
for _, key := range keys {
186209
if controls := s.ParseHeaders(s.parent.GetSurrogateControl(h)); len(controls) != 0 {
187210
if len(controls) == 1 && controls[0] == "" {
188-
s.storeTag(key, cacheKey, urlRegexp)
189-
s.storeTag(stalePrefix+key, staleKey, staleUrlRegexp)
211+
s.storeTag(key, cacheKey)
212+
s.storeTag(stalePrefix+key, staleKey)
190213

191214
continue
192215
}
193216
for _, control := range controls {
194217
if s.parent.candidateStore(control) {
195-
s.storeTag(key, cacheKey, urlRegexp)
196-
s.storeTag(stalePrefix+key, staleKey, staleUrlRegexp)
218+
s.storeTag(key, cacheKey)
219+
s.storeTag(stalePrefix+key, staleKey)
197220
}
198221
}
199222
} else {
200-
s.storeTag(key, cacheKey, urlRegexp)
201-
s.storeTag(stalePrefix+key, staleKey, staleUrlRegexp)
223+
s.storeTag(key, cacheKey)
224+
s.storeTag(stalePrefix+key, staleKey)
202225
}
203226
}
204227

0 commit comments

Comments
 (0)