eko / gocache

☔️ A complete Go cache library that brings you multiple ways of managing your caches
https://vincent.composieux.fr/article/i-wrote-gocache-a-complete-and-extensible-go-cache-library/
MIT License
2.4k stars 193 forks source link

Cache race condition problem #233

Closed Kachit closed 4 months ago

Kachit commented 8 months ago

Hello. I am using your library and I have a problem with concurrency. Two parallel requests to empty cache not found data and fetching it from database twice. I think RWMutex will solve this problem, but maybe I need to use map of RWMutexes by cache keys for example:

cacheMap := map[string]sync.RWMutex{"my-key":sync.RWMutex{}}

Do you have ideas how to solve this problem ?

Steps for Reproduction

redisStore := redis_store.NewRedis(redis.NewClient(&redis.Options{
    Addr: "127.0.0.1:6379",
}))

cacheManager := cache.New[string](redisStore)

value, err := cacheManager.Get(ctx, "my-key")
if err != nil {
    panic(err)
}

if value != nil {
    //fetch from cache
} else {
    //fetch from db
    err := cacheManager.Set(ctx, "my-key", "fetched result", store.WithExpiration(15*time.Second))
    if err != nil {
        panic(err)
    }
} 

Expected behavior: All parallel requests executing one by one (using sync.Map or RWMutex)

Actual behavior: All parallel requests executing parallel

Platforms: go 1.19 (win and linux)

Versions: v4.1.5

Eyal-Shalev commented 5 months ago

Saw some library using the singleflight package for this https://pkg.go.dev/golang.org/x/sync/singleflight

eko commented 4 months ago

Hi,

This is now fixed by: https://github.com/eko/gocache/pull/243

Closing this issue.