github.com/openfga/openfga@v1.5.4-rc1/pkg/storage/storagewrappers/caching.go (about) 1 package storagewrappers 2 3 import ( 4 "context" 5 "fmt" 6 "time" 7 8 "github.com/karlseguin/ccache/v3" 9 openfgav1 "github.com/openfga/api/proto/openfga/v1" 10 "golang.org/x/sync/singleflight" 11 12 "github.com/openfga/openfga/pkg/storage" 13 ) 14 15 const ttl = time.Hour * 168 16 17 var _ storage.OpenFGADatastore = (*cachedOpenFGADatastore)(nil) 18 19 type cachedOpenFGADatastore struct { 20 storage.OpenFGADatastore 21 lookupGroup singleflight.Group 22 cache *ccache.Cache[*openfgav1.AuthorizationModel] 23 } 24 25 // NewCachedOpenFGADatastore returns a wrapper over a datastore that caches up to maxSize 26 // [*openfgav1.AuthorizationModel] on every call to storage.ReadAuthorizationModel. 27 // It caches with unlimited TTL because models are immutable. It uses LRU for eviction. 28 func NewCachedOpenFGADatastore(inner storage.OpenFGADatastore, maxSize int) *cachedOpenFGADatastore { 29 return &cachedOpenFGADatastore{ 30 OpenFGADatastore: inner, 31 cache: ccache.New(ccache.Configure[*openfgav1.AuthorizationModel]().MaxSize(int64(maxSize))), 32 } 33 } 34 35 // ReadAuthorizationModel reads the model corresponding to store and model ID. 36 func (c *cachedOpenFGADatastore) ReadAuthorizationModel(ctx context.Context, storeID, modelID string) (*openfgav1.AuthorizationModel, error) { 37 cacheKey := fmt.Sprintf("%s:%s", storeID, modelID) 38 cachedEntry := c.cache.Get(cacheKey) 39 40 if cachedEntry != nil { 41 return cachedEntry.Value(), nil 42 } 43 44 model, err := c.OpenFGADatastore.ReadAuthorizationModel(ctx, storeID, modelID) 45 if err != nil { 46 return nil, err 47 } 48 49 c.cache.Set(cacheKey, model, ttl) // These are immutable, once created, there cannot be edits, therefore they can be cached without ttl. 50 51 return model, nil 52 } 53 54 // FindLatestAuthorizationModel see [storage.AuthorizationModelReadBackend].FindLatestAuthorizationModel. 55 func (c *cachedOpenFGADatastore) FindLatestAuthorizationModel(ctx context.Context, storeID string) (*openfgav1.AuthorizationModel, error) { 56 v, err, _ := c.lookupGroup.Do(fmt.Sprintf("FindLatestAuthorizationModel:%s", storeID), func() (interface{}, error) { 57 return c.OpenFGADatastore.FindLatestAuthorizationModel(ctx, storeID) 58 }) 59 if err != nil { 60 return nil, err 61 } 62 return v.(*openfgav1.AuthorizationModel), nil 63 } 64 65 // Close closes the datastore and cleans up any residual resources. 66 func (c *cachedOpenFGADatastore) Close() { 67 c.cache.Stop() 68 c.OpenFGADatastore.Close() 69 }