Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bindinfo: add warning message when the memory usage of the cache exceeds its capacity #32866

Merged
merged 30 commits into from
Mar 15, 2022
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
3f152ae
bindinfo: add warning message when the memory usage of the binding_ca…
Reminiscent Mar 7, 2022
0062a52
Merge branch 'master' of github.com:pingcap/tidb into addWarning4bind…
Reminiscent Mar 7, 2022
7b37080
add more test cases
Reminiscent Mar 7, 2022
a40f644
remove useless line
Reminiscent Mar 7, 2022
10bf341
fix ut
Reminiscent Mar 7, 2022
03f6b3a
Merge branch 'master' of github.com:pingcap/tidb into addWarning4bind…
Reminiscent Mar 7, 2022
538182c
Merge branch 'master' of github.com:pingcap/tidb into addWarning4bind…
Reminiscent Mar 7, 2022
fa9ef08
rename TiDBMemQuotaBindCache to TiDBMemQuotaBindingCache
Reminiscent Mar 7, 2022
0dfbe1a
fix ut
Reminiscent Mar 7, 2022
3a1ea99
fix ut
Reminiscent Mar 8, 2022
8a09095
Merge branch 'master' of github.com:pingcap/tidb into addWarning4bind…
Reminiscent Mar 8, 2022
1da4ed2
address comments
Reminiscent Mar 10, 2022
ae595fb
Merge branch 'master' of github.com:pingcap/tidb into addWarning4bind…
Reminiscent Mar 10, 2022
7b1a3f7
fix ut
Reminiscent Mar 10, 2022
7fd22f7
fix ut
Reminiscent Mar 10, 2022
1094fd3
Merge branch 'master' of github.com:pingcap/tidb into addWarning4bind…
Reminiscent Mar 10, 2022
985f3af
address comments
Reminiscent Mar 10, 2022
afe84a7
address comments
Reminiscent Mar 14, 2022
77598b4
Merge branch 'master' of github.com:pingcap/tidb into addWarning4bind…
Reminiscent Mar 14, 2022
3691299
address comments
Reminiscent Mar 15, 2022
5525271
Merge branch 'master' of github.com:pingcap/tidb into addWarning4bind…
Reminiscent Mar 15, 2022
6ddf3b2
Merge branch 'master' into addWarning4bindCache
ti-chi-bot Mar 15, 2022
c08a031
fix ut
Reminiscent Mar 15, 2022
3ecd695
Merge branch 'master' of github.com:pingcap/tidb into addWarning4bind…
Reminiscent Mar 15, 2022
9cdbb67
Merge remote-tracking branch 'origin/addWarning4bindCache' into addWa…
Reminiscent Mar 15, 2022
43db335
Merge branch 'master' into addWarning4bindCache
ti-chi-bot Mar 15, 2022
ee3f7ef
Merge branch 'master' into addWarning4bindCache
ti-chi-bot Mar 15, 2022
8ef10da
Merge branch 'master' of github.com:pingcap/tidb into addWarning4bind…
Reminiscent Mar 15, 2022
8e209b5
fix ut
Reminiscent Mar 15, 2022
4425015
Merge remote-tracking branch 'origin/addWarning4bindCache' into addWa…
Reminiscent Mar 15, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 30 additions & 13 deletions bindinfo/bind_cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package bindinfo

