go-cache icon indicating copy to clipboard operation
go-cache copied to clipboard

Generic go-cache

Open sschiz opened this issue 3 years ago • 15 comments

I have rewritten go-cache to generic one. Originally Cache has incrementing and decrementing methods, but any constraint doesn't allow types to be added and subtracted. I decided to implement the original cache using any restriction, while removing these methods, and also to implement extended Cache named OrderedCache that has incrementing method. I didn't really touch the sharded cache. I made the cached value comparable. The key wasn't touched. Internally the sharded cache has OrderedCache. Last but not least, I initiated the go.mod file because a dependency of golang.org/x/exp was required.

sschiz avatar Mar 18 '22 19:03 sschiz

I'd love to have this merged soon! :raised_hands:

muety avatar Mar 20 '22 14:03 muety

This package hasn't seen any updates for a number of years, so it seems unmaintained.

I'll probably add this, or a version of this, to the fork I use. This is an incompatible change though, so it needs to be as a v2.

arp242 avatar Mar 26 '22 07:03 arp242

Keep me posted once version 2 is out, then I'll probably switch to zcache.

muety avatar Mar 26 '22 08:03 muety

@arp242 If there's anything I can do to help, let me know.

sschiz avatar Mar 26 '22 13:03 sschiz

Working on adding this, as I figured it would be a good project to get up to speed with generics. I wonder if there's a more convenient way to do this?

var zeroValue V
return zeroValue, false

It's just a bit of needless indirection; I'd rather do something like return V{}, false.

For the time being I added:

func (c *cache[K, V]) zero() V {
    var zeroValue V
    return zeroValue
}

So you can do return c.zero(), false. But meh.

arp242 avatar Mar 27 '22 11:03 arp242

You could create function like this instead of method func zero(v V) V

sschiz avatar Mar 27 '22 12:03 sschiz

I put a v2 branch over here, with some other incompatible changes I made last year too: https://github.com/arp242/zcache/tree/v2

I'm not entirely convinced about using a OrderedCache for Increment and Decrement operations; I removed these methods for now (still work-in-progress). Need to look a bit to alternatives.

arp242 avatar Mar 27 '22 12:03 arp242

I think I'll leave out Increment and Decrement; the Modify that I added works for this as well, and the performance is roughly equal:

cache := zcache.New[string, int](zcache.DefaultExpiration, 0)
cache.Set("one", 1)

newValue, err := cache.Modify("one", func(v int) int { return v + 2 })

It's a bit more typing, bit I think that's okay.

Any feedback @muety or @sschiz? I migrated my own application to v2 and it all seems to work grand, but a second pair of eyes never hurts.

arp242 avatar Mar 28 '22 10:03 arp242

@arp242 what is the difference between these:

    1. Get
    2. Do changes
    3. Set
    1. Modify with function

In fact, it's the same thing, and Modify method is boilerplate. I think, you should leave modifications of value to user, because in development it is unlikely that anyone will use such a thing. The purpose of Increment or Decrement is shortcutting like ++ and --. Modify is useless because it's shortcutting nothing.

sschiz avatar Mar 28 '22 10:03 sschiz

The advantage of Modify() is that it's thread-safe; if you have multiple goroutines working on the same key, then in-between the "get" and "set" some other goroutine can also increment it, and the final value will be wrong. So you end up with:

goroutine 1: get value 1
goroutine 2: get value 1
goroutine 1: add 1 to value
goroutine 2: add 1 to value
goroutine 1: set value to 2
goroutine 2: set value to 2

But what really should happen is:

goroutine 1: get value 1
goroutine 1: add 1 to value
goroutine 1: set value to 2

goroutine 2: get value 2
goroutine 2: add 1 to value
goroutine 2: set value to 3

Because Modify() locks from "get value" to "set value" you'll get the correct behaviour: the second goroutine will block until the first one is done, and will use the correct value.

This is/was also the advantage of having Increment.

arp242 avatar Mar 28 '22 11:03 arp242

Initially go-cache is not thread-safe like built-in map. It's not a problem. Anyway there's sync.Mutex.

sschiz avatar Mar 28 '22 11:03 sschiz

I'm not sure if I follow? All operations should be thread-safe (in go-cache, and zcache); this is why the cache object already has a mutex. Much of the value of this comes from being a thread-safe map implementation (and the other value is the cache expiration).

arp242 avatar Mar 28 '22 11:03 arp242

I'm sorry. It was misunderstanding. You're right. Modify is a great agreement.

sschiz avatar Mar 28 '22 11:03 sschiz

Haha, okay; no worries! I updated the documentation a bit as a result because I realized it wasn't all that clear, so it was useful regardless :-)

arp242 avatar Mar 28 '22 12:03 arp242

Here you go: https://github.com/willboland/simcache

willboland avatar Aug 05 '23 21:08 willboland