diff --git a/pkg/util/lfu/key_set.go b/pkg/util/lfu/key_set.go new file mode 100644 index 0000000000000..4467dab30c592 --- /dev/null +++ b/pkg/util/lfu/key_set.go @@ -0,0 +1,73 @@ +// Copyright 2023 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package lfu + +import ( + "sync" + + "github.com/pingcap/tidb/pkg/statistics" + "golang.org/x/exp/maps" +) + +type keySet struct { + set map[int64]*statistics.Table + mu sync.RWMutex +} + +func (ks *keySet) Remove(key int64) int64 { + var cost int64 + ks.mu.Lock() + if table, ok := ks.set[key]; ok { + if table != nil { + cost = table.MemoryUsage().TotalTrackingMemUsage() + } + delete(ks.set, key) + } + ks.mu.Unlock() + return cost +} + +func (ks *keySet) Keys() []int64 { + ks.mu.RLock() + result := maps.Keys(ks.set) + ks.mu.RUnlock() + return result +} + +func (ks *keySet) Len() int { + ks.mu.RLock() + result := len(ks.set) + ks.mu.RUnlock() + return result +} + +func (ks *keySet) AddKeyValue(key int64, value *statistics.Table) { + ks.mu.Lock() + ks.set[key] = value + ks.mu.Unlock() +} + +func (ks *keySet) Get(key int64) (*statistics.Table, bool) { + ks.mu.RLock() + value, ok := ks.set[key] + ks.mu.RUnlock() + return value, ok +} + +func (ks *keySet) Clear() { + ks.mu.Lock() + ks.set = make(map[int64]*statistics.Table) + ks.mu.Unlock() +} diff --git a/pkg/util/lfu/key_set_shard.go b/pkg/util/lfu/key_set_shard.go new file mode 100644 index 0000000000000..f07990400001e --- /dev/null +++ b/pkg/util/lfu/key_set_shard.go @@ -0,0 +1,69 @@ +// Copyright 2023 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package lfu + +import ( + "github.com/pingcap/tidb/pkg/statistics" +) + +const keySetCnt = 256 + +type keySetShard struct { + resultKeySet [keySetCnt]keySet +} + +func newKeySetShard() *keySetShard { + result := keySetShard{} + for i := 0; i < keySetCnt; i++ { + result.resultKeySet[i] = keySet{ + set: make(map[int64]*statistics.Table), + } + } + return &result +} + +func (kss *keySetShard) Get(key int64) (*statistics.Table, bool) { + return kss.resultKeySet[key%keySetCnt].Get(key) +} + +func (kss *keySetShard) AddKeyValue(key int64, table *statistics.Table) { + kss.resultKeySet[key%keySetCnt].AddKeyValue(key, table) +} + +func (kss *keySetShard) Remove(key int64) { + kss.resultKeySet[key%keySetCnt].Remove(key) +} + +func (kss *keySetShard) Keys() []int64 { + result := make([]int64, 0, len(kss.resultKeySet)) + for idx := range kss.resultKeySet { + result = append(result, kss.resultKeySet[idx].Keys()...) + } + return result +} + +func (kss *keySetShard) Len() int { + result := 0 + for idx := range kss.resultKeySet { + result += kss.resultKeySet[idx].Len() + } + return result +} + +func (kss *keySetShard) Clear() { + for idx := range kss.resultKeySet { + kss.resultKeySet[idx].Clear() + } +} diff --git a/pkg/util/lfu/lfu_cache.go b/pkg/util/lfu/lfu_cache.go new file mode 100644 index 0000000000000..d4299d19b45be --- /dev/null +++ b/pkg/util/lfu/lfu_cache.go @@ -0,0 +1,266 @@ +// Copyright 2023 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package lfu + +import ( + "sync" + "sync/atomic" + + "github.com/dgraph-io/ristretto" + "github.com/pingcap/tidb/pkg/statistics" + "github.com/pingcap/tidb/pkg/statistics/handle/cache/internal" + "github.com/pingcap/tidb/pkg/statistics/handle/cache/internal/metrics" + "github.com/pingcap/tidb/pkg/util/intest" + "github.com/pingcap/tidb/pkg/util/logutil" + "github.com/pingcap/tidb/pkg/util/memory" + "go.uber.org/zap" + "golang.org/x/exp/rand" +) + +// LFU is a LFU based on the ristretto.Cache +type LFU struct { + cache *ristretto.Cache + // This is a secondary cache layer used to store all tables, + // including those that have been evicted from the primary cache. + resultKeySet *keySetShard + cost atomic.Int64 + closed atomic.Bool + closeOnce sync.Once +} + +// NewLFU creates a new LFU cache. +func NewLFU(totalMemCost int64) (*LFU, error) { + cost, err := adjustMemCost(totalMemCost) + if err != nil { + return nil, err + } + if intest.InTest && totalMemCost == 0 { + // In test, we set the cost to 5MB to avoid using too many memory in the LFU's CM sketch. + cost = 5000000 + } + metrics.CapacityGauge.Set(float64(cost)) + result := &LFU{} + bufferItems := int64(64) + + cache, err := ristretto.NewCache( + &ristretto.Config{ + NumCounters: max(min(cost/128, 1_000_000), 10), // assume the cost per table stats is 128 + MaxCost: cost, + BufferItems: bufferItems, + OnEvict: result.onEvict, + OnExit: result.onExit, + OnReject: result.onReject, + IgnoreInternalCost: intest.InTest, + Metrics: intest.InTest, + }, + ) + if err != nil { + return nil, err + } + result.cache = cache + result.resultKeySet = newKeySetShard() + return result, err +} + +// adjustMemCost adjusts the memory cost according to the total memory cost. +// When the total memory cost is 0, the memory cost is set to half of the total memory. +func adjustMemCost(totalMemCost int64) (result int64, err error) { + if totalMemCost == 0 { + memTotal, err := memory.MemTotal() + if err != nil { + return 0, err + } + return int64(memTotal / 2), nil + } + return totalMemCost, nil +} + +// Get implements statsCacheInner +func (s *LFU) Get(tid int64) (*statistics.Table, bool) { + result, ok := s.cache.Get(tid) + if !ok { + return s.resultKeySet.Get(tid) + } + return result.(*statistics.Table), ok +} + +// Put implements statsCacheInner +func (s *LFU) Put(tblID int64, tbl *statistics.Table) bool { + cost := tbl.MemoryUsage().TotalTrackingMemUsage() + s.resultKeySet.AddKeyValue(tblID, tbl) + s.addCost(cost) + return s.cache.Set(tblID, tbl, cost) +} + +// Del implements statsCacheInner +func (s *LFU) Del(tblID int64) { + s.cache.Del(tblID) + s.resultKeySet.Remove(tblID) +} + +// Cost implements statsCacheInner +func (s *LFU) Cost() int64 { + return s.cost.Load() +} + +// Values implements statsCacheInner +func (s *LFU) Values() []*statistics.Table { + result := make([]*statistics.Table, 0, 512) + for _, k := range s.resultKeySet.Keys() { + if value, ok := s.resultKeySet.Get(k); ok { + result = append(result, value) + } + } + return result +} + +// DropEvicted drop stats for table column/index +func DropEvicted(item statistics.TableCacheItem) { + if !item.IsStatsInitialized() || + item.GetEvictedStatus() == statistics.AllEvicted { + return + } + item.DropUnnecessaryData() +} + +func (s *LFU) onReject(item *ristretto.Item) { + defer func() { + if r := recover(); r != nil { + logutil.BgLogger().Warn("panic in onReject", zap.Any("error", r), zap.Stack("stack")) + } + }() + s.dropMemory(item) + metrics.RejectCounter.Inc() +} + +func (s *LFU) onEvict(item *ristretto.Item) { + defer func() { + if r := recover(); r != nil { + logutil.BgLogger().Warn("panic in onEvict", zap.Any("error", r), zap.Stack("stack")) + } + }() + s.dropMemory(item) + metrics.EvictCounter.Inc() +} + +func (s *LFU) dropMemory(item *ristretto.Item) { + if item.Value == nil { + // Sometimes the same key may be passed to the "onEvict/onExit" + // function twice, and in the second invocation, the value is empty, + // so it should not be processed. + return + } + if s.closed.Load() { + return + } + // We do not need to calculate the cost during onEvict, + // because the onexit function is also called when the evict event occurs. + // TODO(hawkingrei): not copy the useless part. + table := item.Value.(*statistics.Table).Copy() + for _, column := range table.Columns { + DropEvicted(column) + } + for _, indix := range table.Indices { + DropEvicted(indix) + } + s.resultKeySet.AddKeyValue(int64(item.Key), table) + after := table.MemoryUsage().TotalTrackingMemUsage() + // why add before again? because the cost will be subtracted in onExit. + // in fact, it is after - before + s.addCost(after) + s.triggerEvict() +} + +func (s *LFU) triggerEvict() { + // When the memory usage of the cache exceeds the maximum value, Many item need to evict. But + // ristretto'c cache execute the evict operation when to write the cache. for we can evict as soon as possible, + // we will write some fake item to the cache. fake item have a negative key, and the value is nil. + if s.Cost() > s.cache.MaxCost() { + //nolint: gosec + s.cache.Set(-rand.Int(), nil, 0) + } +} + +func (s *LFU) onExit(val any) { + defer func() { + if r := recover(); r != nil { + logutil.BgLogger().Warn("panic in onExit", zap.Any("error", r), zap.Stack("stack")) + } + }() + if val == nil { + // Sometimes the same key may be passed to the "onEvict/onExit" function twice, + // and in the second invocation, the value is empty, so it should not be processed. + return + } + if s.closed.Load() { + return + } + // Subtract the memory usage of the table from the total memory usage. + s.addCost(-val.(*statistics.Table).MemoryUsage().TotalTrackingMemUsage()) +} + +// Len implements statsCacheInner +func (s *LFU) Len() int { + return s.resultKeySet.Len() +} + +// Copy implements statsCacheInner +func (s *LFU) Copy() internal.StatsCacheInner { + return s +} + +// SetCapacity implements statsCacheInner +func (s *LFU) SetCapacity(maxCost int64) { + cost, err := adjustMemCost(maxCost) + if err != nil { + logutil.BgLogger().Warn("adjustMemCost failed", zap.Error(err)) + return + } + s.cache.UpdateMaxCost(cost) + s.triggerEvict() + metrics.CapacityGauge.Set(float64(cost)) + metrics.CostGauge.Set(float64(s.Cost())) +} + +// wait blocks until all buffered writes have been applied. This ensures a call to Set() +// will be visible to future calls to Get(). it is only used for test. +func (s *LFU) wait() { + s.cache.Wait() +} + +func (s *LFU) metrics() *ristretto.Metrics { + return s.cache.Metrics +} + +// Close implements statsCacheInner +func (s *LFU) Close() { + s.closeOnce.Do(func() { + s.closed.Store(true) + s.Clear() + s.cache.Close() + s.cache.Wait() + }) +} + +// Clear implements statsCacheInner +func (s *LFU) Clear() { + s.cache.Clear() + s.resultKeySet.Clear() +} + +func (s *LFU) addCost(v int64) { + newv := s.cost.Add(v) + metrics.CostGauge.Set(float64(newv)) +} diff --git a/pkg/util/lfu/lfu_cache_test.go b/pkg/util/lfu/lfu_cache_test.go new file mode 100644 index 0000000000000..de690b8e06ca9 --- /dev/null +++ b/pkg/util/lfu/lfu_cache_test.go @@ -0,0 +1,297 @@ +// Copyright 2022 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package lfu + +import ( + "math/rand" + "sync" + "testing" + "time" + + "github.com/pingcap/tidb/pkg/statistics" + "github.com/pingcap/tidb/pkg/statistics/handle/cache/internal/testutil" + "github.com/stretchr/testify/require" +) + +var ( + mockCMSMemoryUsage = int64(4) +) + +func TestLFUPutGetDel(t *testing.T) { + capacity := int64(100) + lfu, err := NewLFU(capacity) + require.NoError(t, err) + mockTable := testutil.NewMockStatisticsTable(1, 1, true, false, false) + mockTableID := int64(1) + lfu.Put(mockTableID, mockTable) + lfu.wait() + lfu.Del(mockTableID) + v, ok := lfu.Get(mockTableID) + require.False(t, ok) + require.Nil(t, v) + lfu.wait() + require.Equal(t, uint64(lfu.Cost()), lfu.metrics().CostAdded()-lfu.metrics().CostEvicted()) + require.Equal(t, 0, len(lfu.Values())) +} + +func TestLFUFreshMemUsage(t *testing.T) { + lfu, err := NewLFU(10000) + require.NoError(t, err) + t1 := testutil.NewMockStatisticsTable(1, 1, true, false, false) + require.Equal(t, mockCMSMemoryUsage+mockCMSMemoryUsage, t1.MemoryUsage().TotalMemUsage) + t2 := testutil.NewMockStatisticsTable(2, 2, true, false, false) + require.Equal(t, 2*mockCMSMemoryUsage+2*mockCMSMemoryUsage, t2.MemoryUsage().TotalMemUsage) + t3 := testutil.NewMockStatisticsTable(3, 3, true, false, false) + require.Equal(t, 3*mockCMSMemoryUsage+3*mockCMSMemoryUsage, t3.MemoryUsage().TotalMemUsage) + lfu.Put(int64(1), t1) + lfu.Put(int64(2), t2) + lfu.Put(int64(3), t3) + lfu.wait() + require.Equal(t, lfu.Cost(), 6*mockCMSMemoryUsage+6*mockCMSMemoryUsage) + t4 := testutil.NewMockStatisticsTable(2, 1, true, false, false) + lfu.Put(int64(1), t4) + lfu.wait() + require.Equal(t, lfu.Cost(), 7*mockCMSMemoryUsage+6*mockCMSMemoryUsage) + t5 := testutil.NewMockStatisticsTable(2, 2, true, false, false) + lfu.Put(int64(1), t5) + lfu.wait() + require.Equal(t, lfu.Cost(), 7*mockCMSMemoryUsage+7*mockCMSMemoryUsage) + + t6 := testutil.NewMockStatisticsTable(1, 2, true, false, false) + lfu.Put(int64(1), t6) + require.Equal(t, lfu.Cost(), 7*mockCMSMemoryUsage+6*mockCMSMemoryUsage) + + t7 := testutil.NewMockStatisticsTable(1, 1, true, false, false) + lfu.Put(int64(1), t7) + require.Equal(t, lfu.Cost(), 6*mockCMSMemoryUsage+6*mockCMSMemoryUsage) + lfu.wait() + require.Equal(t, uint64(lfu.Cost()), lfu.metrics().CostAdded()-lfu.metrics().CostEvicted()) +} + +func TestLFUPutTooBig(t *testing.T) { + lfu, err := NewLFU(1) + require.NoError(t, err) + mockTable := testutil.NewMockStatisticsTable(1, 1, true, false, false) + // put mockTable, the index should be evicted but the table still exists in the list. + lfu.Put(int64(1), mockTable) + _, ok := lfu.Get(int64(1)) + require.True(t, ok) + lfu.wait() + require.Equal(t, uint64(lfu.Cost()), lfu.metrics().CostAdded()-lfu.metrics().CostEvicted()) +} + +func TestCacheLen(t *testing.T) { + capacity := int64(12) + lfu, err := NewLFU(capacity) + require.NoError(t, err) + t1 := testutil.NewMockStatisticsTable(2, 1, true, false, false) + require.Equal(t, int64(12), t1.MemoryUsage().TotalTrackingMemUsage()) + lfu.Put(int64(1), t1) + t2 := testutil.NewMockStatisticsTable(1, 1, true, false, false) + // put t2, t1 should be evicted 2 items and still exists in the list + lfu.Put(int64(2), t2) + lfu.wait() + require.Equal(t, lfu.Len(), 2) + require.Equal(t, uint64(8), lfu.metrics().CostAdded()-lfu.metrics().CostEvicted()) + + // put t3, t1/t2 should be evicted all items. but t1/t2 still exists in the list + t3 := testutil.NewMockStatisticsTable(2, 1, true, false, false) + lfu.Put(int64(3), t3) + lfu.wait() + require.Equal(t, lfu.Len(), 3) + require.Equal(t, uint64(12), lfu.metrics().CostAdded()-lfu.metrics().CostEvicted()) +} + +func TestLFUCachePutGetWithManyConcurrency(t *testing.T) { + // to test DATA RACE + capacity := int64(100000000000) + lfu, err := NewLFU(capacity) + require.NoError(t, err) + var wg sync.WaitGroup + wg.Add(2000) + for i := 0; i < 1000; i++ { + go func(i int) { + defer wg.Done() + t1 := testutil.NewMockStatisticsTable(1, 1, true, false, false) + lfu.Put(int64(i), t1) + }(i) + go func(i int) { + defer wg.Done() + lfu.Get(int64(i)) + }(i) + } + wg.Wait() + lfu.wait() + require.Equal(t, lfu.Len(), 1000) + require.Equal(t, uint64(lfu.Cost()), lfu.metrics().CostAdded()-lfu.metrics().CostEvicted()) + require.Equal(t, 1000, len(lfu.Values())) +} + +func TestLFUCachePutGetWithManyConcurrency2(t *testing.T) { + // to test DATA RACE + capacity := int64(100000000000) + lfu, err := NewLFU(capacity) + require.NoError(t, err) + var wg sync.WaitGroup + wg.Add(10) + for i := 0; i < 5; i++ { + go func() { + defer wg.Done() + for n := 0; n < 1000; n++ { + t1 := testutil.NewMockStatisticsTable(1, 1, true, false, false) + lfu.Put(int64(n), t1) + } + }() + } + for i := 0; i < 5; i++ { + go func() { + defer wg.Done() + for n := 0; n < 1000; n++ { + lfu.Get(int64(n)) + } + }() + } + wg.Wait() + lfu.wait() + require.Equal(t, uint64(lfu.Cost()), lfu.metrics().CostAdded()-lfu.metrics().CostEvicted()) + require.Equal(t, 1000, len(lfu.Values())) +} + +func TestLFUCachePutGetWithManyConcurrencyAndSmallConcurrency(t *testing.T) { + // to test DATA RACE + + capacity := int64(100) + lfu, err := NewLFU(capacity) + require.NoError(t, err) + var wg sync.WaitGroup + wg.Add(10) + for i := 0; i < 5; i++ { + go func() { + defer wg.Done() + for c := 0; c < 1000; c++ { + for n := 0; n < 50; n++ { + t1 := testutil.NewMockStatisticsTable(1, 1, true, true, true) + lfu.Put(int64(n), t1) + } + } + }() + } + time.Sleep(1 * time.Second) + for i := 0; i < 5; i++ { + go func() { + defer wg.Done() + for c := 0; c < 1000; c++ { + for n := 0; n < 50; n++ { + tbl, ok := lfu.Get(int64(n)) + require.True(t, ok) + checkTable(t, tbl) + } + } + }() + } + wg.Wait() + lfu.wait() + v, ok := lfu.Get(rand.Int63n(50)) + require.True(t, ok) + for _, c := range v.Columns { + require.Equal(t, c.GetEvictedStatus(), statistics.AllEvicted) + } + for _, i := range v.Indices { + require.Equal(t, i.GetEvictedStatus(), statistics.AllEvicted) + } +} + +func checkTable(t *testing.T, tbl *statistics.Table) { + for _, column := range tbl.Columns { + if column.GetEvictedStatus() == statistics.AllEvicted { + require.Nil(t, column.TopN) + require.Equal(t, 0, cap(column.Histogram.Buckets)) + } else { + require.NotNil(t, column.TopN) + require.Greater(t, cap(column.Histogram.Buckets), 0) + } + } + for _, idx := range tbl.Indices { + if idx.GetEvictedStatus() == statistics.AllEvicted { + require.Nil(t, idx.TopN) + require.Equal(t, 0, cap(idx.Histogram.Buckets)) + } else { + require.NotNil(t, idx.TopN) + require.Greater(t, cap(idx.Histogram.Buckets), 0) + } + } +} + +func TestLFUReject(t *testing.T) { + capacity := int64(100000000000) + lfu, err := NewLFU(capacity) + require.NoError(t, err) + t1 := testutil.NewMockStatisticsTable(2, 1, true, false, false) + require.Equal(t, 2*mockCMSMemoryUsage+mockCMSMemoryUsage, t1.MemoryUsage().TotalTrackingMemUsage()) + lfu.Put(1, t1) + lfu.wait() + require.Equal(t, lfu.Cost(), 2*mockCMSMemoryUsage+mockCMSMemoryUsage) + + lfu.SetCapacity(2*mockCMSMemoryUsage + mockCMSMemoryUsage - 1) + + t2 := testutil.NewMockStatisticsTable(2, 1, true, false, false) + require.True(t, lfu.Put(2, t2)) + lfu.wait() + time.Sleep(3 * time.Second) + require.Equal(t, int64(0), lfu.Cost()) + require.Len(t, lfu.Values(), 2) + v, ok := lfu.Get(2) + require.True(t, ok) + for _, c := range v.Columns { + require.Equal(t, statistics.AllEvicted, c.GetEvictedStatus()) + } + for _, i := range v.Indices { + require.Equal(t, statistics.AllEvicted, i.GetEvictedStatus()) + } +} + +func TestMemoryControl(t *testing.T) { + capacity := int64(100000000000) + lfu, err := NewLFU(capacity) + require.NoError(t, err) + t1 := testutil.NewMockStatisticsTable(2, 1, true, false, false) + require.Equal(t, 2*mockCMSMemoryUsage+mockCMSMemoryUsage, t1.MemoryUsage().TotalTrackingMemUsage()) + lfu.Put(1, t1) + lfu.wait() + + for i := 2; i <= 1000; i++ { + t1 := testutil.NewMockStatisticsTable(2, 1, true, false, false) + require.Equal(t, 2*mockCMSMemoryUsage+mockCMSMemoryUsage, t1.MemoryUsage().TotalTrackingMemUsage()) + lfu.Put(int64(i), t1) + } + require.Equal(t, 1000*(2*mockCMSMemoryUsage+mockCMSMemoryUsage), lfu.Cost()) + + for i := 1000; i > 990; i-- { + lfu.SetCapacity(int64(i-1) * (2*mockCMSMemoryUsage + mockCMSMemoryUsage)) + lfu.wait() + require.Equal(t, int64(i-1)*(2*mockCMSMemoryUsage+mockCMSMemoryUsage), lfu.Cost()) + } + for i := 990; i > 100; i = i - 100 { + lfu.SetCapacity(int64(i-1) * (2*mockCMSMemoryUsage + mockCMSMemoryUsage)) + lfu.wait() + require.Equal(t, int64(i-1)*(2*mockCMSMemoryUsage+mockCMSMemoryUsage), lfu.Cost()) + } + lfu.SetCapacity(int64(10) * (2*mockCMSMemoryUsage + mockCMSMemoryUsage)) + lfu.wait() + require.Equal(t, int64(10)*(2*mockCMSMemoryUsage+mockCMSMemoryUsage), lfu.Cost()) + lfu.SetCapacity(0) + lfu.wait() + require.Equal(t, int64(10)*(2*mockCMSMemoryUsage+mockCMSMemoryUsage), lfu.Cost()) +}