Files
tidb/statistics/handle/statscache.go

221 lines
6.1 KiB
Go

// Copyright 2020 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,
// See the License for the specific language governing permissions and
// limitations under the License.
package handle
import (
"encoding/binary"
"errors"
"sync"
"github.com/pingcap/tidb/statistics"
"github.com/pingcap/tidb/util/kvcache"
"github.com/pingcap/tidb/util/memory"
)
// StatsCache is an interface for the collection of statistics.
type StatsCache interface {
Lookup(id int64) (*statistics.Table, bool)
Update(tables []*statistics.Table, deletedIDs []int64, newVersion uint64)
GetVersion() uint64
InitStatsCache(tables map[int64]*statistics.Table, version uint64)
GetAll() []*statistics.Table
// Interface below are used only for test.
Clear()
GetBytesLimit() int64
SetBytesLimit(bytesLimit int64)
BytesConsumed() int64
}
type statsCacheType int8
const (
simpleLRUCache statsCacheType = iota
)
var defaultStatsCacheType = simpleLRUCache
// newStatsCacheWithMemCap returns a new stats cache with memory capacity.
func newStatsCacheWithMemCap(memoryCapacity int64, tp statsCacheType) (StatsCache, error) {
switch tp {
case simpleLRUCache:
return newSimpleLRUStatsCache(memoryCapacity), nil
}
return nil, errors.New("wrong statsCache type")
}
// simpleLRUStatsCache uses the simpleLRUCache to store the cache of statistics.
type simpleLRUStatsCache struct {
mu sync.Mutex
cache *kvcache.SimpleLRUCache
memCapacity int64
version uint64
memTracker *memory.Tracker
}
type statsCacheKey int64
func (key statsCacheKey) Hash() []byte {
var buf = make([]byte, 8)
binary.BigEndian.PutUint64(buf, uint64(key))
return buf
}
func newSimpleLRUStatsCache(memoryCapacity int64) *simpleLRUStatsCache {
// since stats cache controls the memory usage by itself, set the capacity of
// the underlying LRUCache to max to close its memory control
cache := kvcache.NewSimpleLRUCache(uint(memoryCapacity), 0.1, 0)
c := simpleLRUStatsCache{
cache: cache,
memCapacity: memoryCapacity,
memTracker: memory.NewTracker(memory.LabelForStatsCache, memoryCapacity),
}
return &c
}
// SetBytesLimit sets the bytes limit for this tracker.
func (sc *simpleLRUStatsCache) SetBytesLimit(BytesLimit int64) {
sc.mu.Lock()
defer sc.mu.Unlock()
sc.memTracker.SetBytesLimit(BytesLimit)
sc.memCapacity = BytesLimit
}
// BytesConsumed returns the consumed memory usage value in bytes.
func (sc *simpleLRUStatsCache) BytesConsumed() int64 {
sc.mu.Lock()
defer sc.mu.Unlock()
return sc.memTracker.BytesConsumed()
}
// lookupUnsafe get table with id without Lock.
func (sc *simpleLRUStatsCache) lookupUnsafe(id int64) (*statistics.Table, bool) {
var key = statsCacheKey(id)
value, hit := sc.cache.Get(key)
if !hit {
return nil, false
}
table := value.(*statistics.Table)
return table, true
}
// Clear clears the cache
func (sc *simpleLRUStatsCache) Clear() {
sc.mu.Lock()
defer sc.mu.Unlock()
sc.version = 0
sc.cache.DeleteAll()
sc.memTracker = memory.NewTracker(memory.LabelForStatsCache, sc.memCapacity)
}
// Lookup get table with id.
func (sc *simpleLRUStatsCache) Lookup(id int64) (*statistics.Table, bool) {
sc.mu.Lock()
defer sc.mu.Unlock()
return sc.lookupUnsafe(id)
}
// Insert inserts a new table to the statsCache.
// If the memory consumption exceeds the capacity, remove the buckets and
// CMSketch of the oldest cache and add metadata of it
func (sc *simpleLRUStatsCache) Insert(table *statistics.Table) {
if table == nil {
return
}
var key = statsCacheKey(table.PhysicalID)
mem := table.MemoryUsage()
// We do not need to check whether mem > sc.memCapacity, because the lower
// bound of statistics is set, it's almost impossible the stats memory usage
// of one table exceeds the capacity.
for mem+sc.memTracker.BytesConsumed() > sc.memCapacity {
evictedKey, evictedValue, evicted := sc.cache.RemoveOldest()
if !evicted {
return
}
sc.memTracker.Consume(-evictedValue.(*statistics.Table).MemoryUsage())
sc.cache.Put(evictedKey, evictedValue.(*statistics.Table).CopyWithoutBucketsAndCMS())
}
// erase the old element since the value may be different from the existing one.
sc.Erase(table.PhysicalID)
sc.cache.Put(key, table)
sc.memTracker.Consume(mem)
return
}
// Erase removes a stateCache with physical id.
func (sc *simpleLRUStatsCache) Erase(deletedID int64) bool {
table, hit := sc.lookupUnsafe(deletedID)
if !hit {
return false
}
key := statsCacheKey(deletedID)
sc.cache.Delete(key)
sc.memTracker.Consume(-table.MemoryUsage())
return true
}
// GetAll get all the tables point.
func (sc *simpleLRUStatsCache) GetAll() []*statistics.Table {
sc.mu.Lock()
defer sc.mu.Unlock()
values := sc.cache.GetAll()
tables := make([]*statistics.Table, 0, len(values))
for _, v := range values {
if t, ok := v.(*statistics.Table); ok && t != nil {
tables = append(tables, t)
}
}
return tables
}
// Update updates the statistics table cache.
func (sc *simpleLRUStatsCache) Update(tables []*statistics.Table, deletedIDs []int64, newVersion uint64) {
sc.mu.Lock()
defer sc.mu.Unlock()
if sc.version <= newVersion {
sc.version = newVersion
for _, id := range deletedIDs {
sc.Erase(id)
}
for _, tbl := range tables {
sc.Insert(tbl)
}
}
}
// GetBytesLimit get the limits of memory.
func (sc *simpleLRUStatsCache) GetBytesLimit() int64 {
sc.mu.Lock()
defer sc.mu.Unlock()
return sc.memTracker.GetBytesLimit()
}
func (sc *simpleLRUStatsCache) GetVersion() uint64 {
sc.mu.Lock()
defer sc.mu.Unlock()
return sc.version
}
// InitStatsCache should be called after the tables and their stats are initilazed
// using tables map and version to init statscache
func (sc *simpleLRUStatsCache) InitStatsCache(tables map[int64]*statistics.Table, version uint64) {
sc.mu.Lock()
defer sc.mu.Unlock()
for _, tbl := range tables {
sc.Insert(tbl)
}
sc.version = version
return
}