Skip to content

Commit 8768b33

Browse files
committed
Invalidate bucket caches on object modification or deletion
Invalidate content, existence, and attributes cached for a particular object when the object is modified or deleted. Part of #9386
1 parent 3b5a9b3 commit 8768b33

File tree

2 files changed

+81
-0
lines changed

2 files changed

+81
-0
lines changed

pkg/storage/tsdb/bucketcache/caching_bucket.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,38 @@ func NewCachingBucket(bucketID string, bucketClient objstore.Bucket, cfg *Cachin
159159
return cb, nil
160160
}
161161

162+
func (cb *CachingBucket) invalidate(ctx context.Context, name string) {
163+
_, cfg := cb.cfg.findGetConfig(name)
164+
if cfg != nil {
165+
contentKey := cachingKeyContent(cb.bucketID, name)
166+
attrsKey := cachingKeyAttributes(cb.bucketID, name)
167+
existsKey := cachingKeyExists(cb.bucketID, name)
168+
169+
// Cache might be down or key might not exist. Ignore errors since invalidation is best-effort.
170+
_ = cfg.cache.Delete(ctx, contentKey)
171+
_ = cfg.cache.Delete(ctx, attrsKey)
172+
_ = cfg.cache.Delete(ctx, existsKey)
173+
}
174+
}
175+
176+
func (cb *CachingBucket) Upload(ctx context.Context, name string, r io.Reader) error {
177+
err := cb.Bucket.Upload(ctx, name, r)
178+
if err == nil {
179+
cb.invalidate(ctx, name)
180+
}
181+
182+
return err
183+
}
184+
185+
func (cb *CachingBucket) Delete(ctx context.Context, name string) error {
186+
err := cb.Bucket.Delete(ctx, name)
187+
if err == nil {
188+
cb.invalidate(ctx, name)
189+
}
190+
191+
return err
192+
}
193+
162194
func (cb *CachingBucket) Name() string {
163195
return "caching: " + cb.Bucket.Name()
164196
}

pkg/storage/tsdb/bucketcache/caching_bucket_test.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -909,6 +909,55 @@ func TestCachingKey_ShouldKeepAllocationsToMinimum(t *testing.T) {
909909
}
910910
}
911911

912+
func TestMutationInvalidatesCache(t *testing.T) {
913+
inmem := objstore.NewInMemBucket()
914+
915+
// We reuse cache between tests (!)
916+
c := cache.NewMockCache()
917+
ctx := context.Background()
918+
919+
const cfgName = "test"
920+
cfg := NewCachingBucketConfig()
921+
cfg.CacheGet(cfgName, c, matchAll, 1024^2, time.Minute, time.Minute, time.Minute)
922+
cfg.CacheExists(cfgName, c, matchAll, time.Minute, time.Minute)
923+
cfg.CacheAttributes(cfgName, c, matchAll, time.Minute)
924+
925+
cb, err := NewCachingBucket("test", inmem, cfg, nil, nil)
926+
require.NoError(t, err)
927+
928+
t.Run("invalidated on upload", func(t *testing.T) {
929+
c.Flush()
930+
931+
// Initial upload bypassing the CachingBucket but read the object back to ensure it is in cache.
932+
require.NoError(t, inmem.Upload(ctx, "/object-1", strings.NewReader("test content 1")))
933+
verifyGet(ctx, t, cb, "/object-1", []byte("test content 1"), true, false, cfgName)
934+
verifyExists(ctx, t, cb, "/object-1", true, true, true, cfgName)
935+
verifyObjectAttrs(ctx, t, cb, "/object-1", 14, true, false, cfgName)
936+
937+
// Do an upload via the CachingBucket and ensure the first read after does not come from cache.
938+
require.NoError(t, cb.Upload(ctx, "/object-1", strings.NewReader("test content 12")))
939+
verifyGet(ctx, t, cb, "/object-1", []byte("test content 12"), true, false, cfgName)
940+
verifyExists(ctx, t, cb, "/object-1", true, true, true, cfgName)
941+
verifyObjectAttrs(ctx, t, cb, "/object-1", 15, true, false, cfgName)
942+
})
943+
944+
t.Run("invalidated on delete", func(t *testing.T) {
945+
c.Flush()
946+
947+
// Initial upload bypassing the CachingBucket but read the object back to ensure it is in cache.
948+
require.NoError(t, inmem.Upload(ctx, "/object-1", strings.NewReader("test content 1")))
949+
verifyGet(ctx, t, cb, "/object-1", []byte("test content 1"), true, false, cfgName)
950+
verifyExists(ctx, t, cb, "/object-1", true, true, true, cfgName)
951+
verifyObjectAttrs(ctx, t, cb, "/object-1", 14, true, false, cfgName)
952+
953+
// Delete via the CachingBucket and ensure the first read after does not come from cache but non-existence is cached.
954+
require.NoError(t, cb.Delete(ctx, "/object-1"))
955+
verifyGet(ctx, t, cb, "/object-1", nil, true, false, cfgName)
956+
verifyExists(ctx, t, cb, "/object-1", false, true, true, cfgName)
957+
verifyObjectAttrs(ctx, t, cb, "/object-1", -1, true, false, cfgName)
958+
})
959+
}
960+
912961
func BenchmarkCachingKey(b *testing.B) {
913962
tests := map[string]struct {
914963
run func(bucketID string)

0 commit comments

Comments
 (0)