Skip to content

Commit

Permalink
infoschema: refine info cache logic to reduce the impact of DDL on in…
Browse files Browse the repository at this point in the history
…formation schema cache (pingcap#48284)

close pingcap#48285

Signed-off-by: crazycs520 <[email protected]>
  • Loading branch information
crazycs520 committed May 27, 2024
1 parent 6daf8fe commit add7db2
Show file tree
Hide file tree
Showing 2 changed files with 102 additions and 6 deletions.
28 changes: 22 additions & 6 deletions infoschema/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,13 @@ func NewCache(capacity int) *InfoCache {
}
}

// Size returns the size of the cache, export for test.
func (h *InfoCache) Size() int {
h.mu.Lock()
defer h.mu.Unlock()
return len(h.cache)
}

// Reset resets the cache.
func (h *InfoCache) Reset(capacity int) {
h.mu.Lock()
Expand Down Expand Up @@ -85,15 +92,24 @@ func (h *InfoCache) getSchemaByTimestampNoLock(ts uint64) (InfoSchema, bool) {
// moreover, the most likely hit element in the array is the first one in steady mode
// thus it may have better performance than binary search
for i, is := range h.cache {
if is.timestamp == 0 || (i > 0 && h.cache[i-1].infoschema.SchemaMetaVersion() != is.infoschema.SchemaMetaVersion()+1) {
// the schema version doesn't have a timestamp or there is a gap in the schema cache
// ignore all the schema cache equals or less than this version in search by timestamp
break
if is.timestamp == 0 || ts < uint64(is.timestamp) {
// is.timestamp == 0 means the schema ts is unknown, so we can't use it, then just skip it.
// ts < is.timestamp means the schema is newer than ts, so we can't use it too, just skip it to find the older one.
continue
}
// ts >= is.timestamp must be true after the above condition.
if i == 0 {
// the first element is the latest schema, so we can return it directly.
return is.infoschema, true
}
if ts >= uint64(is.timestamp) {
// found the largest version before the given ts
if h.cache[i-1].infoschema.SchemaMetaVersion() == is.infoschema.SchemaMetaVersion()+1 && uint64(h.cache[i-1].timestamp) > ts {
// This first condition is to make sure the schema version is continuous. If last(cache[i-1]) schema-version is 10,
// but current(cache[i]) schema-version is not 9, then current schema is not suitable for ts.
// The second condition is to make sure the cache[i-1].timestamp > ts >= cache[i].timestamp, then the current schema is suitable for ts.
return is.infoschema, true
}
// current schema is not suitable for ts, then break the loop to avoid the unnecessary search.
break
}

logutil.BgLogger().Debug("SCHEMA CACHE no schema found")
Expand Down
80 changes: 80 additions & 0 deletions infoschema/cache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package infoschema_test

import (
"fmt"
"testing"

"github.com/pingcap/tidb/infoschema"
Expand Down Expand Up @@ -177,3 +178,82 @@ func TestGetByTimestamp(t *testing.T) {
require.Equal(t, is3, ic.GetBySnapshotTS(3))
require.Equal(t, 3, ic.Len())
}

func TestCacheWithSchemaTsZero(t *testing.T) {
ic := infoschema.NewCache(16)
require.NotNil(t, ic)

for i := 1; i <= 8; i++ {
ic.Insert(infoschema.MockInfoSchemaWithSchemaVer(nil, int64(i)), uint64(i))
}

checkFn := func(start, end int64, exist bool) {
require.True(t, start <= end)
latestSchemaVersion := ic.GetLatest().SchemaMetaVersion()
for ts := start; ts <= end; ts++ {
is := ic.GetBySnapshotTS(uint64(ts))
if exist {
require.NotNil(t, is, fmt.Sprintf("ts %d", ts))
if ts > latestSchemaVersion {
require.Equal(t, latestSchemaVersion, is.SchemaMetaVersion(), fmt.Sprintf("ts %d", ts))
} else {
require.Equal(t, ts, is.SchemaMetaVersion(), fmt.Sprintf("ts %d", ts))
}
} else {
require.Nil(t, is, fmt.Sprintf("ts %d", ts))
}
}
}
checkFn(1, 8, true)
checkFn(8, 10, true)

// mock for meet error There is no Write MVCC info for the schema version
ic.Insert(infoschema.MockInfoSchemaWithSchemaVer(nil, 9), 0)
checkFn(1, 7, true)
checkFn(8, 9, false)
checkFn(9, 10, false)

for i := 10; i <= 16; i++ {
ic.Insert(infoschema.MockInfoSchemaWithSchemaVer(nil, int64(i)), uint64(i))
checkFn(1, 7, true)
checkFn(8, 9, false)
checkFn(10, 16, true)
}
require.Equal(t, 16, ic.Size())

// refill the cache
ic.Insert(infoschema.MockInfoSchemaWithSchemaVer(nil, 9), 9)
checkFn(1, 16, true)
require.Equal(t, 16, ic.Size())

// Test more than capacity
ic.Insert(infoschema.MockInfoSchemaWithSchemaVer(nil, 17), 17)
checkFn(1, 1, false)
checkFn(2, 17, true)
checkFn(2, 20, true)
require.Equal(t, 16, ic.Size())

// Test for there is a hole in the middle.
ic = infoschema.NewCache(16)

// mock for restart with full load the latest version schema.
ic.Insert(infoschema.MockInfoSchemaWithSchemaVer(nil, 100), 100)
checkFn(1, 99, false)
checkFn(100, 100, true)

for i := 1; i <= 16; i++ {
ic.Insert(infoschema.MockInfoSchemaWithSchemaVer(nil, int64(i)), uint64(i))
}
checkFn(1, 1, false)
checkFn(2, 15, true)
checkFn(16, 16, false)
checkFn(100, 100, true)
require.Equal(t, 16, ic.Size())

for i := 85; i < 100; i++ {
ic.Insert(infoschema.MockInfoSchemaWithSchemaVer(nil, int64(i)), uint64(i))
}
checkFn(1, 84, false)
checkFn(85, 100, true)
require.Equal(t, 16, ic.Size())
}

0 comments on commit add7db2

Please sign in to comment.