import (
"errors"
"sync"

"github.com/cznic/mathutil"
Expand Down Expand Up @@ -54,7 +55,7 @@ func newBindCache() *bindCache {
cache := kvcache.NewSimpleLRUCache(mathutil.MaxUint, 0, 0)
c := bindCache{
cache: cache,
memCapacity: variable.MemQuotaBindCache.Load(),
memCapacity: variable.MemQuotaBindingCache.Load(),
memTracker: memory.NewTracker(memory.LabelForBindCache, -1),
}
return &c
Expand All @@ -75,26 +76,30 @@ func (c *bindCache) get(key bindCacheKey) []*BindRecord {

// set inserts an item to the cache. It's not thread-safe.
// Only other functions of the bindCache can use this function.
func (c *bindCache) set(key bindCacheKey, value []*BindRecord) bool {
// The set operation will return error message when the memory usage of binding_cache exceeds its capacity.
func (c *bindCache) set(key bindCacheKey, value []*BindRecord) (ok bool, err error) {
mem := calcBindCacheKVMem(key, value)
if mem > c.memCapacity { // ignore this kv pair if its size is too large
return false
err = errors.New("The memory usage of the binding_cache exceeds its capacity")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's a bit strange here. Actually we haven't exceeded the capacity yet here, because we didn't put this binding into the cache

return
}
bindRecords := c.get(key)
if bindRecords != nil {
// Remove the origin key-value pair.
mem -= calcBindCacheKVMem(key, bindRecords)
}
for mem+c.memTracker.BytesConsumed() > c.memCapacity {
err = errors.New("The memory usage of the binding_cache exceeds its capacity")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto. If we delete those old cache entries, we won't exceed the capacity.

evictedKey, evictedValue, evicted := c.cache.RemoveOldest()
if !evicted {
return false
return
}
c.memTracker.Consume(-calcBindCacheKVMem(evictedKey.(bindCacheKey), evictedValue.([]*BindRecord)))
}
c.memTracker.Consume(mem)
c.cache.Put(key, value)
return true
ok = true
return
}

// delete remove an item from the cache. It's not thread-safe.
Expand All @@ -105,8 +110,9 @@ func (c *bindCache) delete(key bindCacheKey) bool {
mem := calcBindCacheKVMem(key, bindRecords)
c.cache.Delete(key)
c.memTracker.Consume(-mem)
return true
}
return true
return false
Comment on lines +129 to +131
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a bug here, But for now, the return value has not been used yet.

}

// GetBindRecord gets the BindRecord from the cache.
Expand Down Expand Up @@ -140,7 +146,7 @@ func (c *bindCache) GetAllBindRecords() []*BindRecord {

// SetBindRecord sets the BindRecord to the cache.
// The function is thread-safe.
func (c *bindCache) SetBindRecord(hash string, meta *BindRecord) {
func (c *bindCache) SetBindRecord(hash string, meta *BindRecord) (err error) {
c.lock.Lock()
defer c.lock.Unlock()
cacheKey := bindCacheKey(hash)
Expand All @@ -150,7 +156,11 @@ func (c *bindCache) SetBindRecord(hash string, meta *BindRecord) {
metas[i] = meta
}
}
c.set(cacheKey, []*BindRecord{meta})
_, err = c.set(cacheKey, []*BindRecord{meta})
if err != nil {
return
}
return
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems the if check is unnecessary. 🤣

}

// RemoveBindRecord removes the BindRecord which has same originSQL with specified BindRecord.
Expand All @@ -175,7 +185,9 @@ func (c *bindCache) RemoveBindRecord(hash string, meta *BindRecord) {
}
}
}
c.set(bindCacheKey(hash), metas)
// This function can guarantee the memory usage for the cache will never grow up.
// So we don't need to handle the return value here.
_, _ = c.set(bindCacheKey(hash), metas)
}

// SetMemCapacity sets the memory capacity for the cache.
Expand All @@ -197,17 +209,22 @@ func (c *bindCache) GetMemCapacity() int64 {

// Copy copies a new bindCache from the origin cache.
// The function is thread-safe.
func (c *bindCache) Copy() *bindCache {
func (c *bindCache) Copy() (newCache *bindCache, err error) {
c.lock.Lock()
defer c.lock.Unlock()
newCache := newBindCache()
newCache = newBindCache()
if c.memTracker.BytesConsumed() > newCache.GetMemCapacity() {
err = errors.New("The memory usage of the binding_cache exceeds its capacity")
}
keys := c.cache.Keys()
for _, key := range keys {
cacheKey := key.(bindCacheKey)
v := c.get(cacheKey)
bindRecords := make([]*BindRecord, len(v))
copy(bindRecords, v)
newCache.set(cacheKey, bindRecords)
// The memory usage of cache has been handled at the beginning of this function.
// So we don't need to handle the return value here.
_, _ = newCache.set(cacheKey, bindRecords)
}
return newCache
return newCache, err
}
26 changes: 21 additions & 5 deletions bindinfo/bind_cache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,39 +25,55 @@ import (
)

func TestBindCache(t *testing.T) {
variable.MemQuotaBindCache.Store(100)
variable.MemQuotaBindingCache.Store(200)
bindCache := newBindCache()

value := make([][]*BindRecord, 3)
key := make([]bindCacheKey, 3)
var bigKey string
for i := 0; i < 3; i++ {
cacheKey := strings.Repeat(strconv.Itoa(i), 50)
key[i] = bindCacheKey(hack.Slice(cacheKey))
record := &BindRecord{OriginalSQL: cacheKey, Db: ""}
value[i] = []*BindRecord{record}
bigKey += cacheKey

require.Equal(t, int64(100), calcBindCacheKVMem(key[i], value[i]))
}

ok := bindCache.set(key[0], value[0])
ok, err := bindCache.set(key[0], value[0])
require.True(t, ok)
require.Nil(t, err)
result := bindCache.get(key[0])
require.NotNil(t, result)

ok = bindCache.set(key[1], value[1])
ok, err = bindCache.set(key[1], value[1])
require.True(t, ok)
require.Nil(t, err)
result = bindCache.get(key[1])
require.NotNil(t, result)

ok = bindCache.set(key[2], value[2])
ok, err = bindCache.set(key[2], value[2])
require.True(t, ok)
require.NotNil(t, err)
result = bindCache.get(key[2])
require.NotNil(t, result)

// Both key[0] and key[1] are not in the cache
// key[0] is not in the cache
result = bindCache.get(key[0])
require.Nil(t, result)

// key[1] is still in the cache
result = bindCache.get(key[1])
require.NotNil(t, result)

bigBindCacheKey := bindCacheKey(hack.Slice(bigKey))
bigRecord := &BindRecord{OriginalSQL: bigKey, Db: ""}
bigBindCacheValue := []*BindRecord{bigRecord}
require.Equal(t, int64(300), calcBindCacheKVMem(bigBindCacheKey, bigBindCacheValue))
ok, err = bindCache.set(bigBindCacheKey, bigBindCacheValue)
require.False(t, ok)
require.NotNil(t, err)
result = bindCache.get(bigBindCacheKey)
require.Nil(t, result)
}
39 changes: 32 additions & 7 deletions bindinfo/handle.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ func (h *BindHandle) Update(fullLoad bool) (err error) {
return err
}

newCache := h.bindInfo.Value.Load().(*bindCache).Copy()
newCache, memExceededErr := h.bindInfo.Value.Load().(*bindCache).Copy()
defer func() {
h.bindInfo.lastUpdateTime = lastUpdateTime
h.bindInfo.Value.Store(newCache)
Expand Down Expand Up @@ -171,12 +171,21 @@ func (h *BindHandle) Update(fullLoad bool) (err error) {
oldRecord := newCache.GetBindRecord(hash, meta.OriginalSQL, meta.Db)
newRecord := merge(oldRecord, meta).removeDeletedBindings()
if len(newRecord.Bindings) > 0 {
newCache.SetBindRecord(hash, newRecord)
err = newCache.SetBindRecord(hash, newRecord)
if err != nil && memExceededErr != nil {
memExceededErr = err
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we break this loop if meet this error?

}
} else {
newCache.RemoveBindRecord(hash, newRecord)
}
updateMetrics(metrics.ScopeGlobal, oldRecord, newCache.GetBindRecord(hash, meta.OriginalSQL, meta.Db), true)
}
if memExceededErr != nil {
// When the memory capacity of bing_cache is not enough,
// there will be some memory-related errors in multiple places.
// Only needs to be handled once.
logutil.BgLogger().Warn("[sql-bind] ", zap.Error(err))
}
return nil
}

Expand Down Expand Up @@ -570,27 +579,43 @@ func (h *BindHandle) newBindRecord(row chunk.Row) (string, *BindRecord, error) {
// setBindRecord sets the BindRecord to the cache, if there already exists a BindRecord,
// it will be overridden.
func (h *BindHandle) setBindRecord(hash string, meta *BindRecord) {
newCache := h.bindInfo.Value.Load().(*bindCache).Copy()
newCache, err0 := h.bindInfo.Value.Load().(*bindCache).Copy()
if err0 != nil {
logutil.BgLogger().Warn("[sql-bind] ", zap.Error(err0))
}
oldRecord := newCache.GetBindRecord(hash, meta.OriginalSQL, meta.Db)
newCache.SetBindRecord(hash, meta)
err1 := newCache.SetBindRecord(hash, meta)
if err1 != nil && err0 == nil {
logutil.BgLogger().Warn("[sql-bind] ", zap.Error(err1))
}
h.bindInfo.Value.Store(newCache)
updateMetrics(metrics.ScopeGlobal, oldRecord, meta, false)
}

// appendBindRecord addes the BindRecord to the cache, all the stale BindRecords are
// removed from the cache after this operation.
func (h *BindHandle) appendBindRecord(hash string, meta *BindRecord) {
newCache := h.bindInfo.Value.Load().(*bindCache).Copy()
newCache, err0 := h.bindInfo.Value.Load().(*bindCache).Copy()
if err0 != nil {
logutil.BgLogger().Warn("[sql-bind] ", zap.Error(err0))
}
oldRecord := newCache.GetBindRecord(hash, meta.OriginalSQL, meta.Db)
newRecord := merge(oldRecord, meta)
newCache.SetBindRecord(hash, newRecord)
err1 := newCache.SetBindRecord(hash, newRecord)
if err1 != nil && err0 == nil {
// Only need to handle the error once.
logutil.BgLogger().Warn("[sql-bind] ", zap.Error(err1))
}
h.bindInfo.Value.Store(newCache)
updateMetrics(metrics.ScopeGlobal, oldRecord, newRecord, false)
}

// removeBindRecord removes the BindRecord from the cache.
func (h *BindHandle) removeBindRecord(hash string, meta *BindRecord) {
newCache := h.bindInfo.Value.Load().(*bindCache).Copy()
newCache, err := h.bindInfo.Value.Load().(*bindCache).Copy()
if err != nil {
logutil.BgLogger().Warn("[sql-bind] ", zap.Error(err))
}
oldRecord := newCache.GetBindRecord(hash, meta.OriginalSQL, meta.Db)
newCache.RemoveBindRecord(hash, meta)
h.bindInfo.Value.Store(newCache)
Expand Down
12 changes: 10 additions & 2 deletions bindinfo/session_handle.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import (
"github.com/pingcap/tidb/parser/mysql"
"github.com/pingcap/tidb/sessionctx"
"github.com/pingcap/tidb/types"
"github.com/pingcap/tidb/util/logutil"
"go.uber.org/zap"
)

// SessionHandle is used to handle all session sql bind operations.
Expand All @@ -42,7 +44,10 @@ func NewSessionBindHandle(parser *parser.Parser) *SessionHandle {
// removed from the cache after this operation.
func (h *SessionHandle) appendBindRecord(hash string, meta *BindRecord) {
oldRecord := h.ch.GetBindRecord(hash, meta.OriginalSQL, meta.Db)
h.ch.SetBindRecord(hash, meta)
err := h.ch.SetBindRecord(hash, meta)
if err != nil {
logutil.BgLogger().Warn("[sql-bind] ", zap.Error(err))
}
updateMetrics(metrics.ScopeSession, oldRecord, meta, false)
}

Expand Down Expand Up @@ -80,7 +85,10 @@ func (h *SessionHandle) DropBindRecord(originalSQL, db string, binding *Binding)
} else {
newRecord = record
}
h.ch.SetBindRecord(hash, newRecord)
err := h.ch.SetBindRecord(hash, newRecord)
if err != nil {
logutil.BgLogger().Warn("[sql-bind] ", zap.Error(err))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this case possibly happen?

}
updateMetrics(metrics.ScopeSession, oldRecord, newRecord, false)
return nil
}
Expand Down
4 changes: 2 additions & 2 deletions domain/sysvar_cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,8 +164,8 @@ func (do *Domain) rebuildSysVarCache(ctx sessionctx.Context) error {
func (do *Domain) checkEnableServerGlobalVar(name, sVal string) {
var err error
switch name {
case variable.TiDBMemQuotaBindCache:
variable.MemQuotaBindCache.Store(variable.TidbOptInt64(sVal, variable.DefTiDBMemQuotaBindCache))
case variable.TiDBMemQuotaBindingCache:
variable.MemQuotaBindingCache.Store(variable.TidbOptInt64(sVal, variable.DefTiDBMemQuotaBindingCache))
case variable.TiDBTSOClientBatchMaxWaitTime:
var val float64
val, err = strconv.ParseFloat(sVal, 64)
Expand Down
18 changes: 9 additions & 9 deletions executor/set_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -393,15 +393,15 @@ func TestSetVar(t *testing.T) {
tk.MustQuery(`select @@tidb_mem_quota_apply_cache`).Check(testkit.Rows("123"))

// test for tidb_mem_quota_bind_cache
defVal = fmt.Sprintf("%v", variable.DefTiDBMemQuotaBindCache)
tk.MustQuery(`select @@tidb_mem_quota_bind_cache`).Check(testkit.Rows(defVal))
tk.MustExec(`set global tidb_mem_quota_bind_cache = 1`)
tk.MustQuery(`select @@global.tidb_mem_quota_bind_cache`).Check(testkit.Rows("1"))
tk.MustExec(`set global tidb_mem_quota_bind_cache = 0`)
tk.MustQuery(`select @@global.tidb_mem_quota_bind_cache`).Check(testkit.Rows("0"))
tk.MustExec(`set global tidb_mem_quota_bind_cache = 123`)
tk.MustQuery(`select @@global.tidb_mem_quota_bind_cache`).Check(testkit.Rows("123"))
tk.MustQuery(`select @@global.tidb_mem_quota_bind_cache`).Check(testkit.Rows("123"))
defVal = fmt.Sprintf("%v", variable.DefTiDBMemQuotaBindingCache)
tk.MustQuery(`select @@tidb_mem_quota_binding_cache`).Check(testkit.Rows(defVal))
tk.MustExec(`set global tidb_mem_quota_binding_cache = 1`)
tk.MustQuery(`select @@global.tidb_mem_quota_binding_cache`).Check(testkit.Rows("1"))
tk.MustExec(`set global tidb_mem_quota_binding_cache = 0`)
tk.MustQuery(`select @@global.tidb_mem_quota_binding_cache`).Check(testkit.Rows("0"))
tk.MustExec(`set global tidb_mem_quota_binding_cache = 123`)
tk.MustQuery(`select @@global.tidb_mem_quota_binding_cache`).Check(testkit.Rows("123"))
tk.MustQuery(`select @@global.tidb_mem_quota_binding_cache`).Check(testkit.Rows("123"))

// test for tidb_enable_parallel_apply
tk.MustQuery(`select @@tidb_enable_parallel_apply`).Check(testkit.Rows("0"))
Expand Down
6 changes: 3 additions & 3 deletions sessionctx/variable/sysvar.go
Original file line number Diff line number Diff line change
Expand Up @@ -624,10 +624,10 @@ var defaultSysVars = []*SysVar{
s.MemQuotaApplyCache = TidbOptInt64(val, DefTiDBMemQuotaApplyCache)
return nil
}},
{Scope: ScopeGlobal, Name: TiDBMemQuotaBindCache, Value: strconv.FormatInt(DefTiDBMemQuotaBindCache, 10), Type: TypeUnsigned, MaxValue: math.MaxInt32, GetGlobal: func(sv *SessionVars) (string, error) {
return strconv.FormatInt(MemQuotaBindCache.Load(), 10), nil
{Scope: ScopeGlobal, Name: TiDBMemQuotaBindingCache, Value: strconv.FormatInt(DefTiDBMemQuotaBindingCache, 10), Type: TypeUnsigned, MaxValue: math.MaxInt32, GetGlobal: func(sv *SessionVars) (string, error) {
return strconv.FormatInt(MemQuotaBindingCache.Load(), 10), nil
}, SetGlobal: func(s *SessionVars, val string) error {
MemQuotaBindCache.Store(TidbOptInt64(val, DefTiDBMemQuotaBindCache))
MemQuotaBindingCache.Store(TidbOptInt64(val, DefTiDBMemQuotaBindingCache))
return nil
}},
{Scope: ScopeGlobal | ScopeSession, Name: TiDBBackoffLockFast, Value: strconv.Itoa(tikvstore.DefBackoffLockFast), Type: TypeUnsigned, MinValue: 1, MaxValue: math.MaxInt32, SetSession: func(s *SessionVars, val string) error {
Expand Down
8 changes: 4 additions & 4 deletions sessionctx/variable/tidb_vars.go
Original file line number Diff line number Diff line change
Expand Up @@ -636,8 +636,8 @@ const (
TiDBDisableColumnTrackingTime = "tidb_disable_column_tracking_time"
// TiDBStatsLoadPseudoTimeout indicates whether to fallback to pseudo stats after load timeout.
TiDBStatsLoadPseudoTimeout = "tidb_stats_load_pseudo_timeout"
// TiDBMemQuotaBindCache indicates the memory quota for the bind cache.
TiDBMemQuotaBindCache = "tidb_mem_quota_bind_cache"
// TiDBMemQuotaBindingCache indicates the memory quota for the bind cache.
TiDBMemQuotaBindingCache = "tidb_mem_quota_binding_cache"
)

// TiDB intentional limits
Expand Down Expand Up @@ -698,7 +698,7 @@ const (
DefMaxPreparedStmtCount = -1
DefWaitTimeout = 28800
DefTiDBMemQuotaApplyCache = 32 << 20 // 32MB.
DefTiDBMemQuotaBindCache = 64 << 20 // 64MB.
DefTiDBMemQuotaBindingCache = 64 << 20 // 64MB.
DefTiDBGeneralLog = false
DefTiDBPProfSQLCPU = 0
DefTiDBRetryLimit = 10
Expand Down Expand Up @@ -834,5 +834,5 @@ var (
EnableColumnTracking = atomic.NewBool(DefTiDBEnableColumnTracking)
StatsLoadSyncWait = atomic.NewInt64(DefTiDBStatsLoadSyncWait)
StatsLoadPseudoTimeout = atomic.NewBool(DefTiDBStatsLoadPseudoTimeout)
MemQuotaBindCache = atomic.NewInt64(DefTiDBMemQuotaBindCache)
MemQuotaBindingCache = atomic.NewInt64(DefTiDBMemQuotaBindingCache)
)