Skip to content

Commit 339d391

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 4be26db commit 339d391

File tree

3 files changed

+107
-3
lines changed

3 files changed

+107
-3
lines changed

domain/domain.go

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

infoschema/cache.go

+58-3
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

@@ -102,6 +106,11 @@ func (h *InfoCache) Len() int {
102106
return len(h.cache)
103107
}
104108

109+
// GetEmptySchemaVersions returns emptySchemaVersions, exports for testing.
110+
func (h *InfoCache) GetEmptySchemaVersions() map[int64]struct{} {
111+
return h.emptySchemaVersions
112+
}
113+
105114
func (h *InfoCache) getSchemaByTimestampNoLock(ts uint64) (InfoSchema, bool) {
106115
logutil.BgLogger().Debug("SCHEMA CACHE get schema", zap.Uint64("timestamp", ts))
107116
// search one by one instead of binary search, because the timestamp of a schema could be 0
@@ -115,8 +124,32 @@ func (h *InfoCache) getSchemaByTimestampNoLock(ts uint64) (InfoSchema, bool) {
115124
if i == 0 {
116125
return is.infoschema, true
117126
}
118-
if h.cache[i-1].infoschema.SchemaMetaVersion() == is.infoschema.SchemaMetaVersion()+1 && uint64(h.cache[i-1].timestamp) > ts {
119-
return is.infoschema, true
127+
128+
if uint64(h.cache[i-1].timestamp) > ts {
129+
// The first condition is to make sure the cache[i-1].timestamp > ts >= cache[i].timestamp, then the current schema is suitable for ts.
130+
lastVersion := h.cache[i-1].infoschema.SchemaMetaVersion()
131+
currentVersion := is.infoschema.SchemaMetaVersion()
132+
if lastVersion == currentVersion+1 {
133+
// This condition is to make sure the schema version is continuous. If last(cache[i-1]) schema-version is 10,
134+
// but current(cache[i]) schema-version is not 9, then current schema may not suitable for ts.
135+
return is.infoschema, true
136+
}
137+
if lastVersion > currentVersion {
138+
found := true
139+
for ver := currentVersion + 1; ver < lastVersion; ver++ {
140+
_, ok := h.emptySchemaVersions[ver]
141+
if !ok {
142+
found = false
143+
break
144+
}
145+
}
146+
if found {
147+
// This condition is to make sure the schema version is continuous. If last(cache[i-1]) schema-version is 10, and
148+
// 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
149+
// which means those gap versions don't have schema info, then current schema is also suitable for ts.
150+
return is.infoschema, true
151+
}
152+
}
120153
}
121154
break
122155
}
@@ -225,3 +258,25 @@ func (h *InfoCache) Insert(is InfoSchema, schemaTS uint64) bool {
225258

226259
return true
227260
}
261+
262+
// InsertEmptySchemaVersion inserts empty schema version into a map. If exceeded the cache capacity, remove the oldest version.
263+
func (h *InfoCache) InsertEmptySchemaVersion(version int64) {
264+
h.mu.Lock()
265+
defer h.mu.Unlock()
266+
267+
h.emptySchemaVersions[version] = struct{}{}
268+
if len(h.emptySchemaVersions) > cap(h.cache) {
269+
// remove oldest version.
270+
versions := make([]int64, 0, len(h.emptySchemaVersions))
271+
for ver := range h.emptySchemaVersions {
272+
versions = append(versions, ver)
273+
}
274+
sort.Slice(versions, func(i, j int) bool { return versions[i] < versions[j] })
275+
for _, ver := range versions {
276+
delete(h.emptySchemaVersions, ver)
277+
if len(h.emptySchemaVersions) <= cap(h.cache) {
278+
break
279+
}
280+
}
281+
}
282+
}

infoschema/cache_test.go

+47
Original file line numberDiff line numberDiff line change
@@ -290,4 +290,51 @@ func TestCacheWithSchemaTsZero(t *testing.T) {
290290
checkFn(1, 84, false)
291291
checkFn(85, 100, true)
292292
require.Equal(t, 16, ic.Size())
293+
294+
// Test cache with schema version hole, which is cause by schema version doesn't has related schema-diff.
295+
ic = infoschema.NewCache(16)
296+
require.NotNil(t, ic)
297+
for i := 1; i <= 8; i++ {
298+
ic.Insert(infoschema.MockInfoSchemaWithSchemaVer(nil, int64(i)), uint64(i))
299+
}
300+
checkFn(1, 10, true)
301+
// mock for schema version hole, schema-version 9 is missing.
302+
ic.Insert(infoschema.MockInfoSchemaWithSchemaVer(nil, 10), 10)
303+
checkFn(1, 7, true)
304+
// without empty schema version map, get snapshot by ts 8, 9 will both failed.
305+
checkFn(8, 9, false)
306+
checkFn(10, 10, true)
307+
// add empty schema version 9.
308+
ic.InsertEmptySchemaVersion(9)
309+
// after set empty schema version, get snapshot by ts 8, 9 will both success.
310+
checkFn(1, 8, true)
311+
checkFn(10, 10, true)
312+
is := ic.GetBySnapshotTS(uint64(9))
313+
require.NotNil(t, is)
314+
// since schema version 9 is empty, so get by ts 9 will get schema which version is 8.
315+
require.Equal(t, int64(8), is.SchemaMetaVersion())
316+
}
317+
318+
func TestCacheEmptySchemaVersion(t *testing.T) {
319+
ic := infoschema.NewCache(16)
320+
require.NotNil(t, ic)
321+
require.Equal(t, 0, len(ic.GetEmptySchemaVersions()))
322+
for i := 0; i < 16; i++ {
323+
ic.InsertEmptySchemaVersion(int64(i))
324+
}
325+
emptyVersions := ic.GetEmptySchemaVersions()
326+
require.Equal(t, 16, len(emptyVersions))
327+
for i := 0; i < 16; i++ {
328+
_, ok := emptyVersions[int64(i)]
329+
require.True(t, ok)
330+
}
331+
for i := 16; i < 20; i++ {
332+
ic.InsertEmptySchemaVersion(int64(i))
333+
}
334+
emptyVersions = ic.GetEmptySchemaVersions()
335+
require.Equal(t, 16, len(emptyVersions))
336+
for i := 4; i < 20; i++ {
337+
_, ok := emptyVersions[int64(i)]
338+
require.True(t, ok)
339+
}
293340
}

0 commit comments

Comments
 (0)