fix: cache adjust restore order of exact key matches (#2267)
* wip: adjust restore order * fixup * add tests * cleanup * fix typo --------- Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> (cherry picked from commit f825e42ce200fc4973c3f28797ffba942d322d38)
This commit is contained in:
parent
71cfa868fa
commit
c04850088f
2 changed files with 115 additions and 0 deletions
|
@ -352,6 +352,17 @@ func (h *Handler) middleware(handler httprouter.Handle) httprouter.Handle {
|
||||||
func findCache(db *bolthold.Store, keys []string, version string) (*Cache, error) {
|
func findCache(db *bolthold.Store, keys []string, version string) (*Cache, error) {
|
||||||
cache := &Cache{}
|
cache := &Cache{}
|
||||||
for _, prefix := range keys {
|
for _, prefix := range keys {
|
||||||
|
// if a key in the list matches exactly, don't return partial matches
|
||||||
|
if err := db.FindOne(cache,
|
||||||
|
bolthold.Where("Key").Eq(prefix).
|
||||||
|
And("Version").Eq(version).
|
||||||
|
And("Complete").Eq(true).
|
||||||
|
SortBy("CreatedAt").Reverse()); err == nil || !errors.Is(err, bolthold.ErrNotFound) {
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("find cache: %w", err)
|
||||||
|
}
|
||||||
|
return cache, nil
|
||||||
|
}
|
||||||
prefixPattern := fmt.Sprintf("^%s", regexp.QuoteMeta(prefix))
|
prefixPattern := fmt.Sprintf("^%s", regexp.QuoteMeta(prefix))
|
||||||
re, err := regexp.Compile(prefixPattern)
|
re, err := regexp.Compile(prefixPattern)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -422,6 +422,110 @@ func TestHandler(t *testing.T) {
|
||||||
assert.Equal(t, key+"_abc", got.CacheKey)
|
assert.Equal(t, key+"_abc", got.CacheKey)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("exact keys are preferred (key 0)", func(t *testing.T) {
|
||||||
|
version := "c19da02a2bd7e77277f1ac29ab45c09b7d46a4ee758284e26bb3045ad11d9d20"
|
||||||
|
key := strings.ToLower(t.Name())
|
||||||
|
keys := [3]string{
|
||||||
|
key + "_a",
|
||||||
|
key + "_a_b_c",
|
||||||
|
key + "_a_b",
|
||||||
|
}
|
||||||
|
contents := [3][]byte{
|
||||||
|
make([]byte, 100),
|
||||||
|
make([]byte, 200),
|
||||||
|
make([]byte, 300),
|
||||||
|
}
|
||||||
|
for i := range contents {
|
||||||
|
_, err := rand.Read(contents[i])
|
||||||
|
require.NoError(t, err)
|
||||||
|
uploadCacheNormally(t, base, keys[i], version, contents[i])
|
||||||
|
time.Sleep(time.Second) // ensure CreatedAt of caches are different
|
||||||
|
}
|
||||||
|
|
||||||
|
reqKeys := strings.Join([]string{
|
||||||
|
key + "_a",
|
||||||
|
key + "_a_b",
|
||||||
|
}, ",")
|
||||||
|
|
||||||
|
resp, err := http.Get(fmt.Sprintf("%s/cache?keys=%s&version=%s", base, reqKeys, version))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 200, resp.StatusCode)
|
||||||
|
|
||||||
|
/*
|
||||||
|
Expect `key_a` because:
|
||||||
|
- `key_a` matches `key_a`, `key_a_b` and `key_a_b_c`, but `key_a` is an exact match.
|
||||||
|
- `key_a_b` matches `key_a_b` and `key_a_b_c`, but previous key had a match
|
||||||
|
*/
|
||||||
|
expect := 0
|
||||||
|
|
||||||
|
got := struct {
|
||||||
|
ArchiveLocation string `json:"archiveLocation"`
|
||||||
|
CacheKey string `json:"cacheKey"`
|
||||||
|
}{}
|
||||||
|
require.NoError(t, json.NewDecoder(resp.Body).Decode(&got))
|
||||||
|
assert.Equal(t, keys[expect], got.CacheKey)
|
||||||
|
|
||||||
|
contentResp, err := http.Get(got.ArchiveLocation)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 200, contentResp.StatusCode)
|
||||||
|
content, err := io.ReadAll(contentResp.Body)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, contents[expect], content)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("exact keys are preferred (key 1)", func(t *testing.T) {
|
||||||
|
version := "c19da02a2bd7e77277f1ac29ab45c09b7d46a4ee758284e26bb3045ad11d9d20"
|
||||||
|
key := strings.ToLower(t.Name())
|
||||||
|
keys := [3]string{
|
||||||
|
key + "_a",
|
||||||
|
key + "_a_b_c",
|
||||||
|
key + "_a_b",
|
||||||
|
}
|
||||||
|
contents := [3][]byte{
|
||||||
|
make([]byte, 100),
|
||||||
|
make([]byte, 200),
|
||||||
|
make([]byte, 300),
|
||||||
|
}
|
||||||
|
for i := range contents {
|
||||||
|
_, err := rand.Read(contents[i])
|
||||||
|
require.NoError(t, err)
|
||||||
|
uploadCacheNormally(t, base, keys[i], version, contents[i])
|
||||||
|
time.Sleep(time.Second) // ensure CreatedAt of caches are different
|
||||||
|
}
|
||||||
|
|
||||||
|
reqKeys := strings.Join([]string{
|
||||||
|
"------------------------------------------------------",
|
||||||
|
key + "_a",
|
||||||
|
key + "_a_b",
|
||||||
|
}, ",")
|
||||||
|
|
||||||
|
resp, err := http.Get(fmt.Sprintf("%s/cache?keys=%s&version=%s", base, reqKeys, version))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 200, resp.StatusCode)
|
||||||
|
|
||||||
|
/*
|
||||||
|
Expect `key_a` because:
|
||||||
|
- `------------------------------------------------------` doesn't match any caches.
|
||||||
|
- `key_a` matches `key_a`, `key_a_b` and `key_a_b_c`, but `key_a` is an exact match.
|
||||||
|
- `key_a_b` matches `key_a_b` and `key_a_b_c`, but previous key had a match
|
||||||
|
*/
|
||||||
|
expect := 0
|
||||||
|
|
||||||
|
got := struct {
|
||||||
|
ArchiveLocation string `json:"archiveLocation"`
|
||||||
|
CacheKey string `json:"cacheKey"`
|
||||||
|
}{}
|
||||||
|
require.NoError(t, json.NewDecoder(resp.Body).Decode(&got))
|
||||||
|
assert.Equal(t, keys[expect], got.CacheKey)
|
||||||
|
|
||||||
|
contentResp, err := http.Get(got.ArchiveLocation)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 200, contentResp.StatusCode)
|
||||||
|
content, err := io.ReadAll(contentResp.Body)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, contents[expect], content)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func uploadCacheNormally(t *testing.T, base, key, version string, content []byte) {
|
func uploadCacheNormally(t *testing.T, base, key, version string, content []byte) {
|
||||||
|
|
Loading…
Reference in a new issue