Skip to content

Commit bce33c8

Browse files
committed
Support LiveEventList fields in Satori cache
1 parent 6a15ff7 commit bce33c8

File tree

3 files changed

+84
-51
lines changed

3 files changed

+84
-51
lines changed

internal/satori/satori.go

Lines changed: 28 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,9 @@ import (
4242

4343
const satoriCacheCleanupInterval = 5 * time.Second
4444

45-
type satoriCache[T any] interface {
46-
Get(ctx context.Context, userID string, names, labels []string) (values []T, missingNames, missingLabels []string)
47-
Add(ctx context.Context, userID string, names, labels []string, values map[string]T)
45+
type satoriCache[T any, O comparable] interface {
46+
Get(ctx context.Context, userID string, names, labels []string, optFilters ...O) (values []T, missingNames, missingLabels []string)
47+
Add(ctx context.Context, userID string, names, labels []string, values map[string]T, optFilters ...O)
4848
SetAll(ctx context.Context, userID string, values map[string]T)
4949
}
5050

@@ -66,10 +66,10 @@ type SatoriClient struct {
6666
cacheEnabled bool
6767
propertiesCacheMutex sync.RWMutex
6868
propertiesCache map[context.Context]*runtime.Properties
69-
flagsCache satoriCache[flagCacheEntry]
70-
flagsOverridesCache satoriCache[flagOverridesCacheEntry]
71-
liveEventsCache satoriCache[*runtime.LiveEvent]
72-
experimentsCache satoriCache[*runtime.Experiment]
69+
flagsCache satoriCache[flagCacheEntry, struct{}]
70+
flagsOverridesCache satoriCache[flagOverridesCacheEntry, struct{}]
71+
liveEventsCache satoriCache[*runtime.LiveEvent, liveEventFilters]
72+
experimentsCache satoriCache[*runtime.Experiment, struct{}]
7373
}
7474

7575
func NewSatoriClient(ctx context.Context, logger *zap.Logger, satoriUrl, apiKeyName, apiKey, serverKey, signingKey string, nakamaTokenExpirySec, httpTimeoutMs int64, cacheEnabled bool, cacheMode string, cacheTTLSec int64) *SatoriClient {
@@ -92,25 +92,25 @@ func NewSatoriClient(ctx context.Context, logger *zap.Logger, satoriUrl, apiKeyN
9292
cacheEnabled: cacheEnabled,
9393
propertiesCacheMutex: sync.RWMutex{},
9494
propertiesCache: make(map[context.Context]*runtime.Properties),
95-
flagsCache: newSatoriContextCache[flagCacheEntry](ctx, cacheEnabled),
96-
flagsOverridesCache: newSatoriContextCache[flagOverridesCacheEntry](ctx, cacheEnabled),
97-
liveEventsCache: newSatoriContextCache[*runtime.LiveEvent](ctx, cacheEnabled),
98-
experimentsCache: newSatoriContextCache[*runtime.Experiment](ctx, cacheEnabled),
95+
flagsCache: newSatoriContextCache[flagCacheEntry, struct{}](ctx, cacheEnabled),
96+
flagsOverridesCache: newSatoriContextCache[flagOverridesCacheEntry, struct{}](ctx, cacheEnabled),
97+
liveEventsCache: newSatoriContextCache[*runtime.LiveEvent, liveEventFilters](ctx, cacheEnabled),
98+
experimentsCache: newSatoriContextCache[*runtime.Experiment, struct{}](ctx, cacheEnabled),
9999
}
100100

101101
switch cacheMode {
102102
case "time":
103-
sc.flagsCache = newSatoriTimeCache[flagCacheEntry](ctx, cacheEnabled, time.Duration(cacheTTLSec)*time.Second)
104-
sc.flagsOverridesCache = newSatoriTimeCache[flagOverridesCacheEntry](ctx, cacheEnabled, time.Duration(cacheTTLSec)*time.Second)
105-
sc.liveEventsCache = newSatoriTimeCache[*runtime.LiveEvent](ctx, cacheEnabled, time.Duration(cacheTTLSec)*time.Second)
106-
sc.experimentsCache = newSatoriTimeCache[*runtime.Experiment](ctx, cacheEnabled, time.Duration(cacheTTLSec)*time.Second)
103+
sc.flagsCache = newSatoriTimeCache[flagCacheEntry, struct{}](ctx, cacheEnabled, time.Duration(cacheTTLSec)*time.Second)
104+
sc.flagsOverridesCache = newSatoriTimeCache[flagOverridesCacheEntry, struct{}](ctx, cacheEnabled, time.Duration(cacheTTLSec)*time.Second)
105+
sc.liveEventsCache = newSatoriTimeCache[*runtime.LiveEvent, liveEventFilters](ctx, cacheEnabled, time.Duration(cacheTTLSec)*time.Second)
106+
sc.experimentsCache = newSatoriTimeCache[*runtime.Experiment, struct{}](ctx, cacheEnabled, time.Duration(cacheTTLSec)*time.Second)
107107
case "context":
108108
fallthrough
109109
default:
110-
sc.flagsCache = newSatoriContextCache[flagCacheEntry](ctx, cacheEnabled)
111-
sc.flagsOverridesCache = newSatoriContextCache[flagOverridesCacheEntry](ctx, cacheEnabled)
112-
sc.liveEventsCache = newSatoriContextCache[*runtime.LiveEvent](ctx, cacheEnabled)
113-
sc.experimentsCache = newSatoriContextCache[*runtime.Experiment](ctx, cacheEnabled)
110+
sc.flagsCache = newSatoriContextCache[flagCacheEntry, struct{}](ctx, cacheEnabled)
111+
sc.flagsOverridesCache = newSatoriContextCache[flagOverridesCacheEntry, struct{}](ctx, cacheEnabled)
112+
sc.liveEventsCache = newSatoriContextCache[*runtime.LiveEvent, liveEventFilters](ctx, cacheEnabled)
113+
sc.experimentsCache = newSatoriContextCache[*runtime.Experiment, struct{}](ctx, cacheEnabled)
114114
}
115115

116116
if sc.urlString == "" && sc.apiKeyName == "" && sc.apiKey == "" && sc.signingKey == "" {
@@ -947,7 +947,14 @@ func (s *SatoriClient) LiveEventsList(ctx context.Context, id string, names, lab
947947
return nil, status.Errorf(codes.InvalidArgument, "start_time_sec and end_time_sec must be greater than 0")
948948
}
949949

950-
entry, missingNames, missingLabels := s.liveEventsCache.Get(ctx, id, names, labels)
950+
optFilters := liveEventFilters{
951+
pastRunCount: pastRunCount,
952+
futureRunCount: futureRunCount,
953+
startTimeSec: startTimeSec,
954+
endTimeSec: endTimeSec,
955+
}
956+
957+
entry, missingNames, missingLabels := s.liveEventsCache.Get(ctx, id, names, labels, optFilters)
951958

952959
if !s.cacheEnabled || entry == nil || len(missingNames) > 0 || len(missingLabels) > 0 {
953960
url := s.url.JoinPath("/v1/live-event").String()
@@ -1015,11 +1022,7 @@ func (s *SatoriClient) LiveEventsList(ctx context.Context, id string, names, lab
10151022
entry = append(entry, le)
10161023
}
10171024

1018-
if len(names) > 0 {
1019-
s.liveEventsCache.Add(ctx, id, names, labels, entries)
1020-
} else {
1021-
s.liveEventsCache.SetAll(ctx, id, entries)
1022-
}
1025+
s.liveEventsCache.Add(ctx, id, names, labels, entries, optFilters)
10231026

10241027
return liveEvents, nil
10251028
default:

internal/satori/satori_context_cache.go

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,31 +9,39 @@ import (
99
"github.com/heroiclabs/nakama-common/runtime"
1010
)
1111

12-
var _ satoriCache[runtime.SatoriLabeled] = (*satoriContextCache[runtime.SatoriLabeled])(nil)
12+
var _ satoriCache[runtime.SatoriLabeled, struct{}] = (*satoriContextCache[runtime.SatoriLabeled, struct{}])(nil)
1313

14-
type satoriContextCacheEntry[T runtime.SatoriLabeled] struct {
14+
type liveEventFilters struct {
15+
startTimeSec int64
16+
endTimeSec int64
17+
pastRunCount int32
18+
futureRunCount int32
19+
}
20+
21+
type satoriContextCacheEntry[T runtime.SatoriLabeled, O comparable] struct {
1522
containsAll bool
1623
names map[string]struct{}
1724
labels map[string]struct{}
25+
optFilter O
1826
entryData map[string]T
1927
}
2028

21-
type satoriContextCache[T runtime.SatoriLabeled] struct {
29+
type satoriContextCache[T runtime.SatoriLabeled, O comparable] struct {
2230
sync.RWMutex
2331
enabled bool
24-
entries map[context.Context]*satoriContextCacheEntry[T]
32+
entries map[context.Context]*satoriContextCacheEntry[T, O]
2533
}
2634

27-
func newSatoriContextCache[T runtime.SatoriLabeled](ctx context.Context, enabled bool) *satoriContextCache[T] {
35+
func newSatoriContextCache[T runtime.SatoriLabeled, O comparable](ctx context.Context, enabled bool) *satoriContextCache[T, O] {
2836
if !enabled {
29-
return &satoriContextCache[T]{
37+
return &satoriContextCache[T, O]{
3038
enabled: false,
3139
}
3240
}
3341

34-
sc := &satoriContextCache[T]{
42+
sc := &satoriContextCache[T, O]{
3543
enabled: true,
36-
entries: make(map[context.Context]*satoriContextCacheEntry[T]),
44+
entries: make(map[context.Context]*satoriContextCacheEntry[T, O]),
3745
}
3846

3947
go func() {
@@ -58,14 +66,21 @@ func newSatoriContextCache[T runtime.SatoriLabeled](ctx context.Context, enabled
5866
return sc
5967
}
6068

61-
func (s *satoriContextCache[T]) Get(ctx context.Context, userID string, names, labels []string) (values []T, missingNames, missingLabels []string) {
69+
func (s *satoriContextCache[T, O]) Get(ctx context.Context, userID string, names, labels []string, optFilters ...O) (values []T, missingNames, missingLabels []string) {
6270
if !s.enabled {
6371
return nil, names, labels
6472
}
6573
s.RLock()
6674
entry, found := s.entries[ctx]
6775
defer s.RUnlock()
6876

77+
if found && len(optFilters) > 0 {
78+
if entry.optFilter != optFilters[0] {
79+
// The cached entry exists but was created with different optional filters, force a cache miss.
80+
return nil, names, labels
81+
}
82+
}
83+
6984
if !found || (len(names) == 0 && len(labels) == 0 && !entry.containsAll) {
7085
// Asked for all keys, but they were never fetched, or no cache entries exist for the context.
7186
// There may be a partially available data set locally, but it's hard to know the scope of
@@ -177,14 +192,14 @@ func (s *satoriContextCache[T]) Get(ctx context.Context, userID string, names, l
177192
return values, missingNames, missingLabels
178193
}
179194

180-
func (s *satoriContextCache[T]) Add(ctx context.Context, userID string, names, labels []string, values map[string]T) {
195+
func (s *satoriContextCache[T, O]) Add(ctx context.Context, userID string, names, labels []string, values map[string]T, optFilters ...O) {
181196
if !s.enabled {
182197
return
183198
}
184199
s.Lock()
185200
entry, ok := s.entries[ctx]
186201
if !ok {
187-
entry = &satoriContextCacheEntry[T]{
202+
entry = &satoriContextCacheEntry[T, O]{
188203
containsAll: false,
189204
names: map[string]struct{}{},
190205
labels: map[string]struct{}{},
@@ -199,16 +214,20 @@ func (s *satoriContextCache[T]) Add(ctx context.Context, userID string, names, l
199214
entry.labels[label] = struct{}{}
200215
}
201216
maps.Copy(entry.entryData, values)
217+
if len(optFilters) > 0 {
218+
entry.optFilter = optFilters[0]
219+
}
220+
202221
s.Unlock()
203222
}
204223

205-
func (s *satoriContextCache[T]) SetAll(ctx context.Context, userID string, values map[string]T) {
224+
func (s *satoriContextCache[T, O]) SetAll(ctx context.Context, userID string, values map[string]T) {
206225
if !s.enabled {
207226
return
208227
}
209228

210229
s.Lock()
211-
s.entries[ctx] = &satoriContextCacheEntry[T]{
230+
s.entries[ctx] = &satoriContextCacheEntry[T, O]{
212231
containsAll: true,
213232
entryData: values,
214233
}

internal/satori/satori_time_cache.go

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,33 +9,34 @@ import (
99
"github.com/heroiclabs/nakama-common/runtime"
1010
)
1111

12-
var _ satoriCache[runtime.SatoriLabeled] = (*satoriTimeCache[runtime.SatoriLabeled])(nil)
12+
var _ satoriCache[runtime.SatoriLabeled, struct{}] = (*satoriTimeCache[runtime.SatoriLabeled, struct{}])(nil)
1313

14-
type satoriTimeCacheEntry[T runtime.SatoriLabeled] struct {
14+
type satoriTimeCacheEntry[T runtime.SatoriLabeled, O comparable] struct {
1515
containsAll bool
1616
names map[string]struct{}
1717
labels map[string]struct{}
18+
optFilter O
1819
entryData map[string]T
1920
expiryTime time.Time
2021
}
2122

22-
type satoriTimeCache[T runtime.SatoriLabeled] struct {
23+
type satoriTimeCache[T runtime.SatoriLabeled, O comparable] struct {
2324
sync.RWMutex
2425
enabled bool
25-
entries map[string]*satoriTimeCacheEntry[T]
26+
entries map[string]*satoriTimeCacheEntry[T, O]
2627
ttl time.Duration
2728
}
2829

29-
func newSatoriTimeCache[T runtime.SatoriLabeled](ctx context.Context, enabled bool, ttl time.Duration) *satoriTimeCache[T] {
30+
func newSatoriTimeCache[T runtime.SatoriLabeled, O comparable](ctx context.Context, enabled bool, ttl time.Duration) *satoriTimeCache[T, O] {
3031
if !enabled {
31-
return &satoriTimeCache[T]{
32+
return &satoriTimeCache[T, O]{
3233
enabled: false,
3334
}
3435
}
3536

36-
sc := &satoriTimeCache[T]{
37+
sc := &satoriTimeCache[T, O]{
3738
enabled: true,
38-
entries: make(map[string]*satoriTimeCacheEntry[T]),
39+
entries: make(map[string]*satoriTimeCacheEntry[T, O]),
3940
ttl: ttl,
4041
}
4142

@@ -61,7 +62,7 @@ func newSatoriTimeCache[T runtime.SatoriLabeled](ctx context.Context, enabled bo
6162
return sc
6263
}
6364

64-
func (s *satoriTimeCache[T]) Get(ctx context.Context, userID string, names, labels []string) (values []T, missingNames, missingLabels []string) {
65+
func (s *satoriTimeCache[T, O]) Get(ctx context.Context, userID string, names, labels []string, optFilters ...O) (values []T, missingNames, missingLabels []string) {
6566
if !s.enabled {
6667
return nil, names, labels
6768
}
@@ -72,6 +73,13 @@ func (s *satoriTimeCache[T]) Get(ctx context.Context, userID string, names, labe
7273
entry, found := s.entries[userID]
7374
defer s.RUnlock()
7475

76+
if found && len(optFilters) > 0 {
77+
if entry.optFilter != optFilters[0] {
78+
// The cached entry exists but was created with different optional filters, force a cache miss.
79+
return nil, names, labels
80+
}
81+
}
82+
7583
if !found || t.After(entry.expiryTime) || (len(names) == 0 && len(labels) == 0 && !entry.containsAll) {
7684
// Asked for all keys, but they were never fetched, or no cache entries exist for the context.
7785
// There may be a partially available data set locally, but it's hard to know the scope of
@@ -183,7 +191,7 @@ func (s *satoriTimeCache[T]) Get(ctx context.Context, userID string, names, labe
183191
return values, missingNames, missingLabels
184192
}
185193

186-
func (s *satoriTimeCache[T]) Add(ctx context.Context, userID string, names, labels []string, values map[string]T) {
194+
func (s *satoriTimeCache[T, O]) Add(ctx context.Context, userID string, names, labels []string, values map[string]T, optFilters ...O) {
187195
if !s.enabled {
188196
return
189197
}
@@ -193,7 +201,7 @@ func (s *satoriTimeCache[T]) Add(ctx context.Context, userID string, names, labe
193201
s.Lock()
194202
entry, ok := s.entries[userID]
195203
if !ok || t.After(entry.expiryTime) {
196-
entry = &satoriTimeCacheEntry[T]{
204+
entry = &satoriTimeCacheEntry[T, O]{
197205
containsAll: false,
198206
names: map[string]struct{}{},
199207
labels: map[string]struct{}{},
@@ -209,18 +217,21 @@ func (s *satoriTimeCache[T]) Add(ctx context.Context, userID string, names, labe
209217
entry.labels[label] = struct{}{}
210218
}
211219
maps.Copy(entry.entryData, values)
220+
if len(optFilters) > 0 {
221+
entry.optFilter = optFilters[0]
222+
}
212223
s.Unlock()
213224
}
214225

215-
func (s *satoriTimeCache[T]) SetAll(ctx context.Context, userID string, values map[string]T) {
226+
func (s *satoriTimeCache[T, O]) SetAll(ctx context.Context, userID string, values map[string]T) {
216227
if !s.enabled {
217228
return
218229
}
219230

220231
t := time.Now().UTC()
221232

222233
s.Lock()
223-
s.entries[userID] = &satoriTimeCacheEntry[T]{
234+
s.entries[userID] = &satoriTimeCacheEntry[T, O]{
224235
containsAll: true,
225236
entryData: values,
226237
expiryTime: t.Add(s.ttl),

0 commit comments

Comments
 (0)