@@ -21,6 +21,7 @@ import (
21
21
"errors"
22
22
"fmt"
23
23
"sync"
24
+ "time"
24
25
25
26
corev1 "k8s.io/api/core/v1"
26
27
"k8s.io/apimachinery/pkg/labels"
@@ -34,7 +35,9 @@ import (
34
35
)
35
36
36
37
const (
37
- ModelNameIndexKey = "spec.modelName"
38
+ ModelNameIndexKey = "spec.modelName"
39
+ sessionKeepAliveTime = 60 * time .Minute // How long should an idle session be kept alive
40
+ sessionKeepAliveCheckFrequency = 15 * time .Minute // How often to check for overly idle sessions
38
41
)
39
42
40
43
var (
@@ -65,6 +68,9 @@ type Datastore interface {
65
68
PodDelete (namespacedName types.NamespacedName )
66
69
PodResyncAll (ctx context.Context , ctrlClient client.Client , pool * v1alpha2.InferencePool )
67
70
71
+ SetPodForSession (sessionID string , pod * backendmetrics.Pod )
72
+ GetPodForSession (sessionID string ) * backendmetrics.Pod
73
+
68
74
// Clears the store state, happens when the pool gets deleted.
69
75
Clear ()
70
76
}
@@ -75,8 +81,12 @@ func NewDatastore(parentCtx context.Context, pmf *backendmetrics.PodMetricsFacto
75
81
poolAndModelsMu : sync.RWMutex {},
76
82
models : make (map [string ]* v1alpha2.InferenceModel ),
77
83
pods : & sync.Map {},
84
+ sessions : & sync.Map {},
78
85
pmf : pmf ,
79
86
}
87
+
88
+ go store .cleanupSessions (sessionKeepAliveCheckFrequency , sessionKeepAliveTime , parentCtx )
89
+
80
90
return store
81
91
}
82
92
@@ -90,7 +100,9 @@ type datastore struct {
90
100
models map [string ]* v1alpha2.InferenceModel
91
101
// key: types.NamespacedName, value: backendmetrics.PodMetrics
92
102
pods * sync.Map
93
- pmf * backendmetrics.PodMetricsFactory
103
+ // key: session id, value: *backendmetrics.Pod
104
+ sessions * sync.Map
105
+ pmf * backendmetrics.PodMetricsFactory
94
106
}
95
107
96
108
func (ds * datastore ) Clear () {
@@ -291,6 +303,61 @@ func (ds *datastore) PodDelete(namespacedName types.NamespacedName) {
291
303
}
292
304
}
293
305
306
+ type sessionInfo struct {
307
+ pod * backendmetrics.Pod
308
+ lru time.Time
309
+ }
310
+
311
+ // cleanup Cleans up the set of stored session information by removing information
312
+ // of old sessions.
313
+ func (ds * datastore ) cleanupSessions (keepAliveCheckFrequency time.Duration , sessionKeepAlive time.Duration , ctx context.Context ) {
314
+ logger := log .FromContext (ctx )
315
+
316
+ logger .Info ("Session-affinity cleanup started" )
317
+ ticker := time .NewTicker (keepAliveCheckFrequency )
318
+ defer ticker .Stop ()
319
+
320
+ for {
321
+ select {
322
+ case <- ctx .Done ():
323
+ logger .Info ("Session-affinity cleanup stopped:" )
324
+ return
325
+ case now := <- ticker .C :
326
+ logger .Info ("Session affinity checking" )
327
+ ds .sessions .Range (
328
+ func (sessionID any , rawSessionInfo any ) bool {
329
+ if sessionInfo , ok := rawSessionInfo .(* sessionInfo ); ok {
330
+ if now .Sub (sessionInfo .lru ) > sessionKeepAlive {
331
+ // Session is stale, remove it
332
+ ds .sessions .Delete (sessionID )
333
+ }
334
+ } else {
335
+ // Value is not of the correct type, remove it
336
+ ds .sessions .Delete (sessionID )
337
+ }
338
+ return true
339
+ })
340
+ }
341
+ }
342
+ }
343
+
344
+ func (ds * datastore ) SetPodForSession (sessionID string , pod * backendmetrics.Pod ) {
345
+ ds .sessions .Store (sessionID , & sessionInfo {
346
+ pod : pod ,
347
+ lru : time .Now (),
348
+ })
349
+ }
350
+
351
+ func (ds * datastore ) GetPodForSession (sessionID string ) * backendmetrics.Pod {
352
+ if value , ok := ds .sessions .Load (sessionID ); ok {
353
+ if sessionInfo , ok := value .(* sessionInfo ); ok {
354
+ return sessionInfo .pod
355
+ }
356
+ }
357
+
358
+ return nil
359
+ }
360
+
294
361
func selectorFromInferencePoolSelector (selector map [v1alpha2.LabelKey ]v1alpha2.LabelValue ) labels.Selector {
295
362
return labels .SelectorFromSet (stripLabelKeyAliasFromLabelMap (selector ))
296
363
}
0 commit comments