Skip to content

Commit e8c198d

Browse files
authored
*: implement the INSPECTION_SCHEMA to provide snapshot of inspection tables (pingcap#14147)
Signed-off-by: Lonng <[email protected]>
1 parent 1fe93b4 commit e8c198d

File tree

9 files changed

+260
-21
lines changed

9 files changed

+260
-21
lines changed

infoschema/infoschema.go

-10
Original file line numberDiff line numberDiff line change
@@ -370,16 +370,6 @@ func init() {
370370
RegisterVirtualTable(infoSchemaDB, createInfoSchemaTable)
371371
}
372372

373-
// IsMemoryDB checks if the db is in memory.
374-
func IsMemoryDB(dbName string) bool {
375-
for _, driver := range drivers {
376-
if driver.DBInfo.Name.L == dbName {
377-
return true
378-
}
379-
}
380-
return false
381-
}
382-
383373
// HasAutoIncrementColumn checks whether the table has auto_increment columns, if so, return true and the column name.
384374
func HasAutoIncrementColumn(tbInfo *model.TableInfo) (bool, string) {
385375
for _, col := range tbInfo.Columns {

infoschema/infoschema_test.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -123,13 +123,13 @@ func (*testSuite) TestT(c *C) {
123123
is := handle.Get()
124124

125125
schemaNames := is.AllSchemaNames()
126-
c.Assert(schemaNames, HasLen, 4)
127-
c.Assert(testutil.CompareUnorderedStringSlice(schemaNames, []string{util.InformationSchemaName.O, util.MetricSchemaName.O, util.PerformanceSchemaName.O, "Test"}), IsTrue)
126+
c.Assert(schemaNames, HasLen, 5)
127+
c.Assert(testutil.CompareUnorderedStringSlice(schemaNames, []string{util.InformationSchemaName.O, util.MetricSchemaName.O, util.PerformanceSchemaName.O, "Test", util.InspectionSchemaName.O}), IsTrue)
128128

129129
schemas := is.AllSchemas()
130-
c.Assert(schemas, HasLen, 4)
130+
c.Assert(schemas, HasLen, 5)
131131
schemas = is.Clone()
132-
c.Assert(schemas, HasLen, 4)
132+
c.Assert(schemas, HasLen, 5)
133133

134134
c.Assert(is.SchemaExists(dbName), IsTrue)
135135
c.Assert(is.SchemaExists(noexist), IsFalse)

infoschema/inspection_schema.go

+127
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
// Copyright 2019 PingCAP, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
package infoschema
15+
16+
import (
17+
"fmt"
18+
19+
"github.com/pingcap/errors"
20+
"github.com/pingcap/parser/model"
21+
"github.com/pingcap/parser/mysql"
22+
"github.com/pingcap/tidb/kv"
23+
"github.com/pingcap/tidb/meta/autoid"
24+
"github.com/pingcap/tidb/sessionctx"
25+
"github.com/pingcap/tidb/sessionctx/variable"
26+
"github.com/pingcap/tidb/table"
27+
"github.com/pingcap/tidb/util"
28+
)
29+
30+
// The `inspection_schema` is used to provide a consistent view of `information_schema` tables,
31+
// so the related table should have the same table name within `information_schema`.
32+
// The data will be obtained lazily from `information_schema` and cache in `SessionVars`, and
33+
// the cached data will be cleared at `InspectionExec` closing.
34+
var inspectionTables = map[string][]columnInfo{
35+
tableClusterInfo: tableClusterInfoCols,
36+
TableClusterConfig: tableClusterConfigCols,
37+
TableClusterLoad: tableClusterLoadCols,
38+
TableClusterHardware: tableClusterHardwareCols,
39+
TableClusterSystemInfo: tableClusterSystemInfoCols,
40+
}
41+
42+
type inspectionSchemaTable struct {
43+
infoschemaTable
44+
}
45+
46+
// IterRecords implements table.Table IterRecords interface.
47+
func (it *inspectionSchemaTable) IterRecords(ctx sessionctx.Context, startKey kv.Key, cols []*table.Column,
48+
fn table.RecordIterFunc) error {
49+
sessionVars := ctx.GetSessionVars()
50+
// The `InspectionTableCache` will be assigned in `InspectionExec.Open` and be
51+
// cleaned at `InspectionExec.Close`, so nil represents currently in non-inspection mode.
52+
if sessionVars.InspectionTableCache == nil {
53+
return errors.New("not currently in inspection mode")
54+
}
55+
56+
if len(startKey) != 0 {
57+
return table.ErrUnsupportedOp
58+
}
59+
60+
// Obtain data from cache first.
61+
cached, found := sessionVars.InspectionTableCache[it.meta.Name.L]
62+
if !found {
63+
// We retrieve data from `information_schema` if can found in cache.
64+
rows, err := it.getRows(ctx, cols)
65+
cached = variable.TableSnapshot{
66+
Rows: rows,
67+
Err: err,
68+
}
69+
sessionVars.InspectionTableCache[it.meta.Name.L] = cached
70+
}
71+
if cached.Err != nil {
72+
return cached.Err
73+
}
74+
75+
for i, row := range cached.Rows {
76+
more, err := fn(int64(i), row, cols)
77+
if err != nil {
78+
return err
79+
}
80+
if !more {
81+
break
82+
}
83+
}
84+
return nil
85+
}
86+
87+
func init() {
88+
// Initialize the inspection schema database and register the driver to `drivers`.
89+
dbID := autoid.InspectionSchemaDBID
90+
tables := make([]*model.TableInfo, 0, len(inspectionTables))
91+
for name, cols := range inspectionTables {
92+
tableInfo := buildTableMeta(name, cols)
93+
tables = append(tables, tableInfo)
94+
var ok bool
95+
tid, ok := tableIDMap[tableInfo.Name.O]
96+
if !ok {
97+
panic(fmt.Sprintf("get inspection_schema table id failed, unknown system table `%v`", tableInfo.Name.O))
98+
}
99+
// Reuse information_schema table id serial number.
100+
tableInfo.ID = tid - autoid.InformationSchemaDBID + autoid.InspectionSchemaDBID
101+
for i, c := range tableInfo.Columns {
102+
c.ID = int64(i) + 1
103+
}
104+
}
105+
inspectionSchema := &model.DBInfo{
106+
ID: dbID,
107+
Name: util.InspectionSchemaName,
108+
Charset: mysql.DefaultCharset,
109+
Collate: mysql.DefaultCollationName,
110+
Tables: tables,
111+
}
112+
builder := func(_ autoid.Allocators, meta *model.TableInfo) (table.Table, error) {
113+
columns := make([]*table.Column, len(meta.Columns))
114+
for i, col := range meta.Columns {
115+
columns[i] = table.ToColumn(col)
116+
}
117+
tbl := &inspectionSchemaTable{
118+
infoschemaTable{
119+
meta: meta,
120+
cols: columns,
121+
tp: table.VirtualTable,
122+
},
123+
}
124+
return tbl, nil
125+
}
126+
RegisterVirtualTable(inspectionSchema, builder)
127+
}

infoschema/inspection_schema_test.go

+100
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
// Copyright 2019 PingCAP, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
package infoschema_test
15+
16+
import (
17+
"strings"
18+
19+
. "github.com/pingcap/check"
20+
"github.com/pingcap/failpoint"
21+
"github.com/pingcap/tidb/domain"
22+
"github.com/pingcap/tidb/kv"
23+
"github.com/pingcap/tidb/session"
24+
"github.com/pingcap/tidb/sessionctx/variable"
25+
"github.com/pingcap/tidb/store/mockstore"
26+
"github.com/pingcap/tidb/util/testkit"
27+
"github.com/pingcap/tidb/util/testleak"
28+
)
29+
30+
type inspectionSuite struct {
31+
store kv.Storage
32+
dom *domain.Domain
33+
}
34+
35+
var _ = SerialSuites(&inspectionSuite{})
36+
37+
func (s *inspectionSuite) SetUpSuite(c *C) {
38+
testleak.BeforeTest()
39+
40+
var err error
41+
s.store, err = mockstore.NewMockTikvStore()
42+
c.Assert(err, IsNil)
43+
session.DisableStats4Test()
44+
s.dom, err = session.BootstrapSession(s.store)
45+
c.Assert(err, IsNil)
46+
}
47+
48+
func (s *inspectionSuite) TearDownSuite(c *C) {
49+
s.dom.Close()
50+
s.store.Close()
51+
testleak.AfterTest(c)()
52+
}
53+
54+
func (s *inspectionSuite) TestInspectionTables(c *C) {
55+
tk := testkit.NewTestKit(c, s.store)
56+
instances := []string{
57+
"pd,127.0.0.1:11080,127.0.0.1:10080,mock-version,mock-githash",
58+
"tidb,127.0.0.1:11080,127.0.0.1:10080,mock-version,mock-githash",
59+
"tikv,127.0.0.1:11080,127.0.0.1:10080,mock-version,mock-githash",
60+
}
61+
fpName := "github.com/pingcap/tidb/infoschema/mockClusterInfo"
62+
fpExpr := `return("` + strings.Join(instances, ";") + `")`
63+
c.Assert(failpoint.Enable(fpName, fpExpr), IsNil)
64+
defer func() { c.Assert(failpoint.Disable(fpName), IsNil) }()
65+
66+
tk.MustQuery("select * from information_schema.cluster_info").Check(testkit.Rows(
67+
"pd 127.0.0.1:11080 127.0.0.1:10080 mock-version mock-githash",
68+
"tidb 127.0.0.1:11080 127.0.0.1:10080 mock-version mock-githash",
69+
"tikv 127.0.0.1:11080 127.0.0.1:10080 mock-version mock-githash",
70+
))
71+
72+
// enable inspection mode
73+
inspectionTableCache := map[string]variable.TableSnapshot{}
74+
tk.Se.GetSessionVars().InspectionTableCache = inspectionTableCache
75+
tk.MustQuery("select * from inspection_schema.cluster_info").Check(testkit.Rows(
76+
"pd 127.0.0.1:11080 127.0.0.1:10080 mock-version mock-githash",
77+
"tidb 127.0.0.1:11080 127.0.0.1:10080 mock-version mock-githash",
78+
"tikv 127.0.0.1:11080 127.0.0.1:10080 mock-version mock-githash",
79+
))
80+
c.Assert(inspectionTableCache["cluster_info"].Err, IsNil)
81+
c.Assert(len(inspectionTableCache["cluster_info"].Rows), DeepEquals, 3)
82+
83+
// should invisible to other sessions
84+
tk2 := testkit.NewTestKitWithInit(c, s.store)
85+
err := tk2.QueryToErr("select * from inspection_schema.cluster_info")
86+
c.Assert(err, ErrorMatches, "not currently in inspection mode")
87+
88+
// check whether is obtain data from cache at the next time
89+
inspectionTableCache["cluster_info"].Rows[0][0].SetString("modified-pd")
90+
tk.MustQuery("select * from inspection_schema.cluster_info").Check(testkit.Rows(
91+
"modified-pd 127.0.0.1:11080 127.0.0.1:10080 mock-version mock-githash",
92+
"tidb 127.0.0.1:11080 127.0.0.1:10080 mock-version mock-githash",
93+
"tikv 127.0.0.1:11080 127.0.0.1:10080 mock-version mock-githash",
94+
))
95+
tk.Se.GetSessionVars().InspectionTableCache = nil
96+
97+
// disable inspection mode
98+
err = tk.QueryToErr("select * from inspection_schema.cluster_info")
99+
c.Assert(err, ErrorMatches, "not currently in inspection mode")
100+
}

infoschema/tables_test.go

+4-2
Original file line numberDiff line numberDiff line change
@@ -999,8 +999,10 @@ func (s *testClusterTableSuite) TestForClusterServerInfo(c *C) {
999999

10001000
func (s *testTableSuite) TestSystemSchemaID(c *C) {
10011001
uniqueIDMap := make(map[int64]string)
1002-
s.checkSystemSchemaTableID(c, "information_schema", autoid.SystemSchemaIDFlag|1, 1, 10000, uniqueIDMap)
1003-
s.checkSystemSchemaTableID(c, "performance_schema", autoid.SystemSchemaIDFlag|10000, 10000, 20000, uniqueIDMap)
1002+
s.checkSystemSchemaTableID(c, "information_schema", autoid.InformationSchemaDBID, 1, 10000, uniqueIDMap)
1003+
s.checkSystemSchemaTableID(c, "performance_schema", autoid.PerformanceSchemaDBID, 10000, 20000, uniqueIDMap)
1004+
s.checkSystemSchemaTableID(c, "metric_schema", autoid.MetricSchemaDBID, 20000, 30000, uniqueIDMap)
1005+
s.checkSystemSchemaTableID(c, "inspection_schema", autoid.InspectionSchemaDBID, 30000, 40000, uniqueIDMap)
10041006
}
10051007

10061008
func (s *testTableSuite) checkSystemSchemaTableID(c *C, dbName string, dbID, start, end int64, uniqueIDMap map[int64]string) {

meta/autoid/autoid.go

+2
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ const (
4343
PerformanceSchemaDBID int64 = SystemSchemaIDFlag | 10000
4444
// MetricSchemaDBID is the metric_schema schema id, it's exported for test.
4545
MetricSchemaDBID int64 = SystemSchemaIDFlag | 20000
46+
// InspectionSchemaDBID is the inspection_schema id, it's exports for test.
47+
InspectionSchemaDBID int64 = SystemSchemaIDFlag | 30000
4648
)
4749

4850
const (

server/http_handler_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -688,7 +688,7 @@ func (ts *HTTPHandlerTestSuite) TestGetSchema(c *C) {
688688
var dbs []*model.DBInfo
689689
err = decoder.Decode(&dbs)
690690
c.Assert(err, IsNil)
691-
expects := []string{"information_schema", "metric_schema", "mysql", "performance_schema", "test", "tidb"}
691+
expects := []string{"information_schema", "inspection_schema", "metric_schema", "mysql", "performance_schema", "test", "tidb"}
692692
names := make([]string, len(dbs))
693693
for i, v := range dbs {
694694
names[i] = v.Name.L

sessionctx/variable/session.go

+12
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,12 @@ func (ib *WriteStmtBufs) clean() {
204204
ib.IndexKeyBuf = nil
205205
}
206206

207+
// TableSnapshot represents a data snapshot of the table contained in `inspection_schema`.
208+
type TableSnapshot struct {
209+
Rows [][]types.Datum
210+
Err error
211+
}
212+
207213
// SessionVars is to handle user-defined or global variables in the current session.
208214
type SessionVars struct {
209215
Concurrency
@@ -484,6 +490,12 @@ type SessionVars struct {
484490
MetricSchemaStep int64
485491
// MetricSchemaRangeDuration indicates the step when query metric schema.
486492
MetricSchemaRangeDuration int64
493+
494+
// Some data of cluster-level memory tables will be retrieved many times in different inspection rules,
495+
// and the cost of retrieving some data is expensive. We use the `TableSnapshot` to cache those data
496+
// and obtain them lazily, and provide a consistent view of inspection tables for each inspection rules.
497+
// All cached snapshots will be released at the `InspectionExec` executor closing.
498+
InspectionTableCache map[string]TableSnapshot
487499
}
488500

489501
// PreparedParams contains the parameters of the current prepared statement when executing it.

util/misc.go

+10-4
Original file line numberDiff line numberDiff line change
@@ -139,17 +139,23 @@ func SyntaxWarn(err error) error {
139139

140140
var (
141141
// InformationSchemaName is the `INFORMATION_SCHEMA` database name.
142-
InformationSchemaName = model.CIStr{O: "INFORMATION_SCHEMA", L: "information_schema"}
142+
InformationSchemaName = model.NewCIStr("INFORMATION_SCHEMA")
143143
// PerformanceSchemaName is the `PERFORMANCE_SCHEMA` database name.
144-
PerformanceSchemaName = model.CIStr{O: "PERFORMANCE_SCHEMA", L: "performance_schema"}
144+
PerformanceSchemaName = model.NewCIStr("PERFORMANCE_SCHEMA")
145145
// MetricSchemaName is the `METRIC_SCHEMA` database name.
146-
MetricSchemaName = model.CIStr{O: "METRIC_SCHEMA", L: "metric_schema"}
146+
MetricSchemaName = model.NewCIStr("METRIC_SCHEMA")
147+
// InspectionSchemaName is the `INSPECTION_SCHEMA` database name
148+
InspectionSchemaName = model.NewCIStr("INSPECTION_SCHEMA")
147149
)
148150

149151
// IsMemOrSysDB uses to check whether dbLowerName is memory database or system database.
150152
func IsMemOrSysDB(dbLowerName string) bool {
151153
switch dbLowerName {
152-
case InformationSchemaName.L, PerformanceSchemaName.L, mysql.SystemDB, MetricSchemaName.L:
154+
case InformationSchemaName.L,
155+
InspectionSchemaName.L,
156+
PerformanceSchemaName.L,
157+
mysql.SystemDB,
158+
MetricSchemaName.L:
153159
return true
154160
}
155161
return false

0 commit comments

Comments
 (0)