Skip to content

Commit 74df0a7

Browse files
committed
remove the dual map for the models store, and rely on linear search when looking up the model by object name
1 parent 119cee0 commit 74df0a7

File tree

5 files changed

+57
-108
lines changed

5 files changed

+57
-108
lines changed

pkg/epp/controller/inferencemodel_reconciler.go

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -85,17 +85,15 @@ func (c *InferenceModelReconciler) Reconcile(ctx context.Context, req ctrl.Reque
8585
func (c *InferenceModelReconciler) handleModelDeleted(ctx context.Context, req types.NamespacedName) error {
8686
logger := log.FromContext(ctx)
8787

88-
// We will lookup the modelName associated with this object to search for
89-
// other instance referencing the same ModelName if exist to store the oldest in
88+
// We will lookup and delete the modelName associated with this object, and search for
89+
// other instances referencing the same modelName if exist, and store the oldest in
9090
// its place. This ensures that the InferenceModel with the oldest creation
9191
// timestamp is active.
92-
existing, exists := c.Datastore.ModelGetByObjName(req)
92+
existing, exists := c.Datastore.ModelDelete(req)
9393
if !exists {
9494
// No entry exists in the first place, nothing to do.
9595
return nil
9696
}
97-
// Delete the internal object, it may be replaced with another version below.
98-
c.Datastore.ModelDelete(req)
9997
logger.Info("InferenceModel removed from datastore", "poolRef", existing.Spec.PoolRef, "modelName", existing.Spec.ModelName)
10098

10199
// List all InferenceModels with a matching ModelName.

pkg/epp/datastore/datastore.go

Lines changed: 44 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,8 @@ type Datastore interface {
4646

4747
// InferenceModel operations
4848
ModelSetIfOlder(infModel *v1alpha2.InferenceModel) bool
49-
ModelGetByModelName(modelName string) (*v1alpha2.InferenceModel, bool)
50-
ModelGetByObjName(namespacedName types.NamespacedName) (*v1alpha2.InferenceModel, bool)
51-
ModelDelete(namespacedName types.NamespacedName)
49+
ModelGet(modelName string) (*v1alpha2.InferenceModel, bool)
50+
ModelDelete(namespacedName types.NamespacedName) (*v1alpha2.InferenceModel, bool)
5251
ModelGetAll() []*v1alpha2.InferenceModel
5352

5453
// PodMetrics operations
@@ -67,11 +66,9 @@ type Datastore interface {
6766

6867
func NewDatastore() Datastore {
6968
store := &datastore{
70-
poolMu: sync.RWMutex{},
71-
modelsMu: sync.RWMutex{},
72-
modelsByModelName: make(map[string]*v1alpha2.InferenceModel),
73-
modelsByObjName: make(map[types.NamespacedName]*v1alpha2.InferenceModel),
74-
pods: &sync.Map{},
69+
poolAndModelsMu: sync.RWMutex{},
70+
models: make(map[string]*v1alpha2.InferenceModel),
71+
pods: &sync.Map{},
7572
}
7673
return store
7774
}
@@ -97,127 +94,102 @@ func NewFakeDatastore(pods []*PodMetrics, models []*v1alpha2.InferenceModel, poo
9794
}
9895

9996
type datastore struct {
100-
// poolMu is used to synchronize access to the inferencePool.
101-
poolMu sync.RWMutex
102-
pool *v1alpha2.InferencePool
103-
modelsMu sync.RWMutex
104-
// key: types.NamespacedName, value: *InferenceModel
105-
modelsByObjName map[types.NamespacedName]*v1alpha2.InferenceModel
97+
// poolAndModelsMu is used to synchronize access to pool and the models map.
98+
poolAndModelsMu sync.RWMutex
99+
pool *v1alpha2.InferencePool
106100
// key: InferenceModel.Spec.ModelName, value: *InferenceModel
107-
modelsByModelName map[string]*v1alpha2.InferenceModel
101+
models map[string]*v1alpha2.InferenceModel
108102
// key: types.NamespacedName, value: *PodMetrics
109103
pods *sync.Map
110104
}
111105

112106
func (ds *datastore) Clear() {
113-
ds.poolMu.Lock()
114-
defer ds.poolMu.Unlock()
107+
ds.poolAndModelsMu.Lock()
108+
defer ds.poolAndModelsMu.Unlock()
115109
ds.pool = nil
116-
ds.modelsMu.Lock()
117-
ds.modelsByModelName = make(map[string]*v1alpha2.InferenceModel)
118-
ds.modelsByObjName = make(map[types.NamespacedName]*v1alpha2.InferenceModel)
119-
ds.modelsMu.Unlock()
110+
ds.models = make(map[string]*v1alpha2.InferenceModel)
120111
ds.pods.Clear()
121112
}
122113

123114
// /// InferencePool APIs ///
124115
func (ds *datastore) PoolSet(pool *v1alpha2.InferencePool) {
125-
ds.poolMu.Lock()
126-
defer ds.poolMu.Unlock()
116+
ds.poolAndModelsMu.Lock()
117+
defer ds.poolAndModelsMu.Unlock()
127118
ds.pool = pool
128119
}
129120

130121
func (ds *datastore) PoolGet() (*v1alpha2.InferencePool, error) {
131-
ds.poolMu.RLock()
132-
defer ds.poolMu.RUnlock()
122+
ds.poolAndModelsMu.RLock()
123+
defer ds.poolAndModelsMu.RUnlock()
133124
if !ds.PoolHasSynced() {
134125
return nil, errPoolNotSynced
135126
}
136127
return ds.pool, nil
137128
}
138129

139130
func (ds *datastore) PoolHasSynced() bool {
140-
ds.poolMu.RLock()
141-
defer ds.poolMu.RUnlock()
131+
ds.poolAndModelsMu.RLock()
132+
defer ds.poolAndModelsMu.RUnlock()
142133
return ds.pool != nil
143134
}
144135

145136
func (ds *datastore) PoolLabelsMatch(podLabels map[string]string) bool {
146-
ds.poolMu.RLock()
147-
defer ds.poolMu.RUnlock()
137+
ds.poolAndModelsMu.RLock()
138+
defer ds.poolAndModelsMu.RUnlock()
148139
poolSelector := selectorFromInferencePoolSelector(ds.pool.Spec.Selector)
149140
podSet := labels.Set(podLabels)
150141
return poolSelector.Matches(podSet)
151142
}
152143

153144
// /// InferenceModel APIs ///
154145
func (ds *datastore) ModelSetIfOlder(infModel *v1alpha2.InferenceModel) bool {
155-
ds.modelsMu.Lock()
156-
defer ds.modelsMu.Unlock()
146+
ds.poolAndModelsMu.Lock()
147+
defer ds.poolAndModelsMu.Unlock()
157148

158149
// Check first if the existing model is older.
159150
// One exception is if the incoming model object is the same, in which case, we should not
160151
// check for creation timestamp since that means the object was re-created, and so we should override.
161-
existing, exists := ds.modelsByModelName[infModel.Spec.ModelName]
152+
existing, exists := ds.models[infModel.Spec.ModelName]
162153
if exists {
163154
diffObj := infModel.Name != existing.Name || infModel.Namespace != existing.Namespace
164155
if diffObj && existing.ObjectMeta.CreationTimestamp.Before(&infModel.ObjectMeta.CreationTimestamp) {
165156
return false
166157
}
167158
}
168-
169-
// Deleting the model first ensures that the two maps are always aligned.
170-
namespacedName := types.NamespacedName{Name: infModel.Name, Namespace: infModel.Namespace}
171-
ds.modelDeleteByObjName(namespacedName)
172-
ds.modelDeleteByModelName(infModel.Spec.ModelName)
173-
ds.modelsByModelName[infModel.Spec.ModelName] = infModel
174-
ds.modelsByObjName[namespacedName] = infModel
159+
// Delete the model
160+
ds.modelDelete(types.NamespacedName{Name: infModel.Name, Namespace: infModel.Namespace})
161+
ds.models[infModel.Spec.ModelName] = infModel
175162
return true
176163
}
177164

178-
func (ds *datastore) ModelGetByModelName(modelName string) (*v1alpha2.InferenceModel, bool) {
179-
ds.modelsMu.RLock()
180-
defer ds.modelsMu.RUnlock()
181-
m, exists := ds.modelsByModelName[modelName]
182-
return m, exists
183-
}
184-
185-
func (ds *datastore) ModelGetByObjName(namespacedName types.NamespacedName) (*v1alpha2.InferenceModel, bool) {
186-
ds.modelsMu.RLock()
187-
defer ds.modelsMu.RUnlock()
188-
m, exists := ds.modelsByObjName[namespacedName]
165+
func (ds *datastore) ModelGet(modelName string) (*v1alpha2.InferenceModel, bool) {
166+
ds.poolAndModelsMu.RLock()
167+
defer ds.poolAndModelsMu.RUnlock()
168+
m, exists := ds.models[modelName]
189169
return m, exists
190170
}
191171

192-
func (ds *datastore) ModelDelete(namespacedName types.NamespacedName) {
193-
ds.modelsMu.Lock()
194-
defer ds.modelsMu.Unlock()
195-
ds.modelDeleteByObjName(namespacedName)
196-
}
197-
198-
func (ds *datastore) modelDeleteByObjName(namespacedName types.NamespacedName) {
199-
infModel, ok := ds.modelsByObjName[namespacedName]
200-
if !ok {
201-
return
172+
func (ds *datastore) modelDelete(namespacedName types.NamespacedName) (*v1alpha2.InferenceModel, bool) {
173+
for _, m := range ds.models {
174+
if m.Name == namespacedName.Name && m.Namespace == namespacedName.Namespace {
175+
delete(ds.models, m.Spec.ModelName)
176+
return m, true
177+
}
202178
}
203-
delete(ds.modelsByObjName, namespacedName)
204-
delete(ds.modelsByModelName, infModel.Spec.ModelName)
179+
return nil, false
205180
}
206181

207-
func (ds *datastore) modelDeleteByModelName(modelName string) {
208-
infModel, ok := ds.modelsByModelName[modelName]
209-
if !ok {
210-
return
211-
}
212-
delete(ds.modelsByObjName, types.NamespacedName{Name: infModel.Name, Namespace: infModel.Namespace})
213-
delete(ds.modelsByModelName, modelName)
182+
func (ds *datastore) ModelDelete(namespacedName types.NamespacedName) (*v1alpha2.InferenceModel, bool) {
183+
ds.poolAndModelsMu.Lock()
184+
defer ds.poolAndModelsMu.Unlock()
185+
return ds.modelDelete(namespacedName)
214186
}
215187

216188
func (ds *datastore) ModelGetAll() []*v1alpha2.InferenceModel {
217-
ds.modelsMu.RLock()
218-
defer ds.modelsMu.RUnlock()
189+
ds.poolAndModelsMu.RLock()
190+
defer ds.poolAndModelsMu.RUnlock()
219191
res := []*v1alpha2.InferenceModel{}
220-
for _, v := range ds.modelsByObjName {
192+
for _, v := range ds.models {
221193
res = append(res, v)
222194
}
223195
return res

pkg/epp/datastore/datastore_test.go

Lines changed: 8 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -185,32 +185,22 @@ func TestModel1(t *testing.T) {
185185
name: "Getting by model name, chat -> model2",
186186
existingModels: []*v1alpha2.InferenceModel{model2chat, model1ts},
187187
op: func(ds Datastore) bool {
188-
gotChat, exists := ds.ModelGetByModelName(chatModel)
188+
gotChat, exists := ds.ModelGet(chatModel)
189189
return exists && cmp.Diff(model2chat, gotChat) == ""
190190
},
191191
wantOpResult: true,
192192
wantModels: []*v1alpha2.InferenceModel{model2chat, model1ts},
193193
},
194194
{
195-
name: "Getting by obj name, model1 -> tweet-summary",
195+
name: "Delete the model",
196196
existingModels: []*v1alpha2.InferenceModel{model2chat, model1ts},
197197
op: func(ds Datastore) bool {
198-
got, exists := ds.ModelGetByObjName(types.NamespacedName{Name: model1ts.Name, Namespace: model1ts.Namespace})
199-
return exists && cmp.Diff(model1ts, got) == ""
200-
},
201-
wantOpResult: true,
202-
wantModels: []*v1alpha2.InferenceModel{model2chat, model1ts},
203-
},
204-
{
205-
name: "Getting by model name, chat -> model2",
206-
existingModels: []*v1alpha2.InferenceModel{model2chat, model1ts},
207-
op: func(ds Datastore) bool {
208-
ds.ModelDelete(types.NamespacedName{Name: model1ts.Name, Namespace: model1ts.Namespace})
209-
_, exists := ds.ModelGetByModelName(tsModel)
210-
return exists
198+
_, existed := ds.ModelDelete(types.NamespacedName{Name: model1ts.Name, Namespace: model1ts.Namespace})
199+
_, exists := ds.ModelGet(tsModel)
200+
return existed && !exists
211201

212202
},
213-
wantOpResult: false,
203+
wantOpResult: true,
214204
wantModels: []*v1alpha2.InferenceModel{model2chat},
215205
},
216206
}
@@ -221,26 +211,15 @@ func TestModel1(t *testing.T) {
221211
if gotOpResult != test.wantOpResult {
222212
t.Errorf("Unexpected operation result, want: %v, got: %v", test.wantOpResult, gotOpResult)
223213
}
224-
if diff := diffModelMaps(ds.(*datastore), test.wantModels); diff != "" {
214+
215+
if diff := testutil.DiffModelLists(test.wantModels, ds.ModelGetAll()); diff != "" {
225216
t.Errorf("Unexpected models diff: %s", diff)
226217
}
227218

228219
})
229220
}
230221
}
231222

232-
func diffModelMaps(ds *datastore, want []*v1alpha2.InferenceModel) string {
233-
byObjName := ds.ModelGetAll()
234-
byModelName := []*v1alpha2.InferenceModel{}
235-
for _, v := range ds.modelsByModelName {
236-
byModelName = append(byModelName, v)
237-
}
238-
if diff := testutil.DiffModelLists(byObjName, byModelName); diff != "" {
239-
return "Inconsistent maps diff: " + diff
240-
}
241-
return testutil.DiffModelLists(want, byObjName)
242-
}
243-
244223
func TestRandomWeightedDraw(t *testing.T) {
245224
logger := logutil.NewTestLogger()
246225
tests := []struct {

pkg/epp/handlers/request.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ func (s *Server) HandleRequestBody(
6464
// NOTE: The nil checking for the modelObject means that we DO allow passthrough currently.
6565
// This might be a security risk in the future where adapters not registered in the InferenceModel
6666
// are able to be requested by using their distinct name.
67-
modelObj, exist := s.datastore.ModelGetByModelName(model)
67+
modelObj, exist := s.datastore.ModelGet(model)
6868
if !exist {
6969
return nil, errutil.Error{Code: errutil.BadConfiguration, Msg: fmt.Sprintf("error finding a model object in InferenceModel for input %v", model)}
7070
}

test/integration/hermetic_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -476,7 +476,7 @@ func BeforeSuit(t *testing.T) func() {
476476
}
477477

478478
assert.EventuallyWithT(t, func(t *assert.CollectT) {
479-
_, modelExist := serverRunner.Datastore.ModelGetByModelName("my-model")
479+
_, modelExist := serverRunner.Datastore.ModelGet("my-model")
480480
synced := serverRunner.Datastore.PoolHasSynced() && modelExist
481481
assert.True(t, synced, "Timeout waiting for the pool and models to sync")
482482
}, 10*time.Second, 10*time.Millisecond)

0 commit comments

Comments
 (0)