Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(cache) Don't cache failed calls to resourceStore #1894

Merged
merged 6 commits into from
Apr 30, 2021
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 17 additions & 43 deletions pkg/xds/cache/cla/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,9 @@ import (

"github.com/kumahq/kuma/pkg/core/datasource"

"github.com/prometheus/client_golang/prometheus"

"github.com/kumahq/kuma/pkg/core/resources/model"
"github.com/kumahq/kuma/pkg/metrics"

"github.com/patrickmn/go-cache"

"github.com/kumahq/kuma/pkg/core"
"github.com/kumahq/kuma/pkg/core/dns/lookup"
core_mesh "github.com/kumahq/kuma/pkg/core/resources/apis/mesh"
Expand All @@ -35,13 +31,11 @@ var (
// which reconcile Dataplane's state. In scope of one mesh ClusterLoadAssignment
// will be the same for each service so no need to reconcile for each dataplane.
type Cache struct {
cache *cache.Cache
rm manager.ReadOnlyResourceManager
dsl datasource.Loader
ipFunc lookup.LookupIPFunc
zone string
onceMap *once.Map
metrics *prometheus.GaugeVec
cache *once.Cache
rm manager.ReadOnlyResourceManager
dsl datasource.Loader
ipFunc lookup.LookupIPFunc
zone string
}

func NewCache(
Expand All @@ -51,36 +45,22 @@ func NewCache(
ipFunc lookup.LookupIPFunc,
metrics metrics.Metrics,
) (*Cache, error) {
metric := prometheus.NewGaugeVec(prometheus.GaugeOpts{
Name: "cla_cache",
Help: "Summary of CLA Cache",
}, []string{"operation", "result"})
if err := metrics.Register(metric); err != nil {
c, err := once.New(expirationTime, "cla_cache", metrics)
if err != nil {
return nil, err
}
return &Cache{
cache: cache.New(expirationTime, time.Duration(int64(float64(expirationTime)*0.9))),
rm: rm,
dsl: dsl,
zone: zone,
ipFunc: ipFunc,
onceMap: once.NewMap(),
metrics: metric,
cache: c,
rm: rm,
dsl: dsl,
zone: zone,
ipFunc: ipFunc,
}, nil
}

func (c *Cache) GetCLA(ctx context.Context, meshName, meshHash, service string, apiVersion envoy_common.APIVersion) (proto.Message, error) {
key := fmt.Sprintf("%s:%s:%s:%s", apiVersion, meshName, service, meshHash)
value, found := c.cache.Get(key)
if found {
c.metrics.WithLabelValues("get", "hit").Inc()
return value.(proto.Message), nil
}
o := c.onceMap.Get(key)
c.metrics.WithLabelValues("get", "hit-wait").Inc()
o.Do(func() (interface{}, error) {
c.metrics.WithLabelValues("get", "hit-wait").Dec()
c.metrics.WithLabelValues("get", "miss").Inc()
elt, err := c.cache.Get(ctx, key, func(ctx context.Context, key string) (interface{}, error) {
dataplanes, err := topology.GetDataplanes(claCacheLog, ctx, c.rm, c.ipFunc, meshName)
if err != nil {
return nil, err
Expand All @@ -94,16 +74,10 @@ func (c *Cache) GetCLA(ctx context.Context, meshName, meshHash, service string,
return nil, err
}
endpointMap := topology.BuildEndpointMap(mesh, c.zone, dataplanes.Items, externalServices.Items, c.dsl)
cla, err := envoy_endpoints.CreateClusterLoadAssignment(service, endpointMap[service], apiVersion)
if err != nil {
return nil, err
}
c.cache.SetDefault(key, cla)
c.onceMap.Delete(key)
return cla, nil
return envoy_endpoints.CreateClusterLoadAssignment(service, endpointMap[service], apiVersion)
})
if o.Err != nil {
return nil, o.Err
if err != nil {
return nil, err
}
return o.Value.(proto.Message), nil
return elt.(proto.Message), nil
}
55 changes: 16 additions & 39 deletions pkg/xds/cache/mesh/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,10 @@ import (
"context"
"time"

"github.com/prometheus/client_golang/prometheus"

"github.com/kumahq/kuma/pkg/metrics"

"github.com/kumahq/kuma/pkg/xds/cache/once"

"github.com/patrickmn/go-cache"

"github.com/kumahq/kuma/pkg/core/dns/lookup"
"github.com/kumahq/kuma/pkg/core/resources/manager"
core_model "github.com/kumahq/kuma/pkg/core/resources/model"
Expand All @@ -21,12 +17,10 @@ import (
// reconcile Dataplane's state. Calculating hash is a heavy operation
// that requires fetching all the resources belonging to the Mesh.
type Cache struct {
cache *cache.Cache
rm manager.ReadOnlyResourceManager
types []core_model.ResourceType
ipFunc lookup.LookupIPFunc
onceMap *once.Map
metrics *prometheus.GaugeVec
cache *once.Cache
rm manager.ReadOnlyResourceManager
types []core_model.ResourceType
ipFunc lookup.LookupIPFunc
}

func NewCache(
Expand All @@ -36,45 +30,28 @@ func NewCache(
ipFunc lookup.LookupIPFunc,
metrics metrics.Metrics,
) (*Cache, error) {
metric := prometheus.NewGaugeVec(prometheus.GaugeOpts{
Name: "mesh_cache",
Help: "Summary of Mesh Cache",
}, []string{"operation", "result"})
if err := metrics.Register(metric); err != nil {
c, err := once.New(expirationTime, "mesh_cache", metrics)
if err != nil {
return nil, err
}
return &Cache{
rm: rm,
types: types,
ipFunc: ipFunc,
onceMap: once.NewMap(),
cache: cache.New(expirationTime, time.Duration(int64(float64(expirationTime)*0.9))),
metrics: metric,
rm: rm,
types: types,
ipFunc: ipFunc,
cache: c,
}, nil
}

func (c *Cache) GetHash(ctx context.Context, mesh string) (string, error) {
hash, found := c.cache.Get(mesh)
if found {
c.metrics.WithLabelValues("get", "hit").Inc()
return hash.(string), nil
}
o := c.onceMap.Get(mesh)
c.metrics.WithLabelValues("get", "hit-wait").Inc()
o.Do(func() (interface{}, error) {
c.metrics.WithLabelValues("get", "hit-wait").Dec()
c.metrics.WithLabelValues("get", "miss").Inc()
snapshot, err := GetMeshSnapshot(ctx, mesh, c.rm, c.types, c.ipFunc)
elt, err := c.cache.Get(ctx, mesh, func(ctx context.Context, key string) (interface{}, error) {
snapshot, err := GetMeshSnapshot(ctx, key, c.rm, c.types, c.ipFunc)
if err != nil {
return nil, err
}
hash = snapshot.hash()
c.cache.SetDefault(mesh, hash)
c.onceMap.Delete(mesh)
return hash, nil
return snapshot.hash(), nil
})
if o.Err != nil {
return "", o.Err
if err != nil {
return "", err
}
return o.Value.(string), nil
return elt.(string), nil
}
41 changes: 41 additions & 0 deletions pkg/xds/cache/mesh/cache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package mesh_test

import (
"context"
"errors"
"fmt"
"net"
"sync"
Expand All @@ -25,6 +26,7 @@ import (

type countingResourcesManager struct {
store core_store.ResourceStore
err error
getQueries int
listQueries int
}
Expand All @@ -33,11 +35,17 @@ var _ core_manager.ReadOnlyResourceManager = &countingResourcesManager{}

func (c *countingResourcesManager) Get(ctx context.Context, res core_model.Resource, fn ...core_store.GetOptionsFunc) error {
c.getQueries++
if c.err != nil {
return c.err
}
return c.store.Get(ctx, res, fn...)
}

func (c *countingResourcesManager) List(ctx context.Context, list core_model.ResourceList, fn ...core_store.ListOptionsFunc) error {
c.listQueries++
if c.err != nil {
return c.err
}
return c.store.List(ctx, list, fn...)
}

Expand Down Expand Up @@ -198,4 +206,37 @@ var _ = Describe("MeshSnapshot Cache", func() {
}
Expect(hitWaits + hits + 1).To(Equal(100.0))
})

It("should retry previously failed Get() requests", func() {
countingManager.err = errors.New("I want to fail")
By("getting Hash for the first time")
hash, err := meshCache.GetHash(context.Background(), "mesh-0")
Expect(countingManager.getQueries).To(Equal(1)) // should be the same
Expect(err).To(HaveOccurred())
Expect(hash).To(BeEmpty())

By("getting Hash calls again")
_, err = meshCache.GetHash(context.Background(), "mesh-0")
Expect(err).To(HaveOccurred())
Expect(countingManager.getQueries).To(Equal(2)) // should be increased by one (errors are not cached)
Expect(err).To(HaveOccurred())
Expect(hash).To(BeEmpty())

By("Getting the hash once manager is fixed")
countingManager.err = nil
hash, err = meshCache.GetHash(context.Background(), "mesh-0")
Expect(err).ToNot(HaveOccurred())
expectedHash := `Dataplane:mesh-0:dp-0:1:192.168.0.1:,Dataplane:mesh-0:dp-10:1:192.168.0.1:,Dataplane:mesh-0:dp-11:1:192.168.0.1:,Dataplane:mesh-0:dp-12:1:192.168.0.1:,Dataplane:mesh-0:dp-13:1:192.168.0.1:,Dataplane:mesh-0:dp-14:1:192.168.0.1:,Dataplane:mesh-0:dp-15:1:192.168.0.1:,Dataplane:mesh-0:dp-16:1:192.168.0.1:,Dataplane:mesh-0:dp-17:1:192.168.0.1:,Dataplane:mesh-0:dp-18:1:192.168.0.1:,Dataplane:mesh-0:dp-19:1:192.168.0.1:,Dataplane:mesh-0:dp-1:1:192.168.0.1:,Dataplane:mesh-0:dp-20:1:192.168.0.1:,Dataplane:mesh-0:dp-21:1:192.168.0.1:,Dataplane:mesh-0:dp-22:1:192.168.0.1:,Dataplane:mesh-0:dp-23:1:192.168.0.1:,Dataplane:mesh-0:dp-24:1:192.168.0.1:,Dataplane:mesh-0:dp-25:1:192.168.0.1:,Dataplane:mesh-0:dp-26:1:192.168.0.1:,Dataplane:mesh-0:dp-27:1:192.168.0.1:,Dataplane:mesh-0:dp-28:1:192.168.0.1:,Dataplane:mesh-0:dp-29:1:192.168.0.1:,Dataplane:mesh-0:dp-2:1:192.168.0.1:,Dataplane:mesh-0:dp-30:1:192.168.0.1:,Dataplane:mesh-0:dp-31:1:192.168.0.1:,Dataplane:mesh-0:dp-32:1:192.168.0.1:,Dataplane:mesh-0:dp-33:1:192.168.0.1:,Dataplane:mesh-0:dp-34:1:192.168.0.1:,Dataplane:mesh-0:dp-35:1:192.168.0.1:,Dataplane:mesh-0:dp-36:1:192.168.0.1:,Dataplane:mesh-0:dp-37:1:192.168.0.1:,Dataplane:mesh-0:dp-38:1:192.168.0.1:,Dataplane:mesh-0:dp-39:1:192.168.0.1:,Dataplane:mesh-0:dp-3:1:192.168.0.1:,Dataplane:mesh-0:dp-40:1:192.168.0.1:,Dataplane:mesh-0:dp-41:1:192.168.0.1:,Dataplane:mesh-0:dp-42:1:192.168.0.1:,Dataplane:mesh-0:dp-43:1:192.168.0.1:,Dataplane:mesh-0:dp-44:1:192.168.0.1:,Dataplane:mesh-0:dp-45:1:192.168.0.1:,Dataplane:mesh-0:dp-46:1:192.168.0.1:,Dataplane:mesh-0:dp-47:1:192.168.0.1:,Dataplane:mesh-0:dp-48:1:192.168.0.1:,Dataplane:mesh-0:dp-49:1:192.168.0.1:,Dataplane:mesh-0:dp-4:1:192.168.0.1:,Dataplane:mesh-0:dp-50:1:192.168.0.1:,Dataplane:mesh-0:dp-51:1:192.168.0.1:,Dataplane:mesh-0:dp-52:1:192.168.0.1:,Dataplane:mesh-0:dp-53:1:192.168.0.1:,Dataplane:mesh-0:dp-54:1:192.168.0.1:,Dataplane:mesh-0:dp-55:1:192.168.0.1:,Dataplane:mesh-0:dp-56:1:192.168.0.1:,Dataplane:mesh-0:dp-57:1:192.168.0.1:,Dataplane:mesh-0:dp-58:1:192.168.0.1:,Dataplane:mesh-0:dp-59:1:192.168.0.1:,Dataplane:mesh-0:dp-5:1:192.168.0.1:,Dataplane:mesh-0:dp-60:1:192.168.0.1:,Dataplane:mesh-0:dp-61:1:192.168.0.1:,Dataplane:mesh-0:dp-62:1:192.168.0.1:,Dataplane:mesh-0:dp-63:1:192.168.0.1:,Dataplane:mesh-0:dp-64:1:192.168.0.1:,Dataplane:mesh-0:dp-65:1:192.168.0.1:,Dataplane:mesh-0:dp-66:1:192.168.0.1:,Dataplane:mesh-0:dp-67:1:192.168.0.1:,Dataplane:mesh-0:dp-68:1:192.168.0.1:,Dataplane:mesh-0:dp-69:1:192.168.0.1:,Dataplane:mesh-0:dp-6:1:192.168.0.1:,Dataplane:mesh-0:dp-70:1:192.168.0.1:,Dataplane:mesh-0:dp-71:1:192.168.0.1:,Dataplane:mesh-0:dp-72:1:192.168.0.1:,Dataplane:mesh-0:dp-73:1:192.168.0.1:,Dataplane:mesh-0:dp-74:1:192.168.0.1:,Dataplane:mesh-0:dp-75:1:192.168.0.1:,Dataplane:mesh-0:dp-76:1:192.168.0.1:,Dataplane:mesh-0:dp-77:1:192.168.0.1:,Dataplane:mesh-0:dp-78:1:192.168.0.1:,Dataplane:mesh-0:dp-79:1:192.168.0.1:,Dataplane:mesh-0:dp-7:1:192.168.0.1:,Dataplane:mesh-0:dp-80:1:192.168.0.1:,Dataplane:mesh-0:dp-81:1:192.168.0.1:,Dataplane:mesh-0:dp-82:1:192.168.0.1:,Dataplane:mesh-0:dp-83:1:192.168.0.1:,Dataplane:mesh-0:dp-84:1:192.168.0.1:,Dataplane:mesh-0:dp-85:1:192.168.0.1:,Dataplane:mesh-0:dp-86:1:192.168.0.1:,Dataplane:mesh-0:dp-87:1:192.168.0.1:,Dataplane:mesh-0:dp-88:1:192.168.0.1:,Dataplane:mesh-0:dp-89:1:192.168.0.1:,Dataplane:mesh-0:dp-8:1:192.168.0.1:,Dataplane:mesh-0:dp-90:1:192.168.0.1:,Dataplane:mesh-0:dp-91:1:192.168.0.1:,Dataplane:mesh-0:dp-92:1:192.168.0.1:,Dataplane:mesh-0:dp-93:1:192.168.0.1:,Dataplane:mesh-0:dp-94:1:192.168.0.1:,Dataplane:mesh-0:dp-95:1:192.168.0.1:,Dataplane:mesh-0:dp-96:1:192.168.0.1:,Dataplane:mesh-0:dp-97:1:192.168.0.1:,Dataplane:mesh-0:dp-98:1:192.168.0.1:,Dataplane:mesh-0:dp-99:1:192.168.0.1:,Dataplane:mesh-0:dp-9:1:192.168.0.1:,Mesh::mesh-0:1,TrafficRoute:mesh-0:tr-0:1,TrafficRoute:mesh-0:tr-10:1,TrafficRoute:mesh-0:tr-11:1,TrafficRoute:mesh-0:tr-12:1,TrafficRoute:mesh-0:tr-13:1,TrafficRoute:mesh-0:tr-14:1,TrafficRoute:mesh-0:tr-15:1,TrafficRoute:mesh-0:tr-16:1,TrafficRoute:mesh-0:tr-17:1,TrafficRoute:mesh-0:tr-18:1,TrafficRoute:mesh-0:tr-19:1,TrafficRoute:mesh-0:tr-1:1,TrafficRoute:mesh-0:tr-20:1,TrafficRoute:mesh-0:tr-21:1,TrafficRoute:mesh-0:tr-22:1,TrafficRoute:mesh-0:tr-23:1,TrafficRoute:mesh-0:tr-24:1,TrafficRoute:mesh-0:tr-25:1,TrafficRoute:mesh-0:tr-26:1,TrafficRoute:mesh-0:tr-27:1,TrafficRoute:mesh-0:tr-28:1,TrafficRoute:mesh-0:tr-29:1,TrafficRoute:mesh-0:tr-2:1,TrafficRoute:mesh-0:tr-30:1,TrafficRoute:mesh-0:tr-31:1,TrafficRoute:mesh-0:tr-32:1,TrafficRoute:mesh-0:tr-33:1,TrafficRoute:mesh-0:tr-34:1,TrafficRoute:mesh-0:tr-35:1,TrafficRoute:mesh-0:tr-36:1,TrafficRoute:mesh-0:tr-37:1,TrafficRoute:mesh-0:tr-38:1,TrafficRoute:mesh-0:tr-39:1,TrafficRoute:mesh-0:tr-3:1,TrafficRoute:mesh-0:tr-40:1,TrafficRoute:mesh-0:tr-41:1,TrafficRoute:mesh-0:tr-42:1,TrafficRoute:mesh-0:tr-43:1,TrafficRoute:mesh-0:tr-44:1,TrafficRoute:mesh-0:tr-45:1,TrafficRoute:mesh-0:tr-46:1,TrafficRoute:mesh-0:tr-47:1,TrafficRoute:mesh-0:tr-48:1,TrafficRoute:mesh-0:tr-49:1,TrafficRoute:mesh-0:tr-4:1,TrafficRoute:mesh-0:tr-50:1,TrafficRoute:mesh-0:tr-51:1,TrafficRoute:mesh-0:tr-52:1,TrafficRoute:mesh-0:tr-53:1,TrafficRoute:mesh-0:tr-54:1,TrafficRoute:mesh-0:tr-55:1,TrafficRoute:mesh-0:tr-56:1,TrafficRoute:mesh-0:tr-57:1,TrafficRoute:mesh-0:tr-58:1,TrafficRoute:mesh-0:tr-59:1,TrafficRoute:mesh-0:tr-5:1,TrafficRoute:mesh-0:tr-60:1,TrafficRoute:mesh-0:tr-61:1,TrafficRoute:mesh-0:tr-62:1,TrafficRoute:mesh-0:tr-63:1,TrafficRoute:mesh-0:tr-64:1,TrafficRoute:mesh-0:tr-65:1,TrafficRoute:mesh-0:tr-66:1,TrafficRoute:mesh-0:tr-67:1,TrafficRoute:mesh-0:tr-68:1,TrafficRoute:mesh-0:tr-69:1,TrafficRoute:mesh-0:tr-6:1,TrafficRoute:mesh-0:tr-70:1,TrafficRoute:mesh-0:tr-71:1,TrafficRoute:mesh-0:tr-72:1,TrafficRoute:mesh-0:tr-73:1,TrafficRoute:mesh-0:tr-74:1,TrafficRoute:mesh-0:tr-75:1,TrafficRoute:mesh-0:tr-76:1,TrafficRoute:mesh-0:tr-77:1,TrafficRoute:mesh-0:tr-78:1,TrafficRoute:mesh-0:tr-79:1,TrafficRoute:mesh-0:tr-7:1,TrafficRoute:mesh-0:tr-80:1,TrafficRoute:mesh-0:tr-81:1,TrafficRoute:mesh-0:tr-82:1,TrafficRoute:mesh-0:tr-83:1,TrafficRoute:mesh-0:tr-84:1,TrafficRoute:mesh-0:tr-85:1,TrafficRoute:mesh-0:tr-86:1,TrafficRoute:mesh-0:tr-87:1,TrafficRoute:mesh-0:tr-88:1,TrafficRoute:mesh-0:tr-89:1,TrafficRoute:mesh-0:tr-8:1,TrafficRoute:mesh-0:tr-90:1,TrafficRoute:mesh-0:tr-91:1,TrafficRoute:mesh-0:tr-92:1,TrafficRoute:mesh-0:tr-93:1,TrafficRoute:mesh-0:tr-94:1,TrafficRoute:mesh-0:tr-95:1,TrafficRoute:mesh-0:tr-96:1,TrafficRoute:mesh-0:tr-97:1,TrafficRoute:mesh-0:tr-98:1,TrafficRoute:mesh-0:tr-99:1,TrafficRoute:mesh-0:tr-9:1`
Expect(hash).To(Equal(expectedHash))
Expect(countingManager.getQueries).To(Equal(3)) // one Get to obtain Mesh
Expect(countingManager.listQueries).To(Equal(2)) // 2 List to fetch Dataplanes and TrafficRoutes

By("Now it should cache the hash once manager is fixed")
countingManager.err = nil
hash, err = meshCache.GetHash(context.Background(), "mesh-0")
Expect(err).ToNot(HaveOccurred())
Expect(hash).To(Equal(expectedHash))
Expect(countingManager.getQueries).To(Equal(3)) // one Get to obtain Mesh
Expect(countingManager.listQueries).To(Equal(2)) // 2 List to fetch Dataplanes and TrafficRoutes
})
})
58 changes: 58 additions & 0 deletions pkg/xds/cache/once/cache.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package once

import (
"context"
"time"

"github.com/patrickmn/go-cache"
"github.com/prometheus/client_golang/prometheus"

"github.com/kumahq/kuma/pkg/metrics"
)

type Cache struct {
cache *cache.Cache
onceMap *omap
metrics *prometheus.GaugeVec
}

func New(expirationTime time.Duration, name string, metrics metrics.Metrics) (*Cache, error) {
metric := prometheus.NewGaugeVec(prometheus.GaugeOpts{
Name: name,
Help: "Summary of " + name,
}, []string{"operation", "result"})
if err := metrics.Register(metric); err != nil {
return nil, err
}
return &Cache{
cache: cache.New(expirationTime, time.Duration(int64(float64(expirationTime)*0.9))),
onceMap: newMap(),
metrics: metric,
}, nil
}

func (c *Cache) Get(ctx context.Context, key string, fn func(context.Context, string) (interface{}, error)) (interface{}, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, there was duplication indeed, but it's quite problematic to introduce a well-understandable and reusable abstraction here. I don't mind going with this once.Cache abstraction, but we have to do something with the interface because it's really unclear what Get does.

Maybe we can introduce new types:

type Key string
type Value interface{}
type Refresher func(context.Context, Key) (Value, error)

and rename Get to GetOrRefresh so it will look like:

func (c *Cache) GetOrRefresh(ctx context.Context, key Key, refresher Refresher) (Value, error)

and document this method with a comment something like:

// GetOrRefresh method returns cached Value if it was created within the last `expirationTime` time period.
// If the Value is older than `expirationTime` then Refresher will be launched and will update the Value.
// Regardless of the number of goroutines that have run into the expired Value, Refresher will be called only
// once, all goroutines will be blocked until the new Value returned by Refresher.
func (c *Cache) GetOrRefresh(ctx context.Context, k Key, r Refresher) (Value, error) {

WDYT?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good!
How about GetOrCompute() as Refresh sounds like it will only work when the item has been in the cache?

For the Key what's the motivation for adding the subtype?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The main reason for questioning the introduction of subtypes is that the underlying lib go-cache uses string as key and interface{} as value.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the end went for GetOrRetrieve and didn't add subtypes for key and value. Let me know what you think.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like GetOrRetrieve 👍

Subtypes help to make the signature more readable. If you define it like:

type FetcherFunc func(context.Context, string) (interface{}, error)

then it's not clear that FetcherFunc takes key as a parameter and returns value.

Also, interface{} is used twice - as a return value of GetOrRetrieve and as a return value of FetcherFunc. I think it makes sense to show that those interface{} are the same type. For example, if in the future we would like to introduce a new type for Value.

But I don't mind going with just string and interface{}

v, found := c.cache.Get(key)
if found {
c.metrics.WithLabelValues("get", "hit").Inc()
return v, nil
}
o := c.onceMap.Get(key)
c.metrics.WithLabelValues("get", "hit-wait").Inc()
o.Do(func() (interface{}, error) {
defer c.onceMap.Delete(key)
c.metrics.WithLabelValues("get", "hit-wait").Dec()
c.metrics.WithLabelValues("get", "miss").Inc()
res, err := fn(ctx, key)
if err != nil {
c.metrics.WithLabelValues("get", "error").Inc()
return nil, err
}
c.cache.SetDefault(key, res)
return res, nil
})
if o.Err != nil {
return "", o.Err
}
return o.Value, nil
}
Loading