@@ -18,6 +18,7 @@ package cacher
18
18
19
19
import (
20
20
"fmt"
21
+ "reflect"
21
22
"sort"
22
23
"sync"
23
24
"time"
@@ -44,6 +45,20 @@ const (
44
45
// resourceVersionTooHighRetrySeconds is the seconds before a operation should be retried by the client
45
46
// after receiving a 'too high resource version' error.
46
47
resourceVersionTooHighRetrySeconds = 1
48
+
49
+ // eventFreshDuration is time duration of events we want to keep.
50
+ eventFreshDuration = 5 * time .Minute
51
+
52
+ // defaultLowerBoundCapacity is a default value for event cache capacity's lower bound.
53
+ // 100 is minimum in NewHeuristicWatchCacheSizes.
54
+ // TODO: Figure out, to what value we can decreased it.
55
+ defaultLowerBoundCapacity = 100
56
+
57
+ // defaultUpperBoundCapacity should be able to keep eventFreshDuration of history.
58
+ // With the current 102400 value though, it's not enough for leases in 5k-node cluster,
59
+ // but that is conscious decision.
60
+ // TODO: Validate if the current value is high enough for large scale clusters.
61
+ defaultUpperBoundCapacity = 100 * 1024
47
62
)
48
63
49
64
// watchCacheEvent is a single "watch event" that is send to users of
@@ -60,6 +75,7 @@ type watchCacheEvent struct {
60
75
PrevObjFields fields.Set
61
76
Key string
62
77
ResourceVersion uint64
78
+ RecordTime time.Time
63
79
}
64
80
65
81
// Computing a key of an object is generally non-trivial (it performs
@@ -126,6 +142,12 @@ type watchCache struct {
126
142
// Maximum size of history window.
127
143
capacity int
128
144
145
+ // upper bound of capacity since event cache has a dynamic size.
146
+ upperBoundCapacity int
147
+
148
+ // lower bound of capacity since event cache has a dynamic size.
149
+ lowerBoundCapacity int
150
+
129
151
// keyFunc is used to get a key in the underlying storage for a given object.
130
152
keyFunc func (runtime.Object ) (string , error )
131
153
@@ -165,6 +187,9 @@ type watchCache struct {
165
187
166
188
// An underlying storage.Versioner.
167
189
versioner storage.Versioner
190
+
191
+ // cacher's objectType.
192
+ objectType reflect.Type
168
193
}
169
194
170
195
func newWatchCache (
@@ -173,12 +198,16 @@ func newWatchCache(
173
198
eventHandler func (* watchCacheEvent ),
174
199
getAttrsFunc func (runtime.Object ) (labels.Set , fields.Set , error ),
175
200
versioner storage.Versioner ,
176
- indexers * cache.Indexers ) * watchCache {
201
+ indexers * cache.Indexers ,
202
+ objectType reflect.Type ) * watchCache {
177
203
wc := & watchCache {
178
- capacity : capacity ,
179
- keyFunc : keyFunc ,
180
- getAttrsFunc : getAttrsFunc ,
181
- cache : make ([]* watchCacheEvent , capacity ),
204
+ capacity : capacity ,
205
+ keyFunc : keyFunc ,
206
+ getAttrsFunc : getAttrsFunc ,
207
+ cache : make ([]* watchCacheEvent , capacity ),
208
+ // TODO get rid of them once we stop passing capacity as a parameter to watch cache.
209
+ lowerBoundCapacity : min (capacity , defaultLowerBoundCapacity ),
210
+ upperBoundCapacity : max (capacity , defaultUpperBoundCapacity ),
182
211
startIndex : 0 ,
183
212
endIndex : 0 ,
184
213
store : cache .NewIndexer (storeElementKey , storeElementIndexers (indexers )),
@@ -187,6 +216,7 @@ func newWatchCache(
187
216
eventHandler : eventHandler ,
188
217
clock : clock.RealClock {},
189
218
versioner : versioner ,
219
+ objectType : objectType ,
190
220
}
191
221
wc .cond = sync .NewCond (wc .RLocker ())
192
222
return wc
@@ -260,6 +290,7 @@ func (w *watchCache) processEvent(event watch.Event, resourceVersion uint64, upd
260
290
ObjFields : elem .Fields ,
261
291
Key : key ,
262
292
ResourceVersion : resourceVersion ,
293
+ RecordTime : w .clock .Now (),
263
294
}
264
295
265
296
if err := func () error {
@@ -301,14 +332,57 @@ func (w *watchCache) processEvent(event watch.Event, resourceVersion uint64, upd
301
332
302
333
// Assumes that lock is already held for write.
303
334
func (w * watchCache ) updateCache (event * watchCacheEvent ) {
304
- if w .endIndex == w .startIndex + w .capacity {
335
+ w .resizeCacheLocked (event .RecordTime )
336
+ if w .isCacheFullLocked () {
305
337
// Cache is full - remove the oldest element.
306
338
w .startIndex ++
307
339
}
308
340
w .cache [w .endIndex % w .capacity ] = event
309
341
w .endIndex ++
310
342
}
311
343
344
+ // resizeCacheLocked resizes the cache if necessary:
345
+ // - increases capacity by 2x if cache is full and all cached events occurred within last eventFreshDuration.
346
+ // - decreases capacity by 2x when recent quarter of events occurred outside of eventFreshDuration(protect watchCache from flapping).
347
+ func (w * watchCache ) resizeCacheLocked (eventTime time.Time ) {
348
+ if w .isCacheFullLocked () && eventTime .Sub (w .cache [w .startIndex % w .capacity ].RecordTime ) < eventFreshDuration {
349
+ capacity := min (w .capacity * 2 , w .upperBoundCapacity )
350
+ if capacity > w .capacity {
351
+ w .doCacheResizeLocked (capacity )
352
+ }
353
+ return
354
+ }
355
+ if w .isCacheFullLocked () && eventTime .Sub (w .cache [(w .endIndex - w .capacity / 4 )% w .capacity ].RecordTime ) > eventFreshDuration {
356
+ capacity := max (w .capacity / 2 , w .lowerBoundCapacity )
357
+ if capacity < w .capacity {
358
+ w .doCacheResizeLocked (capacity )
359
+ }
360
+ return
361
+ }
362
+ }
363
+
364
+ // isCacheFullLocked used to judge whether watchCacheEvent is full.
365
+ // Assumes that lock is already held for write.
366
+ func (w * watchCache ) isCacheFullLocked () bool {
367
+ return w .endIndex == w .startIndex + w .capacity
368
+ }
369
+
370
+ // doCacheResizeLocked resize watchCache's event array with different capacity.
371
+ // Assumes that lock is already held for write.
372
+ func (w * watchCache ) doCacheResizeLocked (capacity int ) {
373
+ newCache := make ([]* watchCacheEvent , capacity )
374
+ if capacity < w .capacity {
375
+ // adjust startIndex if cache capacity shrink.
376
+ w .startIndex = w .endIndex - capacity
377
+ }
378
+ for i := w .startIndex ; i < w .endIndex ; i ++ {
379
+ newCache [i % capacity ] = w .cache [i % w .capacity ]
380
+ }
381
+ w .cache = newCache
382
+ recordsWatchCacheCapacityChange (w .objectType .String (), w .capacity , capacity )
383
+ w .capacity = capacity
384
+ }
385
+
312
386
// List returns list of pointers to <storeElement> objects.
313
387
func (w * watchCache ) List () []interface {} {
314
388
return w .store .List ()
0 commit comments