Skip to content

Commit 07fbf65

Browse files
committed
infoschema: fix issue of information schema cache miss cause by schema version gap (pingcap#53445)
close pingcap#53428 Signed-off-by: crazycs520 <[email protected]>
1 parent add7db2 commit 07fbf65

File tree

3 files changed

+107
-6
lines changed

3 files changed

+107
-6
lines changed

domain/domain.go

+2
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,8 @@ func (do *Domain) tryLoadSchemaDiffs(m *meta.Meta, usedVersion, newVersion int64
370370
if diff == nil {
371371
// Empty diff means the txn of generating schema version is committed, but the txn of `runDDLJob` is not or fail.
372372
// It is safe to skip the empty diff because the infoschema is new enough and consistent.
373+
logutil.BgLogger().Info("diff load InfoSchema get empty schema diff", zap.Int64("version", usedVersion))
374+
do.infoCache.InsertEmptySchemaVersion(usedVersion)
373375
continue
374376
}
375377
diffs = append(diffs, diff)

infoschema/cache.go

+58-6
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ type InfoCache struct {
4040
mu sync.RWMutex
4141
// cache is sorted by both SchemaVersion and timestamp in descending order, assume they have same order
4242
cache []schemaAndTimestamp
43+
44+
// emptySchemaVersions stores schema version which has no schema_diff.
45+
emptySchemaVersions map[int64]struct{}
4346
}
4447

4548
type schemaAndTimestamp struct {
@@ -50,7 +53,8 @@ type schemaAndTimestamp struct {
5053
// NewCache creates a new InfoCache.
5154
func NewCache(capacity int) *InfoCache {
5255
return &InfoCache{
53-
cache: make([]schemaAndTimestamp, 0, capacity),
56+
cache: make([]schemaAndTimestamp, 0, capacity),
57+
emptySchemaVersions: make(map[int64]struct{}),
5458
}
5559
}
5660

@@ -85,6 +89,11 @@ func (h *InfoCache) Len() int {
8589
return len(h.cache)
8690
}
8791

92+
// GetEmptySchemaVersions returns emptySchemaVersions, exports for testing.
93+
func (h *InfoCache) GetEmptySchemaVersions() map[int64]struct{} {
94+
return h.emptySchemaVersions
95+
}
96+
8897
func (h *InfoCache) getSchemaByTimestampNoLock(ts uint64) (InfoSchema, bool) {
8998
logutil.BgLogger().Debug("SCHEMA CACHE get schema", zap.Uint64("timestamp", ts))
9099
// search one by one instead of binary search, because the timestamp of a schema could be 0
@@ -102,11 +111,32 @@ func (h *InfoCache) getSchemaByTimestampNoLock(ts uint64) (InfoSchema, bool) {
102111
// the first element is the latest schema, so we can return it directly.
103112
return is.infoschema, true
104113
}
105-
if h.cache[i-1].infoschema.SchemaMetaVersion() == is.infoschema.SchemaMetaVersion()+1 && uint64(h.cache[i-1].timestamp) > ts {
106-
// This first condition is to make sure the schema version is continuous. If last(cache[i-1]) schema-version is 10,
107-
// but current(cache[i]) schema-version is not 9, then current schema is not suitable for ts.
108-
// The second condition is to make sure the cache[i-1].timestamp > ts >= cache[i].timestamp, then the current schema is suitable for ts.
109-
return is.infoschema, true
114+
115+
if uint64(h.cache[i-1].timestamp) > ts {
116+
// The first condition is to make sure the cache[i-1].timestamp > ts >= cache[i].timestamp, then the current schema is suitable for ts.
117+
lastVersion := h.cache[i-1].infoschema.SchemaMetaVersion()
118+
currentVersion := is.infoschema.SchemaMetaVersion()
119+
if lastVersion == currentVersion+1 {
120+
// This condition is to make sure the schema version is continuous. If last(cache[i-1]) schema-version is 10,
121+
// but current(cache[i]) schema-version is not 9, then current schema may not suitable for ts.
122+
return is.infoschema, true
123+
}
124+
if lastVersion > currentVersion {
125+
found := true
126+
for ver := currentVersion + 1; ver < lastVersion; ver++ {
127+
_, ok := h.emptySchemaVersions[ver]
128+
if !ok {
129+
found = false
130+
break
131+
}
132+
}
133+
if found {
134+
// This condition is to make sure the schema version is continuous. If last(cache[i-1]) schema-version is 10, and
135+
// current(cache[i]) schema-version is 8, then there is a gap exist, and if all the gap version can be found in cache.emptySchemaVersions
136+
// which means those gap versions don't have schema info, then current schema is also suitable for ts.
137+
return is.infoschema, true
138+
}
139+
}
110140
}
111141
// current schema is not suitable for ts, then break the loop to avoid the unnecessary search.
112142
break
@@ -216,3 +246,25 @@ func (h *InfoCache) Insert(is InfoSchema, schemaTS uint64) bool {
216246

217247
return true
218248
}
249+
250+
// InsertEmptySchemaVersion inserts empty schema version into a map. If exceeded the cache capacity, remove the oldest version.
251+
func (h *InfoCache) InsertEmptySchemaVersion(version int64) {
252+
h.mu.Lock()
253+
defer h.mu.Unlock()
254+
255+
h.emptySchemaVersions[version] = struct{}{}
256+
if len(h.emptySchemaVersions) > cap(h.cache) {
257+
// remove oldest version.
258+
versions := make([]int64, 0, len(h.emptySchemaVersions))
259+
for ver := range h.emptySchemaVersions {
260+
versions = append(versions, ver)
261+
}
262+
sort.Slice(versions, func(i, j int) bool { return versions[i] < versions[j] })
263+
for _, ver := range versions {
264+
delete(h.emptySchemaVersions, ver)
265+
if len(h.emptySchemaVersions) <= cap(h.cache) {
266+
break
267+
}
268+
}
269+
}
270+
}

infoschema/cache_test.go

+47
Original file line numberDiff line numberDiff line change
@@ -256,4 +256,51 @@ func TestCacheWithSchemaTsZero(t *testing.T) {
256256
checkFn(1, 84, false)
257257
checkFn(85, 100, true)
258258
require.Equal(t, 16, ic.Size())
259+
260+
// Test cache with schema version hole, which is cause by schema version doesn't has related schema-diff.
261+
ic = infoschema.NewCache(16)
262+
require.NotNil(t, ic)
263+
for i := 1; i <= 8; i++ {
264+
ic.Insert(infoschema.MockInfoSchemaWithSchemaVer(nil, int64(i)), uint64(i))
265+
}
266+
checkFn(1, 10, true)
267+
// mock for schema version hole, schema-version 9 is missing.
268+
ic.Insert(infoschema.MockInfoSchemaWithSchemaVer(nil, 10), 10)
269+
checkFn(1, 7, true)
270+
// without empty schema version map, get snapshot by ts 8, 9 will both failed.
271+
checkFn(8, 9, false)
272+
checkFn(10, 10, true)
273+
// add empty schema version 9.
274+
ic.InsertEmptySchemaVersion(9)
275+
// after set empty schema version, get snapshot by ts 8, 9 will both success.
276+
checkFn(1, 8, true)
277+
checkFn(10, 10, true)
278+
is := ic.GetBySnapshotTS(uint64(9))
279+
require.NotNil(t, is)
280+
// since schema version 9 is empty, so get by ts 9 will get schema which version is 8.
281+
require.Equal(t, int64(8), is.SchemaMetaVersion())
282+
}
283+
284+
func TestCacheEmptySchemaVersion(t *testing.T) {
285+
ic := infoschema.NewCache(16)
286+
require.NotNil(t, ic)
287+
require.Equal(t, 0, len(ic.GetEmptySchemaVersions()))
288+
for i := 0; i < 16; i++ {
289+
ic.InsertEmptySchemaVersion(int64(i))
290+
}
291+
emptyVersions := ic.GetEmptySchemaVersions()
292+
require.Equal(t, 16, len(emptyVersions))
293+
for i := 0; i < 16; i++ {
294+
_, ok := emptyVersions[int64(i)]
295+
require.True(t, ok)
296+
}
297+
for i := 16; i < 20; i++ {
298+
ic.InsertEmptySchemaVersion(int64(i))
299+
}
300+
emptyVersions = ic.GetEmptySchemaVersions()
301+
require.Equal(t, 16, len(emptyVersions))
302+
for i := 4; i < 20; i++ {
303+
_, ok := emptyVersions[int64(i)]
304+
require.True(t, ok)
305+
}
259306
}

0 commit comments

Comments
 (0)