Skip to content

Commit d11e039

Browse files
committed
introduce DataQuality enum
1 parent 941fee4 commit d11e039

File tree

2 files changed

+73
-68
lines changed

2 files changed

+73
-68
lines changed

internal/datasystem/store.go

Lines changed: 60 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -34,58 +34,53 @@ import (
3434
//
3535
// We have found this to almost always be undesirable for users.
3636
type Store struct {
37-
// Represents a remote store, like Redis. This is optional; if present, it's only used
38-
// before the in-memory store is initialized.
39-
persistentStore subsystems.DataStore
37+
// Represents the SDK's source of truth for flag evals before initialization, or permanently if there are
38+
// no initializers/synchronizers configured. This is option; if not defined, only the memoryStore is used.
39+
persistentStore *persistentStore
4040

41+
// Represents the SDK's source of truth for flag evaluations (once initialized). Before initialization,
42+
// the persistentStore may be used if configured.
43+
memoryStore subsystems.DataStore
44+
45+
// Points to the active store. Swapped upon initialization.
46+
active subsystems.DataStore
47+
48+
quality DataQuality
49+
50+
// Protects the availability, persistentStore, quality, and active fields.
51+
mu sync.RWMutex
52+
53+
loggers ldlog.Loggers
54+
}
55+
56+
type persistentStore struct {
57+
impl subsystems.DataStore
4158
// The persistentStore is read-only, or read-write. In read-only mode, the store
4259
// is *never* written to, and only read before the in-memory store is initialized.
4360
// This is equivalent to the concept of "daemon mode".
4461
//
4562
// In read-write mode, data from initializers/synchronizers is written to the store
4663
// as it is received. This is equivalent to the normal "persistent store" configuration
4764
// that an SDK can use to collaborate with zero or more other SDKs with a (possibly shared) database.
48-
persistentStoreMode subsystems.DataStoreMode
49-
65+
mode subsystems.DataStoreMode
5066
// This exists as a quirk of the DataSourceUpdateSink interface, which store implements. The DataSourceUpdateSink
5167
// has a method to return a DataStoreStatusProvider so that a DataSource can monitor the state of the store. This
5268
// was originally used in fdv1 to know when the store went offline/online, so that data could be committed back
5369
// to the store when it came back online. In fdv2 system, this is handled by the FDv2 struct itself, so the
5470
// data source doesn't need any knowledge of it. We can delete this piece of infrastructure when we no longer
5571
// need to support fdv1 (or we could refactor the fdv2 data sources to use a different set of interfaces that don't
5672
// require this.)
57-
persistentStoreStatusProvider interfaces.DataStoreStatusProvider
58-
59-
// Represents the store that all flag/segment data queries are served from after data is received from
60-
// initializers/synchronizers. Before the in-memory store is initialized, queries are served from the
61-
// persistentStore (if configured).
62-
memoryStore subsystems.DataStore
63-
64-
active subsystems.DataStore
65-
66-
// Whether the memoryStore's data should be considered authoritative, or fresh - that is, if it is known
67-
// to be the latest data. Data from a baked in file for example would not be considered refreshed. The purpose
68-
// of this is to know if we should commit data to the persistentStore. For example, if we initialize with "stale"
69-
// data from a local file (refreshed=false), we may not want to pollute a connected Redis database with it.
70-
// TODO: this could also be called "Authoritative". "It was the latest at some point.. that point being when we asked
71-
// if it was the latest".
72-
availability DataAvailability
73-
74-
// Protects the refreshed, persistentStore, persistentStoreMode, and active fields.
75-
mu sync.RWMutex
76-
77-
loggers ldlog.Loggers
73+
statusProvider interfaces.DataStoreStatusProvider
7874
}
7975

8076
// NewStore creates a new store. By default the store is in-memory. To add a persistent store, call SwapToPersistent. Ensure this is
8177
// called at configuration time, only once and before the store is ever accessed.
8278
func NewStore(loggers ldlog.Loggers) *Store {
8379
s := &Store{
84-
persistentStore: nil,
85-
persistentStoreMode: subsystems.DataStoreModeRead,
86-
memoryStore: datastore.NewInMemoryDataStore(loggers),
87-
availability: Defaults,
88-
loggers: loggers,
80+
persistentStore: nil,
81+
memoryStore: datastore.NewInMemoryDataStore(loggers),
82+
quality: QualityNone,
83+
loggers: loggers,
8984
}
9085
s.active = s.memoryStore
9186
return s
@@ -96,7 +91,7 @@ func (s *Store) Close() error {
9691
s.mu.Lock()
9792
defer s.mu.Unlock()
9893
if s.persistentStore != nil {
99-
return s.persistentStore.Close()
94+
return s.persistentStore.impl.Close()
10095
}
10196
return nil
10297
}
@@ -109,17 +104,10 @@ func (s *Store) getActive() subsystems.DataStore {
109104
return s.active
110105
}
111106

112-
// DataAvailability returns the status of the store's data. Defaults means there is no data, Cached means there is
113-
// data, but it's not guaranteed to be recent, and Refreshed means the data has been refreshed from the server.
114-
func (s *Store) DataAvailability() DataAvailability {
115-
s.mu.RLock()
116-
defer s.mu.RUnlock()
117-
return s.availability
118-
}
119-
120107
// Mirroring returns true data is being mirrored to a persistent store.
121108
func (s *Store) mirroring() bool {
122-
return s.persistentStore != nil && s.persistentStoreMode == subsystems.DataStoreModeReadWrite
109+
return s.persistentStore != nil && s.persistentStore.mode == subsystems.DataStoreModeReadWrite &&
110+
s.quality == QualityTrusted
123111
}
124112

125113
// nolint:revive // Standard DataSourceUpdateSink method
@@ -131,15 +119,11 @@ func (s *Store) Init(allData []ldstoretypes.Collection, payloadVersion *int) boo
131119
// TODO: handle errors from initializing the memory or persistent stores.
132120
if err := s.memoryStore.Init(allData); err == nil {
133121
s.active = s.memoryStore
134-
if payloadVersion != nil {
135-
s.availability = Refreshed
136-
} else {
137-
s.availability = Cached
138-
}
122+
s.quality = QualityTrusted
139123
}
140124

141125
if s.mirroring() {
142-
_ = s.persistentStore.Init(allData) // TODO: insert in topo-sort order
126+
_ = s.persistentStore.impl.Init(allData) // TODO: insert in topo-sort order
143127
}
144128
return true
145129
}
@@ -158,7 +142,7 @@ func (s *Store) Upsert(kind ldstoretypes.DataKind, key string, item ldstoretypes
158142
_, memErr = s.memoryStore.Upsert(kind, key, item)
159143

160144
if s.mirroring() {
161-
_, persErr = s.persistentStore.Upsert(kind, key, item)
145+
_, persErr = s.persistentStore.impl.Upsert(kind, key, item)
162146
}
163147
return memErr == nil && persErr == nil
164148
}
@@ -167,7 +151,10 @@ func (s *Store) Upsert(kind ldstoretypes.DataKind, key string, item ldstoretypes
167151
func (s *Store) GetDataStoreStatusProvider() interfaces.DataStoreStatusProvider {
168152
s.mu.RLock()
169153
defer s.mu.RUnlock()
170-
return s.persistentStoreStatusProvider
154+
if s.persistentStore == nil {
155+
return nil
156+
}
157+
return s.persistentStore.statusProvider
171158
}
172159

173160
// WithPersistence exists only because of the way the SDK's configuration builders work - we need a ClientContext
@@ -176,24 +163,23 @@ func (s *Store) GetDataStoreStatusProvider() interfaces.DataStoreStatusProvider
176163
func (s *Store) WithPersistence(persistent subsystems.DataStore, mode subsystems.DataStoreMode, statusProvider interfaces.DataStoreStatusProvider) *Store {
177164
s.mu.Lock()
178165
defer s.mu.Unlock()
179-
s.persistentStore = persistent
180-
s.persistentStoreMode = mode
181-
s.persistentStoreStatusProvider = statusProvider
182-
s.active = s.persistentStore
183-
184-
if s.persistentStore.IsInitialized() {
185-
s.availability = Cached
186-
} else {
187-
s.availability = Defaults
166+
167+
s.persistentStore = &persistentStore{
168+
impl: persistent,
169+
mode: mode,
170+
statusProvider: statusProvider,
188171
}
172+
173+
s.active = s.persistentStore.impl
174+
s.quality = QualityUntrusted
189175
return s
190176
}
191177

192178
func (s *Store) Commit() error {
193179
s.mu.RLock()
194180
defer s.mu.RUnlock()
195181

196-
if s.availability == Refreshed && s.mirroring() {
182+
if s.mirroring() {
197183
flags, err := s.memoryStore.GetAll(datakinds.Features)
198184
if err != nil {
199185
return err
@@ -202,7 +188,7 @@ func (s *Store) Commit() error {
202188
if err != nil {
203189
return err
204190
}
205-
return s.persistentStore.Init([]ldstoretypes.Collection{
191+
return s.persistentStore.impl.Init([]ldstoretypes.Collection{
206192
{Kind: datakinds.Features, Items: flags},
207193
{Kind: datakinds.Segments, Items: segments},
208194
})
@@ -221,3 +207,17 @@ func (s *Store) Get(kind ldstoretypes.DataKind, key string) (ldstoretypes.ItemDe
221207
func (s *Store) IsInitialized() bool {
222208
return s.getActive().IsInitialized()
223209
}
210+
211+
type DataQuality int
212+
213+
const (
214+
QualityNone = DataQuality(0)
215+
QualityUntrusted = DataQuality(1)
216+
QualityTrusted = DataQuality(2)
217+
)
218+
219+
func (s *Store) DataQuality() DataQuality {
220+
s.mu.RLock()
221+
defer s.mu.RUnlock()
222+
return s.quality
223+
}

internal/datasystem/store_test.go

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,11 @@ func TestStore_New(t *testing.T) {
2222
assert.NoError(t, store.Close())
2323
}
2424

25-
func TestStore_NoPersistence_NewStore_DataStatus(t *testing.T) {
25+
func TestStore_NoPersistence_NewStore_DataQuality(t *testing.T) {
2626
logCapture := ldlogtest.NewMockLog()
2727
store := NewStore(logCapture.Loggers)
2828
defer store.Close()
29-
assert.Equal(t, store.DataAvailability(), Defaults)
29+
assert.Equal(t, store.DataQuality(), QualityNone)
3030
}
3131

3232
func TestStore_NoPersistence_NewStore_IsInitialized(t *testing.T) {
@@ -36,23 +36,28 @@ func TestStore_NoPersistence_NewStore_IsInitialized(t *testing.T) {
3636
assert.False(t, store.IsInitialized())
3737
}
3838

39-
func TestStore_NoPersistence_MemoryStoreInitialized_DataStatus(t *testing.T) {
39+
func TestStore_NoPersistence_MemoryStoreInitialized_DataQualityIsTrusted(t *testing.T) {
40+
// It doesn't matter if the data has a payload version or not: the data quality should be
41+
// trusted if it came from an initializer or synchronizer.
42+
// This isn't necessarily what we want going forward, the quality should vary depending on the
43+
// initializer/synchronizer implementation.
44+
4045
version1 := 1
4146
tests := []struct {
4247
name string
4348
payloadVersion *int
44-
expected DataAvailability
49+
quality DataQuality
4550
}{
46-
{"fresh data", &version1, Refreshed},
47-
{"stale data", nil, Cached},
51+
{"fresh data", &version1, QualityTrusted},
52+
{"stale data", nil, QualityTrusted},
4853
}
4954
for _, tt := range tests {
5055
t.Run(tt.name, func(t *testing.T) {
5156
logCapture := ldlogtest.NewMockLog()
5257
store := NewStore(logCapture.Loggers)
5358
defer store.Close()
5459
store.Init([]ldstoretypes.Collection{}, tt.payloadVersion)
55-
assert.Equal(t, store.DataAvailability(), tt.expected)
60+
assert.Equal(t, store.DataQuality(), tt.quality)
5661
assert.True(t, store.IsInitialized())
5762
})
5863
}
@@ -207,7 +212,7 @@ func TestStore_Concurrency(t *testing.T) {
207212
wg.Add(1)
208213
defer wg.Done()
209214
for i := 0; i < 100; i++ {
210-
_ = store.DataAvailability()
215+
_ = store.DataQuality()
211216
time.Sleep(time.Duration(rand.Intn(10)) * time.Millisecond)
212217
}
213218
}()

0 commit comments

Comments
 (0)