From acdd6d792d9676a30df4d2e670dcaa51fcc59c25 Mon Sep 17 00:00:00 2001 From: yisaer Date: Tue, 1 Nov 2022 12:48:30 +0800 Subject: [PATCH 01/20] refactor Signed-off-by: yisaer --- executor/plan_replayer.go | 120 ++++++++++++++++++++++++++------------ 1 file changed, 82 insertions(+), 38 deletions(-) diff --git a/executor/plan_replayer.go b/executor/plan_replayer.go index d4dd78294a015..fb48c62c2aca2 100644 --- a/executor/plan_replayer.go +++ b/executor/plan_replayer.go @@ -154,7 +154,7 @@ func (e *PlanReplayerExec) Next(ctx context.Context, req *chunk.Chunk) error { if e.endFlag { return nil } - err := e.createFile(domain.GetPlanReplayerDirName()) + err := e.createFile() if err != nil { return err } @@ -180,33 +180,76 @@ func (e *PlanReplayerExec) Next(ctx context.Context, req *chunk.Chunk) error { return nil } -func (e *PlanReplayerExec) createFile(path string) error { - // Create path +func (e *PlanReplayerExec) createFile() error { + var err error + e.DumpInfo.File, e.DumpInfo.FileName, err = GeneratePlanReplayerFile() + if err != nil { + return err + } + return nil +} + +// GeneratePlanReplayerFile generates plan replayer file +func GeneratePlanReplayerFile() (*os.File, string, error) { + path := domain.GetPlanReplayerDirName() err := os.MkdirAll(path, os.ModePerm) if err != nil { - return errors.AddStack(err) + return nil, "", errors.AddStack(err) + } + fileName, err := generatePlanReplayerFileName() + if err != nil { + return nil, "", errors.AddStack(err) } + zf, err := os.Create(filepath.Join(path, fileName)) + if err != nil { + return nil, "", errors.AddStack(err) + } + return zf, fileName, err +} +func generatePlanReplayerFileName() (string, error) { // Generate key and create zip file time := time.Now().UnixNano() b := make([]byte, 16) //nolint: gosec - _, err = rand.Read(b) + _, err := rand.Read(b) if err != nil { - return err + return "", err } key := base64.URLEncoding.EncodeToString(b) - fileName := fmt.Sprintf("replayer_%v_%v.zip", key, time) - zf, err := os.Create(filepath.Join(path, fileName)) + return fmt.Sprintf("replayer_%v_%v.zip", key, time), nil +} + +func (e *PlanReplayerDumpInfo) dump(ctx context.Context) (err error) { + fileName := e.FileName + zf := e.File + task := &PlanReplayerDumpTask{ + FileName: fileName, + Zf: zf, + SessionVars: e.ctx.GetSessionVars(), + TblStats: nil, + ExecStmts: e.ExecStmts, + Analyze: e.Analyze, + } + err = DumpPlanReplayerInfo(ctx, e.ctx, task) if err != nil { - return errors.AddStack(err) + return err } - e.DumpInfo.File = zf - e.DumpInfo.FileName = fileName + e.ctx.GetSessionVars().LastPlanReplayerToken = e.FileName return nil } -// dumpSingle will dump the information about sqls. +// PlanReplayerDumpTask wrap the params for plan replayer dump +type PlanReplayerDumpTask struct { + FileName string + Zf *os.File + SessionVars *variable.SessionVars + TblStats map[int64]*handle.JSONTable + ExecStmts []ast.StmtNode + Analyze bool +} + +// DumpPlanReplayerInfo will dump the information about sqls. // The files will be organized into the following format: /* |-meta.txt @@ -235,10 +278,12 @@ func (e *PlanReplayerExec) createFile(path string) error { |-explain2.txt |-.... */ -func (e *PlanReplayerDumpInfo) dump(ctx context.Context) (err error) { - fileName := e.FileName - zf := e.File - // Create zip writer +func DumpPlanReplayerInfo(ctx context.Context, sctx sessionctx.Context, + task *PlanReplayerDumpTask) (err error) { + zf := task.Zf + fileName := task.FileName + sessionVars := task.SessionVars + execStmts := task.ExecStmts zw := zip.NewWriter(zf) defer func() { err = zw.Close() @@ -250,7 +295,6 @@ func (e *PlanReplayerDumpInfo) dump(ctx context.Context) (err error) { logutil.BgLogger().Error("Closing zip file failed", zap.Error(err), zap.String("filename", fileName)) } }() - // Dump config if err = dumpConfig(zw); err != nil { return err @@ -260,59 +304,55 @@ func (e *PlanReplayerDumpInfo) dump(ctx context.Context) (err error) { if err = dumpMeta(zw); err != nil { return err } - // Retrieve current DB - sessionVars := e.ctx.GetSessionVars() dbName := model.NewCIStr(sessionVars.CurrentDB) - do := domain.GetDomain(e.ctx) + do := domain.GetDomain(sctx) // Retrieve all tables - pairs, err := e.extractTableNames(ctx, e.ExecStmts, dbName) + pairs, err := extractTableNames(ctx, sctx, execStmts, dbName) if err != nil { return errors.AddStack(fmt.Errorf("plan replayer: invalid SQL text, err: %v", err)) } // Dump Schema and View - if err = dumpSchemas(e.ctx, zw, pairs); err != nil { + if err = dumpSchemas(sctx, zw, pairs); err != nil { return err } // Dump tables tiflash replicas - if err = dumpTiFlashReplica(e.ctx, zw, pairs); err != nil { + if err = dumpTiFlashReplica(sctx, zw, pairs); err != nil { return err } // Dump stats - if err = dumpStats(zw, pairs, do); err != nil { + if err = dumpStats(zw, pairs, task.TblStats, do); err != nil { return err } // Dump variables - if err = dumpVariables(e.ctx, zw); err != nil { + if err = dumpVariables(sctx, zw); err != nil { return err } // Dump sql - if err = dumpSQLs(e.ExecStmts, zw); err != nil { + if err = dumpSQLs(execStmts, zw); err != nil { return err } // Dump session bindings - if err = dumpSessionBindings(e.ctx, zw); err != nil { + if err = dumpSessionBindings(sctx, zw); err != nil { return err } // Dump global bindings - if err = dumpGlobalBindings(e.ctx, zw); err != nil { + if err = dumpGlobalBindings(sctx, zw); err != nil { return err } // Dump explain - if err = dumpExplain(e.ctx, zw, e.ExecStmts, e.Analyze); err != nil { + if err = dumpExplain(sctx, zw, execStmts, task.Analyze); err != nil { return err } - - e.ctx.GetSessionVars().LastPlanReplayerToken = e.FileName return nil } @@ -374,12 +414,12 @@ func dumpSchemas(ctx sessionctx.Context, zw *zip.Writer, pairs map[tableNamePair return nil } -func dumpStats(zw *zip.Writer, pairs map[tableNamePair]struct{}, do *domain.Domain) error { +func dumpStats(zw *zip.Writer, pairs map[tableNamePair]struct{}, tblJsonStats map[int64]*handle.JSONTable, do *domain.Domain) error { for pair := range pairs { if pair.IsView { continue } - jsonTbl, err := getStatsForTable(do, pair) + jsonTbl, err := getStatsForTable(do, tblJsonStats, pair) if err != nil { return err } @@ -527,12 +567,12 @@ func dumpExplain(ctx sessionctx.Context, zw *zip.Writer, execStmts []ast.StmtNod return nil } -func (e *PlanReplayerDumpInfo) extractTableNames(ctx context.Context, +func extractTableNames(ctx context.Context, sctx sessionctx.Context, ExecStmts []ast.StmtNode, curDB model.CIStr) (map[tableNamePair]struct{}, error) { tableExtractor := &tableNameExtractor{ ctx: ctx, - executor: e.ctx.(sqlexec.RestrictedSQLExecutor), - is: domain.GetDomain(e.ctx).InfoSchema(), + executor: sctx.(sqlexec.RestrictedSQLExecutor), + is: domain.GetDomain(sctx).InfoSchema(), curDB: curDB, names: make(map[tableNamePair]struct{}), cteNames: make(map[string]struct{}), @@ -586,14 +626,18 @@ func (e *PlanReplayerDumpInfo) DumpSQLsFromFile(ctx context.Context, b []byte) e return e.dump(ctx) } -func getStatsForTable(do *domain.Domain, pair tableNamePair) (*handle.JSONTable, error) { +func getStatsForTable(do *domain.Domain, tblJsonStats map[int64]*handle.JSONTable, pair tableNamePair) (*handle.JSONTable, error) { is := do.InfoSchema() h := do.StatsHandle() tbl, err := is.TableByName(model.NewCIStr(pair.DBName), model.NewCIStr(pair.TableName)) if err != nil { return nil, err } - js, err := h.DumpStatsToJSON(pair.DBName, tbl.Meta(), nil, true) + js, ok := tblJsonStats[tbl.Meta().ID] + if ok && js != nil { + return js, nil + } + js, err = h.DumpStatsToJSON(pair.DBName, tbl.Meta(), nil, true) return js, err } From 74026c4ce5ac85523ac39ad32cd02122727d2b97 Mon Sep 17 00:00:00 2001 From: yisaer Date: Tue, 1 Nov 2022 16:19:48 +0800 Subject: [PATCH 02/20] fix lint Signed-off-by: yisaer --- executor/plan_replayer.go | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/executor/plan_replayer.go b/executor/plan_replayer.go index fb48c62c2aca2..be10ca8539cb5 100644 --- a/executor/plan_replayer.go +++ b/executor/plan_replayer.go @@ -350,10 +350,7 @@ func DumpPlanReplayerInfo(ctx context.Context, sctx sessionctx.Context, } // Dump explain - if err = dumpExplain(sctx, zw, execStmts, task.Analyze); err != nil { - return err - } - return nil + return dumpExplain(sctx, zw, execStmts, task.Analyze) } func dumpConfig(zw *zip.Writer) error { @@ -414,12 +411,12 @@ func dumpSchemas(ctx sessionctx.Context, zw *zip.Writer, pairs map[tableNamePair return nil } -func dumpStats(zw *zip.Writer, pairs map[tableNamePair]struct{}, tblJsonStats map[int64]*handle.JSONTable, do *domain.Domain) error { +func dumpStats(zw *zip.Writer, pairs map[tableNamePair]struct{}, tblJSONStats map[int64]*handle.JSONTable, do *domain.Domain) error { for pair := range pairs { if pair.IsView { continue } - jsonTbl, err := getStatsForTable(do, tblJsonStats, pair) + jsonTbl, err := getStatsForTable(do, tblJSONStats, pair) if err != nil { return err } @@ -626,14 +623,14 @@ func (e *PlanReplayerDumpInfo) DumpSQLsFromFile(ctx context.Context, b []byte) e return e.dump(ctx) } -func getStatsForTable(do *domain.Domain, tblJsonStats map[int64]*handle.JSONTable, pair tableNamePair) (*handle.JSONTable, error) { +func getStatsForTable(do *domain.Domain, tblJSONStats map[int64]*handle.JSONTable, pair tableNamePair) (*handle.JSONTable, error) { is := do.InfoSchema() h := do.StatsHandle() tbl, err := is.TableByName(model.NewCIStr(pair.DBName), model.NewCIStr(pair.TableName)) if err != nil { return nil, err } - js, ok := tblJsonStats[tbl.Meta().ID] + js, ok := tblJSONStats[tbl.Meta().ID] if ok && js != nil { return js, nil } From 77d9af54b78a13c28184bddb23bfed85aeb36285 Mon Sep 17 00:00:00 2001 From: yisaer Date: Thu, 3 Nov 2022 14:29:34 +0800 Subject: [PATCH 03/20] support Signed-off-by: yisaer --- domain/domain.go | 5 ++ domain/plan_replayer.go | 101 +++++++++++++++++++++++++++++++++++++++- session/bootstrap.go | 34 +++++++++++++- session/session.go | 20 ++++++-- 4 files changed, 154 insertions(+), 6 deletions(-) diff --git a/domain/domain.go b/domain/domain.go index 0076081e71e5c..761d761363323 100644 --- a/domain/domain.go +++ b/domain/domain.go @@ -1530,6 +1530,11 @@ func (do *Domain) TelemetryRotateSubWindowLoop(ctx sessionctx.Context) { }() } +// SetupDumpFileGcCheckerSCTX setup sctx for dumpFileGcChecker +func (do *Domain) SetupDumpFileGcCheckerSCTX(ctx sessionctx.Context) { + do.dumpFileGcChecker.setupSctx(ctx) +} + // DumpFileGcCheckerLoop creates a goroutine that handles `exit` and `gc`. func (do *Domain) DumpFileGcCheckerLoop() { do.wg.Add(1) diff --git a/domain/plan_replayer.go b/domain/plan_replayer.go index b207f904a6608..7a4183741213a 100644 --- a/domain/plan_replayer.go +++ b/domain/plan_replayer.go @@ -15,7 +15,8 @@ package domain import ( - "errors" + "context" + "fmt" "io/ioutil" "os" "path/filepath" @@ -24,7 +25,13 @@ import ( "sync" "time" + "github.com/pingcap/errors" + "github.com/pingcap/tidb/domain/infosync" + "github.com/pingcap/tidb/parser/terror" + "github.com/pingcap/tidb/sessionctx" + "github.com/pingcap/tidb/util/chunk" "github.com/pingcap/tidb/util/logutil" + "github.com/pingcap/tidb/util/sqlexec" "go.uber.org/zap" ) @@ -34,6 +41,7 @@ type dumpFileGcChecker struct { sync.Mutex gcLease time.Duration paths []string + ctx sessionctx.Context } // GetPlanReplayerDirName returns plan replayer directory path. @@ -42,6 +50,10 @@ func GetPlanReplayerDirName() string { return filepath.Join(os.TempDir(), "replayer", strconv.Itoa(os.Getpid())) } +func parseType(s string) string { + return strings.Split(s, "_")[0] +} + func parseTime(s string) (time.Time, error) { startIdx := strings.LastIndex(s, "_") if startIdx == -1 { @@ -58,6 +70,10 @@ func parseTime(s string) (time.Time, error) { return time.Unix(0, i), nil } +func (p *dumpFileGcChecker) setupSctx(ctx sessionctx.Context) { + p.ctx = ctx +} + func (p *dumpFileGcChecker) gcDumpFiles(t time.Duration) { p.Lock() defer p.Unlock() @@ -82,6 +98,7 @@ func (p *dumpFileGcChecker) gcDumpFilesByPath(path string, t time.Duration) { logutil.BgLogger().Error("[dumpFileGcChecker] parseTime failed", zap.Error(err), zap.String("filename", fileName)) continue } + isPlanReplayer := parseType(fileName) == "replayer" if !createTime.After(gcTime) { err := os.Remove(filepath.Join(path, f.Name())) if err != nil { @@ -89,6 +106,88 @@ func (p *dumpFileGcChecker) gcDumpFilesByPath(path string, t time.Duration) { continue } logutil.BgLogger().Info("dumpFileGcChecker successful", zap.String("filename", fileName)) + if isPlanReplayer { + DeletePlanReplayerStatus(context.Background(), p.ctx, fileName) + } } } } + +// DeletePlanReplayerStatus delete mysql.plan_replayer_status record +func DeletePlanReplayerStatus(ctx context.Context, sctx sessionctx.Context, token string) { + exec := sctx.(sqlexec.SQLExecutor) + r, err := exec.ExecuteInternal(ctx, fmt.Sprintf("delete from mysql.plan_replayer_status where token = %v", token)) + if err != nil { + logutil.BgLogger().Warn("delete mysql.plan_replayer_status record failed", zap.String("token", token), zap.Error(err)) + } + r.Close() +} + +// InsertPlanReplayerStatus insert mysql.plan_replayer_status record +func InsertPlanReplayerStatus(ctx context.Context, sctx sessionctx.Context, sqlDigest, planDigest, token string, err error) { + var instance string + serverInfo, err := infosync.GetServerInfo() + if err != nil { + logutil.BgLogger().Error("failed to get server info", zap.Error(err)) + instance = "unknown" + } else { + instance = fmt.Sprintf("%s:%d", serverInfo.IP, serverInfo.Port) + } + exec := sctx.(sqlexec.SQLExecutor) + if err != nil { + _, err := exec.ExecuteInternal(ctx, fmt.Sprintf( + "insert into mysql.plan_replayer_Status (sql_digest, plan_digest, fail_reason, instance) values (%s,%s,%s,%s)", + sqlDigest, planDigest, err.Error(), instance)) + if err != nil { + logutil.BgLogger().Error("insert mysql.plan_replayer_status record failed", + zap.String("token", token), + zap.Error(err)) + } + } else { + _, err := exec.ExecuteInternal(ctx, fmt.Sprintf( + "insert into mysql.plan_replayer_Status (sql_digest, plan_digest, token, instance) values (%s,%s,%s,%s)", + sqlDigest, planDigest, token, instance)) + if err != nil { + logutil.BgLogger().Error("insert mysql.plan_replayer_status record failed", + zap.String("token", token), + zap.Error(err)) + } + } +} + +func GetAllPlanReplayerTask(ctx context.Context, sctx sessionctx.Context) ([]PlanReplayerKey, error) { + exec := sctx.(sqlexec.SQLExecutor) + rs, err := exec.ExecuteInternal(ctx, "select sql_digest, plan_digest from mysql.plan_replayer_task") + if err != nil { + return nil, err + } + if rs == nil { + return nil, nil + } + var rows []chunk.Row + defer terror.Call(rs.Close) + if rows, err = sqlexec.DrainRecordSet(ctx, rs, 8); err != nil { + return nil, errors.Trace(err) + } + keys := make([]PlanReplayerKey, 0, len(rows)) + for _, row := range rows { + sqlDigest, planDigest := row.GetString(0), row.GetString(1) + keys = append(keys, PlanReplayerKey{ + sqlDigest: sqlDigest, + planDigest: planDigest, + }) + } + return keys, nil +} + +type PlanReplayerKey struct { + sqlDigest string + planDigest string +} + +type PlanReplayerHandle struct { + mu struct { + sync.RWMutex + planReplayerCapture map[PlanReplayerKey]struct{} + } +} diff --git a/session/bootstrap.go b/session/bootstrap.go index c449087a2689c..cd84fb93a1369 100644 --- a/session/bootstrap.go +++ b/session/bootstrap.go @@ -430,6 +430,23 @@ const ( CreateMDLView = `CREATE OR REPLACE VIEW mysql.tidb_mdl_view as ( select JOB_ID, DB_NAME, TABLE_NAME, QUERY, SESSION_ID, TxnStart, TIDB_DECODE_SQL_DIGESTS(ALL_SQL_DIGESTS, 4096) AS SQL_DIGESTS from information_schema.ddl_jobs, information_schema.CLUSTER_TIDB_TRX, information_schema.CLUSTER_PROCESSLIST where ddl_jobs.STATE = 'running' and find_in_set(ddl_jobs.table_id, CLUSTER_TIDB_TRX.RELATED_TABLE_IDS) and CLUSTER_TIDB_TRX.SESSION_ID=CLUSTER_PROCESSLIST.ID );` + + CreatePlanReplayerTaskTable = `CREATE TABLE IF NOT EXISTS mysql.plan_replayer_task ( + sql_digest VARCHAR(128) NOT NULL, + plan_digest VARCHAR(128) NOT NULL, + update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (sql_digest,plan_digest));` + + // CreatePlanReplayerStatusTable is a table about plan replayer status + CreatePlanReplayerStatusTable = `CREATE TABLE IF NOT EXISTS mysql.plan_replayer_status ( + id BIGINT(64) UNSIGNED NOT NULL AUTO_INCREMENT, + sql_digest VARCHAR(128) NOT NULL, + plan_digest VARCHAR(128) NOT NULL, + token VARCHAR(128), + update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + fail_reason TEXT, + instance VARCHAR(512) NOT NULL comment 'address of the TiDB instance executing the plan replayer job',, + PRIMARY KEY (id));` ) // bootstrap initiates system DB for a store. @@ -643,11 +660,13 @@ const ( version97 = 97 // version98 add a column `Token_issuer` to `mysql.user` version98 = 98 + // version 99 add a table mysql.plan_replayer_status + version99 = 99 ) // currentBootstrapVersion is defined as a variable, so we can modify its value for testing. // please make sure this is the largest version -var currentBootstrapVersion int64 = version98 +var currentBootstrapVersion int64 = version99 // DDL owner key's expired time is ManagerSessionTTL seconds, we should wait the time and give more time to have a chance to finish it. var internalSQLTimeout = owner.ManagerSessionTTL + 15 @@ -751,6 +770,7 @@ var ( upgradeToVer96, upgradeToVer97, upgradeToVer98, + upgradeToVer99, } ) @@ -1989,6 +2009,14 @@ func upgradeToVer98(s Session, ver int64) { doReentrantDDL(s, "ALTER TABLE mysql.user ADD COLUMN IF NOT EXISTS `Token_issuer` varchar(255)") } +func upgradeToVer99(s Session, ver int64) { + if ver >= version99 { + return + } + doReentrantDDL(s, CreatePlanReplayerTaskTable) + doReentrantDDL(s, CreatePlanReplayerStatusTable) +} + func writeOOMAction(s Session) { comment := "oom-action is `log` by default in v3.0.x, `cancel` by default in v4.0.11+" mustExecute(s, `INSERT HIGH_PRIORITY INTO %n.%n VALUES (%?, %?, %?) ON DUPLICATE KEY UPDATE VARIABLE_VALUE= %?`, @@ -2085,6 +2113,10 @@ func doDDLWorks(s Session) { mustExecute(s, CreateAdvisoryLocks) // Create mdl view. mustExecute(s, CreateMDLView) + // Create plan_replayer_task table + mustExecute(s, CreatePlanReplayerTaskTable) + // Create plan_replayer_status table + mustExecute(s, CreatePlanReplayerStatusTable) } // inTestSuite checks if we are bootstrapping in the context of tests. diff --git a/session/session.go b/session/session.go index f43e265462c11..5bd3993578569 100644 --- a/session/session.go +++ b/session/session.go @@ -2876,7 +2876,7 @@ func BootstrapSession(store kv.Storage) (*domain.Domain, error) { analyzeConcurrencyQuota := int(config.GetGlobalConfig().Performance.AnalyzePartitionConcurrencyQuota) concurrency := int(config.GetGlobalConfig().Performance.StatsLoadConcurrency) - ses, err := createSessions(store, 7+concurrency+analyzeConcurrencyQuota) + ses, err := createSessions(store, 7) if err != nil { return nil, err } @@ -2950,21 +2950,33 @@ func BootstrapSession(store kv.Storage) (*domain.Domain, error) { }() } + // setup dumpFileGcChecker + dom.SetupDumpFileGcCheckerSCTX(ses[6]) + dom.DumpFileGcCheckerLoop() + // A sub context for update table stats, and other contexts for concurrent stats loading. cnt := 1 + concurrency + syncStatsCtxs, err := createSessions(store, cnt) + if err != nil { + return nil, err + } subCtxs := make([]sessionctx.Context, cnt) for i := 0; i < cnt; i++ { - subCtxs[i] = sessionctx.Context(ses[6+i]) + subCtxs[i] = sessionctx.Context(syncStatsCtxs[i]) } if err = dom.LoadAndUpdateStatsLoop(subCtxs); err != nil { return nil, err } + + analyzeCtxs, err := createSessions(store, analyzeConcurrencyQuota) + if err != nil { + return nil, err + } subCtxs2 := make([]sessionctx.Context, analyzeConcurrencyQuota) for i := 0; i < analyzeConcurrencyQuota; i++ { - subCtxs2[i] = ses[7+concurrency+i] + subCtxs2[i] = analyzeCtxs[i] } dom.SetupAnalyzeExec(subCtxs2) - dom.DumpFileGcCheckerLoop() dom.LoadSigningCertLoop(cfg.Security.SessionTokenSigningCert, cfg.Security.SessionTokenSigningKey) if raw, ok := store.(kv.EtcdBackend); ok { From 9d1bc4a29c2bcbe74e184f4ef236b12035a82a06 Mon Sep 17 00:00:00 2001 From: yisaer Date: Tue, 8 Nov 2022 12:24:45 +0800 Subject: [PATCH 04/20] revise Signed-off-by: yisaer --- domain/plan_replayer.go | 78 +++++++++++++++------------------------ executor/plan_replayer.go | 22 +++++++++++ session/bootstrap.go | 17 ++------- 3 files changed, 55 insertions(+), 62 deletions(-) diff --git a/domain/plan_replayer.go b/domain/plan_replayer.go index 26f0b6b67d44d..a6b8afa753087 100644 --- a/domain/plan_replayer.go +++ b/domain/plan_replayer.go @@ -28,9 +28,7 @@ import ( "github.com/pingcap/errors" "github.com/pingcap/tidb/config" "github.com/pingcap/tidb/domain/infosync" - "github.com/pingcap/tidb/parser/terror" "github.com/pingcap/tidb/sessionctx" - "github.com/pingcap/tidb/util/chunk" "github.com/pingcap/tidb/util/logutil" "github.com/pingcap/tidb/util/sqlexec" "go.uber.org/zap" @@ -126,7 +124,7 @@ func DeletePlanReplayerStatus(ctx context.Context, sctx sessionctx.Context, toke } // InsertPlanReplayerStatus insert mysql.plan_replayer_status record -func InsertPlanReplayerStatus(ctx context.Context, sctx sessionctx.Context, sqlDigest, planDigest, token string, err error) { +func InsertPlanReplayerStatus(ctx context.Context, sctx sessionctx.Context, records []PlanReplayerStatusRecord) { var instance string serverInfo, err := infosync.GetServerInfo() if err != nil { @@ -135,61 +133,43 @@ func InsertPlanReplayerStatus(ctx context.Context, sctx sessionctx.Context, sqlD } else { instance = fmt.Sprintf("%s:%d", serverInfo.IP, serverInfo.Port) } - exec := sctx.(sqlexec.SQLExecutor) - if err != nil { - _, err := exec.ExecuteInternal(ctx, fmt.Sprintf( - "insert into mysql.plan_replayer_Status (sql_digest, plan_digest, fail_reason, instance) values (%s,%s,%s,%s)", - sqlDigest, planDigest, err.Error(), instance)) - if err != nil { - logutil.BgLogger().Error("insert mysql.plan_replayer_status record failed", - zap.String("token", token), - zap.Error(err)) - } - } else { - _, err := exec.ExecuteInternal(ctx, fmt.Sprintf( - "insert into mysql.plan_replayer_Status (sql_digest, plan_digest, token, instance) values (%s,%s,%s,%s)", - sqlDigest, planDigest, token, instance)) - if err != nil { - logutil.BgLogger().Error("insert mysql.plan_replayer_status record failed", - zap.String("token", token), - zap.Error(err)) + for _, record := range records { + if !record.Internal { + if len(record.FailedReason) > 0 { + insertExternalPlanReplayerErrorStatusRecord(ctx, sctx, instance, record) + } else { + insertExternalPlanReplayerSuccessStatusRecord(ctx, sctx, instance, record) + } } } } -func GetAllPlanReplayerTask(ctx context.Context, sctx sessionctx.Context) ([]PlanReplayerKey, error) { +func insertExternalPlanReplayerErrorStatusRecord(ctx context.Context, sctx sessionctx.Context, instance string, record PlanReplayerStatusRecord) { exec := sctx.(sqlexec.SQLExecutor) - rs, err := exec.ExecuteInternal(ctx, "select sql_digest, plan_digest from mysql.plan_replayer_task") + _, err := exec.ExecuteInternal(ctx, fmt.Sprintf( + "insert into mysql.plan_replayer_Status (origin_sql, fail_reason, instance) values (%s,%s,%s)", + record.OriginSql, record.FailedReason, instance)) if err != nil { - return nil, err + logutil.BgLogger().Warn("insert mysql.plan_replayer_status record failed", + zap.Error(err)) } - if rs == nil { - return nil, nil - } - var rows []chunk.Row - defer terror.Call(rs.Close) - if rows, err = sqlexec.DrainRecordSet(ctx, rs, 8); err != nil { - return nil, errors.Trace(err) - } - keys := make([]PlanReplayerKey, 0, len(rows)) - for _, row := range rows { - sqlDigest, planDigest := row.GetString(0), row.GetString(1) - keys = append(keys, PlanReplayerKey{ - sqlDigest: sqlDigest, - planDigest: planDigest, - }) - } - return keys, nil } -type PlanReplayerKey struct { - sqlDigest string - planDigest string +func insertExternalPlanReplayerSuccessStatusRecord(ctx context.Context, sctx sessionctx.Context, instance string, record PlanReplayerStatusRecord) { + exec := sctx.(sqlexec.SQLExecutor) + _, err := exec.ExecuteInternal(ctx, fmt.Sprintf( + "insert into mysql.plan_replayer_Status (origin_sql, token, instance) values (%s,%s,%s)", + record.OriginSql, record.Token, instance)) + if err != nil { + logutil.BgLogger().Warn("insert mysql.plan_replayer_status record failed", + zap.Error(err)) + } } -type PlanReplayerHandle struct { - mu struct { - sync.RWMutex - planReplayerCapture map[PlanReplayerKey]struct{} - } +// PlanReplayerStatusRecord indicates record in mysql.plan_replayer_status +type PlanReplayerStatusRecord struct { + Internal bool + OriginSql string + Token string + FailedReason string } diff --git a/executor/plan_replayer.go b/executor/plan_replayer.go index 1fcc1d85ff24a..b05a5d7326d57 100644 --- a/executor/plan_replayer.go +++ b/executor/plan_replayer.go @@ -290,6 +290,7 @@ func DumpPlanReplayerInfo(ctx context.Context, sctx sessionctx.Context, sessionVars := task.SessionVars execStmts := task.ExecStmts zw := zip.NewWriter(zf) + records := generateRecords(task) defer func() { err = zw.Close() if err != nil { @@ -299,6 +300,13 @@ func DumpPlanReplayerInfo(ctx context.Context, sctx sessionctx.Context, if err != nil { logutil.BgLogger().Error("Closing zip file failed", zap.Error(err), zap.String("filename", fileName)) } + if err != nil { + for i, record := range records { + record.FailedReason = err.Error() + records[i] = record + } + } + domain.InsertPlanReplayerStatus(ctx, sctx, records) }() // Dump config if err = dumpConfig(zw); err != nil { @@ -367,6 +375,20 @@ func DumpPlanReplayerInfo(ctx context.Context, sctx sessionctx.Context, return dumpExplain(sctx, zw, execStmts, task.Analyze) } +func generateRecords(task *PlanReplayerDumpTask) []domain.PlanReplayerStatusRecord { + records := make([]domain.PlanReplayerStatusRecord, 0) + if len(task.ExecStmts) > 0 { + for _, execStmt := range task.ExecStmts { + records = append(records, domain.PlanReplayerStatusRecord{ + OriginSql: execStmt.Text(), + Token: task.FileName, + Internal: true, + }) + } + } + return records +} + func dumpConfig(zw *zip.Writer) error { cf, err := zw.Create(configFile) if err != nil { diff --git a/session/bootstrap.go b/session/bootstrap.go index 1444abbe0a9a2..cb4572af030fb 100644 --- a/session/bootstrap.go +++ b/session/bootstrap.go @@ -431,21 +431,15 @@ const ( select JOB_ID, DB_NAME, TABLE_NAME, QUERY, SESSION_ID, TxnStart, TIDB_DECODE_SQL_DIGESTS(ALL_SQL_DIGESTS, 4096) AS SQL_DIGESTS from information_schema.ddl_jobs, information_schema.CLUSTER_TIDB_TRX, information_schema.CLUSTER_PROCESSLIST where ddl_jobs.STATE = 'running' and find_in_set(ddl_jobs.table_id, CLUSTER_TIDB_TRX.RELATED_TABLE_IDS) and CLUSTER_TIDB_TRX.SESSION_ID=CLUSTER_PROCESSLIST.ID );` - CreatePlanReplayerTaskTable = `CREATE TABLE IF NOT EXISTS mysql.plan_replayer_task ( - sql_digest VARCHAR(128) NOT NULL, - plan_digest VARCHAR(128) NOT NULL, - update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - PRIMARY KEY (sql_digest,plan_digest));` - // CreatePlanReplayerStatusTable is a table about plan replayer status CreatePlanReplayerStatusTable = `CREATE TABLE IF NOT EXISTS mysql.plan_replayer_status ( - id BIGINT(64) UNSIGNED NOT NULL AUTO_INCREMENT, - sql_digest VARCHAR(128) NOT NULL, - plan_digest VARCHAR(128) NOT NULL, + sql_digest VARCHAR(128) + plan_digest VARCHAR(128) + origin_sql TEXT, token VARCHAR(128), update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, fail_reason TEXT, - instance VARCHAR(512) NOT NULL comment 'address of the TiDB instance executing the plan replayer job',, + instance VARCHAR(512) NOT NULL comment 'address of the TiDB instance executing the plan replayer job', PRIMARY KEY (id));` ) @@ -2011,7 +2005,6 @@ func upgradeToVer101(s Session, ver int64) { if ver >= version101 { return } - doReentrantDDL(s, CreatePlanReplayerTaskTable) doReentrantDDL(s, CreatePlanReplayerStatusTable) } @@ -2150,8 +2143,6 @@ func doDDLWorks(s Session) { mustExecute(s, CreateAdvisoryLocks) // Create mdl view. mustExecute(s, CreateMDLView) - // Create plan_replayer_task table - mustExecute(s, CreatePlanReplayerTaskTable) // Create plan_replayer_status table mustExecute(s, CreatePlanReplayerStatusTable) } From 2a3c4599190ec6ca8549c17716bae4ac4667025f Mon Sep 17 00:00:00 2001 From: yisaer Date: Tue, 8 Nov 2022 15:31:50 +0800 Subject: [PATCH 05/20] add test Signed-off-by: yisaer --- domain/domain.go | 15 ++++++++--- domain/plan_replayer.go | 55 ++++++++++++++++++++++++--------------- executor/executor_test.go | 7 +++++ executor/plan_replayer.go | 4 +-- session/bootstrap.go | 7 +++-- session/session.go | 2 +- 6 files changed, 59 insertions(+), 31 deletions(-) diff --git a/domain/domain.go b/domain/domain.go index 761d761363323..cd256c581ab7b 100644 --- a/domain/domain.go +++ b/domain/domain.go @@ -114,6 +114,7 @@ type Domain struct { cancel context.CancelFunc indexUsageSyncLease time.Duration dumpFileGcChecker *dumpFileGcChecker + planReplayerHandle *planReplayerHandle expiredTimeStamp4PC types.Time logBackupAdvancer *daemon.OwnerDaemon @@ -1530,9 +1531,17 @@ func (do *Domain) TelemetryRotateSubWindowLoop(ctx sessionctx.Context) { }() } -// SetupDumpFileGcCheckerSCTX setup sctx for dumpFileGcChecker -func (do *Domain) SetupDumpFileGcCheckerSCTX(ctx sessionctx.Context) { - do.dumpFileGcChecker.setupSctx(ctx) +// SetupPlanReplayerHandle setup plan replayer handle +func (do *Domain) SetupPlanReplayerHandle(ctx sessionctx.Context) { + do.planReplayerHandle = &planReplayerHandle{ + sctx: ctx, + } + do.dumpFileGcChecker.setupPlanReplayerHandle(do.planReplayerHandle) +} + +// GetPlanReplayerHandle returns plan replayer handle +func (do *Domain) GetPlanReplayerHandle() *planReplayerHandle { + return do.planReplayerHandle } // DumpFileGcCheckerLoop creates a goroutine that handles `exit` and `gc`. diff --git a/domain/plan_replayer.go b/domain/plan_replayer.go index a6b8afa753087..2fbbf1e52d2e8 100644 --- a/domain/plan_replayer.go +++ b/domain/plan_replayer.go @@ -28,6 +28,7 @@ import ( "github.com/pingcap/errors" "github.com/pingcap/tidb/config" "github.com/pingcap/tidb/domain/infosync" + "github.com/pingcap/tidb/kv" "github.com/pingcap/tidb/sessionctx" "github.com/pingcap/tidb/util/logutil" "github.com/pingcap/tidb/util/sqlexec" @@ -38,9 +39,9 @@ import ( // For now it is used by `plan replayer` and `trace plan` statement type dumpFileGcChecker struct { sync.Mutex - gcLease time.Duration - paths []string - ctx sessionctx.Context + gcLease time.Duration + paths []string + planReplayerHandle *planReplayerHandle } // GetPlanReplayerDirName returns plan replayer directory path. @@ -70,10 +71,6 @@ func parseTime(s string) (time.Time, error) { return time.Unix(0, i), nil } -func (p *dumpFileGcChecker) setupSctx(ctx sessionctx.Context) { - p.ctx = ctx -} - func (p *dumpFileGcChecker) gcDumpFiles(t time.Duration) { p.Lock() defer p.Unlock() @@ -82,6 +79,10 @@ func (p *dumpFileGcChecker) gcDumpFiles(t time.Duration) { } } +func (p *dumpFileGcChecker) setupPlanReplayerHandle(handle *planReplayerHandle) { + p.planReplayerHandle = handle +} + func (p *dumpFileGcChecker) gcDumpFilesByPath(path string, t time.Duration) { files, err := ioutil.ReadDir(path) if err != nil { @@ -107,24 +108,32 @@ func (p *dumpFileGcChecker) gcDumpFilesByPath(path string, t time.Duration) { } logutil.BgLogger().Info("dumpFileGcChecker successful", zap.String("filename", fileName)) if isPlanReplayer { - DeletePlanReplayerStatus(context.Background(), p.ctx, fileName) + p.planReplayerHandle.deletePlanReplayerStatus(context.Background(), fileName) } } } } +type planReplayerHandle struct { + sync.Mutex + sctx sessionctx.Context +} + // DeletePlanReplayerStatus delete mysql.plan_replayer_status record -func DeletePlanReplayerStatus(ctx context.Context, sctx sessionctx.Context, token string) { - exec := sctx.(sqlexec.SQLExecutor) - r, err := exec.ExecuteInternal(ctx, fmt.Sprintf("delete from mysql.plan_replayer_status where token = %v", token)) +func (h *planReplayerHandle) deletePlanReplayerStatus(ctx context.Context, token string) { + ctx1 := kv.WithInternalSourceType(ctx, kv.InternalTxnStats) + h.Lock() + defer h.Unlock() + exec := h.sctx.(sqlexec.SQLExecutor) + _, err := exec.ExecuteInternal(ctx1, fmt.Sprintf("delete from mysql.plan_replayer_status where token = %v", token)) if err != nil { logutil.BgLogger().Warn("delete mysql.plan_replayer_status record failed", zap.String("token", token), zap.Error(err)) } - r.Close() } // InsertPlanReplayerStatus insert mysql.plan_replayer_status record -func InsertPlanReplayerStatus(ctx context.Context, sctx sessionctx.Context, records []PlanReplayerStatusRecord) { +func (h *planReplayerHandle) InsertPlanReplayerStatus(ctx context.Context, records []PlanReplayerStatusRecord) { + ctx1 := kv.WithInternalSourceType(ctx, kv.InternalTxnStats) var instance string serverInfo, err := infosync.GetServerInfo() if err != nil { @@ -136,18 +145,20 @@ func InsertPlanReplayerStatus(ctx context.Context, sctx sessionctx.Context, reco for _, record := range records { if !record.Internal { if len(record.FailedReason) > 0 { - insertExternalPlanReplayerErrorStatusRecord(ctx, sctx, instance, record) + h.insertExternalPlanReplayerErrorStatusRecord(ctx1, instance, record) } else { - insertExternalPlanReplayerSuccessStatusRecord(ctx, sctx, instance, record) + h.insertExternalPlanReplayerSuccessStatusRecord(ctx1, instance, record) } } } } -func insertExternalPlanReplayerErrorStatusRecord(ctx context.Context, sctx sessionctx.Context, instance string, record PlanReplayerStatusRecord) { - exec := sctx.(sqlexec.SQLExecutor) +func (h *planReplayerHandle) insertExternalPlanReplayerErrorStatusRecord(ctx context.Context, instance string, record PlanReplayerStatusRecord) { + h.Lock() + defer h.Unlock() + exec := h.sctx.(sqlexec.SQLExecutor) _, err := exec.ExecuteInternal(ctx, fmt.Sprintf( - "insert into mysql.plan_replayer_Status (origin_sql, fail_reason, instance) values (%s,%s,%s)", + "insert into mysql.plan_replayer_status (origin_sql, fail_reason, instance) values ('%s','%s','%s')", record.OriginSql, record.FailedReason, instance)) if err != nil { logutil.BgLogger().Warn("insert mysql.plan_replayer_status record failed", @@ -155,10 +166,12 @@ func insertExternalPlanReplayerErrorStatusRecord(ctx context.Context, sctx sessi } } -func insertExternalPlanReplayerSuccessStatusRecord(ctx context.Context, sctx sessionctx.Context, instance string, record PlanReplayerStatusRecord) { - exec := sctx.(sqlexec.SQLExecutor) +func (h *planReplayerHandle) insertExternalPlanReplayerSuccessStatusRecord(ctx context.Context, instance string, record PlanReplayerStatusRecord) { + h.Lock() + defer h.Unlock() + exec := h.sctx.(sqlexec.SQLExecutor) _, err := exec.ExecuteInternal(ctx, fmt.Sprintf( - "insert into mysql.plan_replayer_Status (origin_sql, token, instance) values (%s,%s,%s)", + "insert into mysql.plan_replayer_status (origin_sql, token, instance) values ('%s','%s','%s')", record.OriginSql, record.Token, instance)) if err != nil { logutil.BgLogger().Warn("insert mysql.plan_replayer_status record failed", diff --git a/executor/executor_test.go b/executor/executor_test.go index dd03c68fd4d74..7160e74209c6f 100644 --- a/executor/executor_test.go +++ b/executor/executor_test.go @@ -165,6 +165,13 @@ func TestPlanReplayer(t *testing.T) { tk.MustQuery("plan replayer dump explain select * from v1") tk.MustQuery("plan replayer dump explain select * from v2") require.True(t, len(tk.Session().GetSessionVars().LastPlanReplayerToken) > 0) + + // clear the status table and assert + tk.MustExec("delete from mysql.plan_replayer_status") + tk.MustQuery("plan replayer dump explain select * from v2") + token := tk.Session().GetSessionVars().LastPlanReplayerToken + rows := tk.MustQuery(fmt.Sprintf("select * from mysql.plan_replayer_status where token = '%v'", token)).Rows() + require.Len(t, rows, 1) } func TestShow(t *testing.T) { diff --git a/executor/plan_replayer.go b/executor/plan_replayer.go index b05a5d7326d57..fb90bd6574276 100644 --- a/executor/plan_replayer.go +++ b/executor/plan_replayer.go @@ -306,7 +306,7 @@ func DumpPlanReplayerInfo(ctx context.Context, sctx sessionctx.Context, records[i] = record } } - domain.InsertPlanReplayerStatus(ctx, sctx, records) + domain.GetDomain(sctx).GetPlanReplayerHandle().InsertPlanReplayerStatus(ctx, records) }() // Dump config if err = dumpConfig(zw); err != nil { @@ -382,7 +382,7 @@ func generateRecords(task *PlanReplayerDumpTask) []domain.PlanReplayerStatusReco records = append(records, domain.PlanReplayerStatusRecord{ OriginSql: execStmt.Text(), Token: task.FileName, - Internal: true, + Internal: false, }) } } diff --git a/session/bootstrap.go b/session/bootstrap.go index cb4572af030fb..7e647370ca4f3 100644 --- a/session/bootstrap.go +++ b/session/bootstrap.go @@ -433,14 +433,13 @@ const ( // CreatePlanReplayerStatusTable is a table about plan replayer status CreatePlanReplayerStatusTable = `CREATE TABLE IF NOT EXISTS mysql.plan_replayer_status ( - sql_digest VARCHAR(128) - plan_digest VARCHAR(128) + sql_digest VARCHAR(128), + plan_digest VARCHAR(128), origin_sql TEXT, token VARCHAR(128), update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, fail_reason TEXT, - instance VARCHAR(512) NOT NULL comment 'address of the TiDB instance executing the plan replayer job', - PRIMARY KEY (id));` + instance VARCHAR(512) NOT NULL comment 'address of the TiDB instance executing the plan replayer job');` ) // bootstrap initiates system DB for a store. diff --git a/session/session.go b/session/session.go index 3a39cd43c600d..268c1a9f84092 100644 --- a/session/session.go +++ b/session/session.go @@ -2970,7 +2970,7 @@ func BootstrapSession(store kv.Storage) (*domain.Domain, error) { } // setup dumpFileGcChecker - dom.SetupDumpFileGcCheckerSCTX(ses[6]) + dom.SetupPlanReplayerHandle(ses[6]) dom.DumpFileGcCheckerLoop() // A sub context for update table stats, and other contexts for concurrent stats loading. From 386f9ab76ac8b3dfe4b47fc9428423a061b155dd Mon Sep 17 00:00:00 2001 From: yisaer Date: Tue, 8 Nov 2022 16:52:15 +0800 Subject: [PATCH 06/20] add test Signed-off-by: yisaer --- domain/plan_replayer.go | 8 ++++---- executor/infoschema_cluster_table_test.go | 2 +- executor/plan_replayer.go | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/domain/plan_replayer.go b/domain/plan_replayer.go index 2fbbf1e52d2e8..0b13500be3a7b 100644 --- a/domain/plan_replayer.go +++ b/domain/plan_replayer.go @@ -107,7 +107,7 @@ func (p *dumpFileGcChecker) gcDumpFilesByPath(path string, t time.Duration) { continue } logutil.BgLogger().Info("dumpFileGcChecker successful", zap.String("filename", fileName)) - if isPlanReplayer { + if isPlanReplayer && p.planReplayerHandle != nil { p.planReplayerHandle.deletePlanReplayerStatus(context.Background(), fileName) } } @@ -159,7 +159,7 @@ func (h *planReplayerHandle) insertExternalPlanReplayerErrorStatusRecord(ctx con exec := h.sctx.(sqlexec.SQLExecutor) _, err := exec.ExecuteInternal(ctx, fmt.Sprintf( "insert into mysql.plan_replayer_status (origin_sql, fail_reason, instance) values ('%s','%s','%s')", - record.OriginSql, record.FailedReason, instance)) + record.OriginSQL, record.FailedReason, instance)) if err != nil { logutil.BgLogger().Warn("insert mysql.plan_replayer_status record failed", zap.Error(err)) @@ -172,7 +172,7 @@ func (h *planReplayerHandle) insertExternalPlanReplayerSuccessStatusRecord(ctx c exec := h.sctx.(sqlexec.SQLExecutor) _, err := exec.ExecuteInternal(ctx, fmt.Sprintf( "insert into mysql.plan_replayer_status (origin_sql, token, instance) values ('%s','%s','%s')", - record.OriginSql, record.Token, instance)) + record.OriginSQL, record.Token, instance)) if err != nil { logutil.BgLogger().Warn("insert mysql.plan_replayer_status record failed", zap.Error(err)) @@ -182,7 +182,7 @@ func (h *planReplayerHandle) insertExternalPlanReplayerSuccessStatusRecord(ctx c // PlanReplayerStatusRecord indicates record in mysql.plan_replayer_status type PlanReplayerStatusRecord struct { Internal bool - OriginSql string + OriginSQL string Token string FailedReason string } diff --git a/executor/infoschema_cluster_table_test.go b/executor/infoschema_cluster_table_test.go index 38fe23036e313..d2e230c6fd766 100644 --- a/executor/infoschema_cluster_table_test.go +++ b/executor/infoschema_cluster_table_test.go @@ -290,7 +290,7 @@ func TestTableStorageStats(t *testing.T) { "test 2", )) rows := tk.MustQuery("select TABLE_NAME from information_schema.TABLE_STORAGE_STATS where TABLE_SCHEMA = 'mysql';").Rows() - result := 37 + result := 38 require.Len(t, rows, result) // More tests about the privileges. diff --git a/executor/plan_replayer.go b/executor/plan_replayer.go index fb90bd6574276..566e274469bee 100644 --- a/executor/plan_replayer.go +++ b/executor/plan_replayer.go @@ -380,7 +380,7 @@ func generateRecords(task *PlanReplayerDumpTask) []domain.PlanReplayerStatusReco if len(task.ExecStmts) > 0 { for _, execStmt := range task.ExecStmts { records = append(records, domain.PlanReplayerStatusRecord{ - OriginSql: execStmt.Text(), + OriginSQL: execStmt.Text(), Token: task.FileName, Internal: false, }) From 0e8638924f10e73cce6f27da2b5e7fd6deca1849 Mon Sep 17 00:00:00 2001 From: yisaer Date: Wed, 9 Nov 2022 16:18:32 +0800 Subject: [PATCH 07/20] support replayer task Signed-off-by: yisaer --- domain/domain.go | 30 +++++- domain/plan_replayer.go | 144 +++++++++++++++++++++++++--- domain/plan_replayer_handle_test.go | 50 ++++++++++ executor/plan_replayer.go | 18 +--- session/bootstrap.go | 21 +++- session/session.go | 4 +- 6 files changed, 236 insertions(+), 31 deletions(-) create mode 100644 domain/plan_replayer_handle_test.go diff --git a/domain/domain.go b/domain/domain.go index cd256c581ab7b..8001cbdb23421 100644 --- a/domain/domain.go +++ b/domain/domain.go @@ -1533,12 +1533,36 @@ func (do *Domain) TelemetryRotateSubWindowLoop(ctx sessionctx.Context) { // SetupPlanReplayerHandle setup plan replayer handle func (do *Domain) SetupPlanReplayerHandle(ctx sessionctx.Context) { - do.planReplayerHandle = &planReplayerHandle{ - sctx: ctx, - } + do.planReplayerHandle = &planReplayerHandle{} + do.planReplayerHandle.sctxMu.sctx = ctx do.dumpFileGcChecker.setupPlanReplayerHandle(do.planReplayerHandle) } +// StartPlanReplayerHandle start plan replayer handle job +func (do *Domain) StartPlanReplayerHandle() { + do.wg.Add(1) + go func() { + tikcer := time.NewTicker(10 * time.Second) + defer func() { + tikcer.Stop() + do.wg.Done() + logutil.BgLogger().Info("PlanReplayerHandle exited.") + util.Recover(metrics.LabelDomain, "PlanReplayerHandle", nil, false) + }() + for { + select { + case <-do.exit: + return + case <-tikcer.C: + err := do.planReplayerHandle.CollectPlanReplayerTask(context.Background()) + if err != nil { + logutil.BgLogger().Warn("plan replayer handle collect tasks failed", zap.Error(err)) + } + } + } + }() +} + // GetPlanReplayerHandle returns plan replayer handle func (do *Domain) GetPlanReplayerHandle() *planReplayerHandle { return do.planReplayerHandle diff --git a/domain/plan_replayer.go b/domain/plan_replayer.go index 0b13500be3a7b..37f3a4568c880 100644 --- a/domain/plan_replayer.go +++ b/domain/plan_replayer.go @@ -26,10 +26,16 @@ import ( "time" "github.com/pingcap/errors" + "github.com/pingcap/tidb/bindinfo" "github.com/pingcap/tidb/config" "github.com/pingcap/tidb/domain/infosync" "github.com/pingcap/tidb/kv" + "github.com/pingcap/tidb/parser/ast" + "github.com/pingcap/tidb/parser/terror" "github.com/pingcap/tidb/sessionctx" + "github.com/pingcap/tidb/sessionctx/variable" + "github.com/pingcap/tidb/statistics/handle" + "github.com/pingcap/tidb/util/chunk" "github.com/pingcap/tidb/util/logutil" "github.com/pingcap/tidb/util/sqlexec" "go.uber.org/zap" @@ -115,16 +121,23 @@ func (p *dumpFileGcChecker) gcDumpFilesByPath(path string, t time.Duration) { } type planReplayerHandle struct { - sync.Mutex - sctx sessionctx.Context + sctxMu struct { + sync.Mutex + sctx sessionctx.Context + } + + taskMu struct { + sync.RWMutex + tasks map[PlanReplayerTaskKey]struct{} + } } // DeletePlanReplayerStatus delete mysql.plan_replayer_status record func (h *planReplayerHandle) deletePlanReplayerStatus(ctx context.Context, token string) { ctx1 := kv.WithInternalSourceType(ctx, kv.InternalTxnStats) - h.Lock() - defer h.Unlock() - exec := h.sctx.(sqlexec.SQLExecutor) + h.sctxMu.Lock() + defer h.sctxMu.Unlock() + exec := h.sctxMu.sctx.(sqlexec.SQLExecutor) _, err := exec.ExecuteInternal(ctx1, fmt.Sprintf("delete from mysql.plan_replayer_status where token = %v", token)) if err != nil { logutil.BgLogger().Warn("delete mysql.plan_replayer_status record failed", zap.String("token", token), zap.Error(err)) @@ -154,9 +167,9 @@ func (h *planReplayerHandle) InsertPlanReplayerStatus(ctx context.Context, recor } func (h *planReplayerHandle) insertExternalPlanReplayerErrorStatusRecord(ctx context.Context, instance string, record PlanReplayerStatusRecord) { - h.Lock() - defer h.Unlock() - exec := h.sctx.(sqlexec.SQLExecutor) + h.sctxMu.Lock() + defer h.sctxMu.Unlock() + exec := h.sctxMu.sctx.(sqlexec.SQLExecutor) _, err := exec.ExecuteInternal(ctx, fmt.Sprintf( "insert into mysql.plan_replayer_status (origin_sql, fail_reason, instance) values ('%s','%s','%s')", record.OriginSQL, record.FailedReason, instance)) @@ -167,9 +180,9 @@ func (h *planReplayerHandle) insertExternalPlanReplayerErrorStatusRecord(ctx con } func (h *planReplayerHandle) insertExternalPlanReplayerSuccessStatusRecord(ctx context.Context, instance string, record PlanReplayerStatusRecord) { - h.Lock() - defer h.Unlock() - exec := h.sctx.(sqlexec.SQLExecutor) + h.sctxMu.Lock() + defer h.sctxMu.Unlock() + exec := h.sctxMu.sctx.(sqlexec.SQLExecutor) _, err := exec.ExecuteInternal(ctx, fmt.Sprintf( "insert into mysql.plan_replayer_status (origin_sql, token, instance) values ('%s','%s','%s')", record.OriginSQL, record.Token, instance)) @@ -179,6 +192,97 @@ func (h *planReplayerHandle) insertExternalPlanReplayerSuccessStatusRecord(ctx c } } +// CollectPlanReplayerTask collects all unhandled plan replayer task +func (h *planReplayerHandle) CollectPlanReplayerTask(ctx context.Context) error { + ctx1 := kv.WithInternalSourceType(ctx, kv.InternalTxnStats) + allKeys, err := h.collectAllPlanReplayerTask(ctx1) + if err != nil { + return err + } + tasks := make([]PlanReplayerTaskKey, 0) + for _, key := range allKeys { + unhandled, err := h.checkUnHandledReplayerTask(ctx1, key) + if err != nil { + return err + } + if unhandled { + tasks = append(tasks, key) + } + } + h.setupTasks(tasks) + return nil +} + +// GetTasks get all tasks +func (h *planReplayerHandle) GetTasks() []PlanReplayerTaskKey { + tasks := make([]PlanReplayerTaskKey, 0) + h.taskMu.RLock() + defer h.taskMu.RUnlock() + for taskKey := range h.taskMu.tasks { + tasks = append(tasks, taskKey) + } + return tasks +} + +func (h *planReplayerHandle) setupTasks(tasks []PlanReplayerTaskKey) { + r := make(map[PlanReplayerTaskKey]struct{}) + for _, task := range tasks { + r[task] = struct{}{} + } + h.taskMu.Lock() + defer h.taskMu.Unlock() + h.taskMu.tasks = r +} + +func (h *planReplayerHandle) collectAllPlanReplayerTask(ctx context.Context) ([]PlanReplayerTaskKey, error) { + h.sctxMu.Lock() + defer h.sctxMu.Unlock() + exec := h.sctxMu.sctx.(sqlexec.SQLExecutor) + rs, err := exec.ExecuteInternal(ctx, "select sql_digest, plan_digest from mysql.plan_replayer_task") + if err != nil { + return nil, err + } + if rs == nil { + return nil, nil + } + var rows []chunk.Row + defer terror.Call(rs.Close) + if rows, err = sqlexec.DrainRecordSet(ctx, rs, 8); err != nil { + return nil, errors.Trace(err) + } + allKeys := make([]PlanReplayerTaskKey, 0, len(rows)) + for _, row := range rows { + sqlDigest, planDigest := row.GetString(0), row.GetString(1) + allKeys = append(allKeys, PlanReplayerTaskKey{ + sqlDigest: sqlDigest, + planDigest: planDigest, + }) + } + return allKeys, nil +} + +func (h *planReplayerHandle) checkUnHandledReplayerTask(ctx context.Context, task PlanReplayerTaskKey) (bool, error) { + h.sctxMu.Lock() + defer h.sctxMu.Unlock() + exec := h.sctxMu.sctx.(sqlexec.SQLExecutor) + rs, err := exec.ExecuteInternal(ctx, fmt.Sprintf("select * from mysql.plan_replayer_status where sql_digest = '%v' and plan_digest = '%v' and fail_reason is null", task.sqlDigest, task.planDigest)) + if err != nil { + return false, err + } + if rs == nil { + return true, nil + } + var rows []chunk.Row + defer terror.Call(rs.Close) + if rows, err = sqlexec.DrainRecordSet(ctx, rs, 8); err != nil { + return false, errors.Trace(err) + } + if len(rows) > 0 { + return false, nil + } + return true, nil +} + // PlanReplayerStatusRecord indicates record in mysql.plan_replayer_status type PlanReplayerStatusRecord struct { Internal bool @@ -186,3 +290,21 @@ type PlanReplayerStatusRecord struct { Token string FailedReason string } + +// PlanReplayerTaskKey indicates key of a plan replayer task +type PlanReplayerTaskKey struct { + sqlDigest string + planDigest string +} + +// PlanReplayerDumpTask wrap the params for plan replayer dump +type PlanReplayerDumpTask struct { + SessionBindings []*bindinfo.BindRecord + EncodedPlan string + FileName string + Zf *os.File + SessionVars *variable.SessionVars + TblStats map[int64]*handle.JSONTable + ExecStmts []ast.StmtNode + Analyze bool +} diff --git a/domain/plan_replayer_handle_test.go b/domain/plan_replayer_handle_test.go new file mode 100644 index 0000000000000..3ca8e9e408c61 --- /dev/null +++ b/domain/plan_replayer_handle_test.go @@ -0,0 +1,50 @@ +package domain_test + +import ( + "context" + "testing" + + "github.com/pingcap/tidb/testkit" + "github.com/stretchr/testify/require" +) + +func TestPlanReplayerHandleCollectTask(t *testing.T) { + store, dom := testkit.CreateMockStoreAndDomain(t) + tk := testkit.NewTestKit(t, store) + prHandle := dom.GetPlanReplayerHandle() + + // assert 1 task + tk.MustExec("delete from mysql.plan_replayer_task") + tk.MustExec("delete from mysql.plan_replayer_status") + tk.MustExec("insert into mysql.plan_replayer_task (sql_digest, plan_digest) values ('123','123');") + err := prHandle.CollectPlanReplayerTask(context.Background()) + require.NoError(t, err) + require.Len(t, prHandle.GetTasks(), 1) + + // assert no task + tk.MustExec("delete from mysql.plan_replayer_task") + tk.MustExec("delete from mysql.plan_replayer_status") + err = prHandle.CollectPlanReplayerTask(context.Background()) + require.NoError(t, err) + require.Len(t, prHandle.GetTasks(), 0) + + // assert 1 unhandled task + tk.MustExec("delete from mysql.plan_replayer_task") + tk.MustExec("delete from mysql.plan_replayer_status") + tk.MustExec("insert into mysql.plan_replayer_task (sql_digest, plan_digest) values ('123','123');") + tk.MustExec("insert into mysql.plan_replayer_task (sql_digest, plan_digest) values ('345','345');") + tk.MustExec("insert into mysql.plan_replayer_status(sql_digest, plan_digest, token, instance) values ('123','123','123','123')") + err = prHandle.CollectPlanReplayerTask(context.Background()) + require.NoError(t, err) + require.Len(t, prHandle.GetTasks(), 1) + + // assert 2 unhandled task + tk.MustExec("delete from mysql.plan_replayer_task") + tk.MustExec("delete from mysql.plan_replayer_status") + tk.MustExec("insert into mysql.plan_replayer_task (sql_digest, plan_digest) values ('123','123');") + tk.MustExec("insert into mysql.plan_replayer_task (sql_digest, plan_digest) values ('345','345');") + tk.MustExec("insert into mysql.plan_replayer_status(sql_digest, plan_digest, fail_reason, instance) values ('123','123','123','123')") + err = prHandle.CollectPlanReplayerTask(context.Background()) + require.NoError(t, err) + require.Len(t, prHandle.GetTasks(), 2) +} diff --git a/executor/plan_replayer.go b/executor/plan_replayer.go index 566e274469bee..195728c01e96d 100644 --- a/executor/plan_replayer.go +++ b/executor/plan_replayer.go @@ -226,7 +226,7 @@ func generatePlanReplayerFileName() (string, error) { func (e *PlanReplayerDumpInfo) dump(ctx context.Context) (err error) { fileName := e.FileName zf := e.File - task := &PlanReplayerDumpTask{ + task := &domain.PlanReplayerDumpTask{ FileName: fileName, Zf: zf, SessionVars: e.ctx.GetSessionVars(), @@ -242,18 +242,6 @@ func (e *PlanReplayerDumpInfo) dump(ctx context.Context) (err error) { return nil } -// PlanReplayerDumpTask wrap the params for plan replayer dump -type PlanReplayerDumpTask struct { - SessionBindings []*bindinfo.BindRecord - EncodedPlan string - FileName string - Zf *os.File - SessionVars *variable.SessionVars - TblStats map[int64]*handle.JSONTable - ExecStmts []ast.StmtNode - Analyze bool -} - // DumpPlanReplayerInfo will dump the information about sqls. // The files will be organized into the following format: /* @@ -284,7 +272,7 @@ type PlanReplayerDumpTask struct { |-.... */ func DumpPlanReplayerInfo(ctx context.Context, sctx sessionctx.Context, - task *PlanReplayerDumpTask) (err error) { + task *domain.PlanReplayerDumpTask) (err error) { zf := task.Zf fileName := task.FileName sessionVars := task.SessionVars @@ -375,7 +363,7 @@ func DumpPlanReplayerInfo(ctx context.Context, sctx sessionctx.Context, return dumpExplain(sctx, zw, execStmts, task.Analyze) } -func generateRecords(task *PlanReplayerDumpTask) []domain.PlanReplayerStatusRecord { +func generateRecords(task *domain.PlanReplayerDumpTask) []domain.PlanReplayerStatusRecord { records := make([]domain.PlanReplayerStatusRecord, 0) if len(task.ExecStmts) > 0 { for _, execStmt := range task.ExecStmts { diff --git a/session/bootstrap.go b/session/bootstrap.go index 7e647370ca4f3..53a864b58a29e 100644 --- a/session/bootstrap.go +++ b/session/bootstrap.go @@ -440,6 +440,13 @@ const ( update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, fail_reason TEXT, instance VARCHAR(512) NOT NULL comment 'address of the TiDB instance executing the plan replayer job');` + + // CreatePlanReplayerTaskTable is a table about plan replayer capture task + CreatePlanReplayerTaskTable = `CREATE TABLE IF NOT EXISTS mysql.plan_replayer_task ( + sql_digest VARCHAR(128) NOT NULL, + plan_digest VARCHAR(128) NOT NULL, + update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (sql_digest,plan_digest));` ) // bootstrap initiates system DB for a store. @@ -656,11 +663,13 @@ const ( version100 = 100 // version101 add a table version101 = 101 + // version102 add mysql.plan_replayer_task table + version102 = 102 ) // currentBootstrapVersion is defined as a variable, so we can modify its value for testing. // please make sure this is the largest version -var currentBootstrapVersion int64 = version101 +var currentBootstrapVersion int64 = version102 // DDL owner key's expired time is ManagerSessionTTL seconds, we should wait the time and give more time to have a chance to finish it. var internalSQLTimeout = owner.ManagerSessionTTL + 15 @@ -766,6 +775,7 @@ var ( upgradeToVer98, upgradeToVer100, upgradeToVer101, + upgradeToVer102, } ) @@ -2007,6 +2017,13 @@ func upgradeToVer101(s Session, ver int64) { doReentrantDDL(s, CreatePlanReplayerStatusTable) } +func upgradeToVer102(s Session, ver int64) { + if ver >= version102 { + return + } + doReentrantDDL(s, CreatePlanReplayerTaskTable) +} + func upgradeToVer99Before(s Session, ver int64) bool { if ver >= version99 { return false @@ -2144,6 +2161,8 @@ func doDDLWorks(s Session) { mustExecute(s, CreateMDLView) // Create plan_replayer_status table mustExecute(s, CreatePlanReplayerStatusTable) + // Create plan_replayer_task table + mustExecute(s, CreatePlanReplayerTaskTable) } // inTestSuite checks if we are bootstrapping in the context of tests. diff --git a/session/session.go b/session/session.go index 268c1a9f84092..d2f4afd10b679 100644 --- a/session/session.go +++ b/session/session.go @@ -2969,8 +2969,10 @@ func BootstrapSession(store kv.Storage) (*domain.Domain, error) { }() } - // setup dumpFileGcChecker + // setup plan replayer handle dom.SetupPlanReplayerHandle(ses[6]) + dom.StartPlanReplayerHandle() + // setup dumpFileGcChecker dom.DumpFileGcCheckerLoop() // A sub context for update table stats, and other contexts for concurrent stats loading. From 22f9af228d133e611d9f1e48cb6b0c7fdea5d0ee Mon Sep 17 00:00:00 2001 From: yisaer Date: Thu, 10 Nov 2022 10:40:16 +0800 Subject: [PATCH 08/20] fix bazel Signed-off-by: yisaer --- domain/BUILD.bazel | 3 +++ 1 file changed, 3 insertions(+) diff --git a/domain/BUILD.bazel b/domain/BUILD.bazel index 7594fdb610b1b..74fd5c6cbc14f 100644 --- a/domain/BUILD.bazel +++ b/domain/BUILD.bazel @@ -46,6 +46,7 @@ go_library( "//telemetry", "//types", "//util", + "//util/chunk", "//util/dbterror", "//util/domainutil", "//util/engine", @@ -86,6 +87,7 @@ go_test( "domain_utils_test.go", "domainctx_test.go", "main_test.go", + "plan_replayer_handle_test.go", "plan_replayer_test.go", "schema_checker_test.go", "schema_validator_test.go", @@ -109,6 +111,7 @@ go_test( "//session", "//sessionctx/variable", "//store/mockstore", + "//testkit", "//testkit/testsetup", "//util", "//util/mock", From eb0bad40bef46e80f6829312beff95828b6000d1 Mon Sep 17 00:00:00 2001 From: yisaer Date: Thu, 10 Nov 2022 10:58:09 +0800 Subject: [PATCH 09/20] fix liense Signed-off-by: yisaer --- domain/plan_replayer_handle_test.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/domain/plan_replayer_handle_test.go b/domain/plan_replayer_handle_test.go index 3ca8e9e408c61..2c25f56e15045 100644 --- a/domain/plan_replayer_handle_test.go +++ b/domain/plan_replayer_handle_test.go @@ -1,3 +1,17 @@ +// Copyright 2022 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package domain_test import ( From 21f08b5a91de6aedea350fa8f21bb882d7af49e4 Mon Sep 17 00:00:00 2001 From: yisaer Date: Thu, 10 Nov 2022 12:46:50 +0800 Subject: [PATCH 10/20] fix test Signed-off-by: yisaer --- domain/domain.go | 12 +++++++++++- executor/infoschema_cluster_table_test.go | 2 +- server/http_handler_serial_test.go | 4 ++-- testkit/mockstore.go | 1 + 4 files changed, 15 insertions(+), 4 deletions(-) diff --git a/domain/domain.go b/domain/domain.go index 8001cbdb23421..cf94bee52a8bc 100644 --- a/domain/domain.go +++ b/domain/domain.go @@ -1538,11 +1538,21 @@ func (do *Domain) SetupPlanReplayerHandle(ctx sessionctx.Context) { do.dumpFileGcChecker.setupPlanReplayerHandle(do.planReplayerHandle) } +var planReplayerHandleLease = 10 * time.Second + +// DisablePlanReplayerBackgroundJob4Test disable plan replayer handle for test +func DisablePlanReplayerBackgroundJob4Test() { + planReplayerHandleLease = 0 +} + // StartPlanReplayerHandle start plan replayer handle job func (do *Domain) StartPlanReplayerHandle() { + if planReplayerHandleLease < 1 { + return + } do.wg.Add(1) go func() { - tikcer := time.NewTicker(10 * time.Second) + tikcer := time.NewTicker(planReplayerHandleLease) defer func() { tikcer.Stop() do.wg.Done() diff --git a/executor/infoschema_cluster_table_test.go b/executor/infoschema_cluster_table_test.go index d2e230c6fd766..f00cd5bfc0ff1 100644 --- a/executor/infoschema_cluster_table_test.go +++ b/executor/infoschema_cluster_table_test.go @@ -290,7 +290,7 @@ func TestTableStorageStats(t *testing.T) { "test 2", )) rows := tk.MustQuery("select TABLE_NAME from information_schema.TABLE_STORAGE_STATS where TABLE_SCHEMA = 'mysql';").Rows() - result := 38 + result := 39 require.Len(t, rows, result) // More tests about the privileges. diff --git a/server/http_handler_serial_test.go b/server/http_handler_serial_test.go index b0b3903eeb201..cfc542443c000 100644 --- a/server/http_handler_serial_test.go +++ b/server/http_handler_serial_test.go @@ -310,13 +310,13 @@ func TestTiFlashReplica(t *testing.T) { require.Equal(t, "a,b", strings.Join(data[0].LocationLabels, ",")) require.Equal(t, false, data[0].Available) - resp, err = ts.postStatus("/tiflash/replica-deprecated", "application/json", bytes.NewBuffer([]byte(`{"id":84,"region_count":3,"flash_region_count":3}`))) + resp, err = ts.postStatus("/tiflash/replica-deprecated", "application/json", bytes.NewBuffer([]byte(`{"id":184,"region_count":3,"flash_region_count":3}`))) require.NoError(t, err) require.NotNil(t, resp) body, err := io.ReadAll(resp.Body) require.NoError(t, err) require.NoError(t, resp.Body.Close()) - require.Equal(t, "[schema:1146]Table which ID = 84 does not exist.", string(body)) + require.Equal(t, "[schema:1146]Table which ID = 184 does not exist.", string(body)) tbl, err := ts.domain.InfoSchema().TableByName(model.NewCIStr("tidb"), model.NewCIStr("test")) require.NoError(t, err) diff --git a/testkit/mockstore.go b/testkit/mockstore.go index 21b12eaecd611..525381dd9c148 100644 --- a/testkit/mockstore.go +++ b/testkit/mockstore.go @@ -79,6 +79,7 @@ func CreateMockStoreAndDomain(t testing.TB, opts ...mockstore.MockTiKVStoreOptio func bootstrap(t testing.TB, store kv.Storage, lease time.Duration) *domain.Domain { session.SetSchemaLease(lease) session.DisableStats4Test() + domain.DisablePlanReplayerBackgroundJob4Test() dom, err := session.BootstrapSession(store) require.NoError(t, err) From 654d2ceb76a9a89d4b74735d41f6c108185a91b0 Mon Sep 17 00:00:00 2001 From: yisaer Date: Fri, 11 Nov 2022 11:18:51 +0800 Subject: [PATCH 11/20] address the comment Signed-off-by: yisaer --- domain/domain.go | 5 ++--- session/bootstrap.go | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/domain/domain.go b/domain/domain.go index cf94bee52a8bc..13e131875db07 100644 --- a/domain/domain.go +++ b/domain/domain.go @@ -1550,8 +1550,7 @@ func (do *Domain) StartPlanReplayerHandle() { if planReplayerHandleLease < 1 { return } - do.wg.Add(1) - go func() { + do.wg.Run(func() { tikcer := time.NewTicker(planReplayerHandleLease) defer func() { tikcer.Stop() @@ -1570,7 +1569,7 @@ func (do *Domain) StartPlanReplayerHandle() { } } } - }() + }) } // GetPlanReplayerHandle returns plan replayer handle diff --git a/session/bootstrap.go b/session/bootstrap.go index 2debd13a58bcf..f73bfd560d3d8 100644 --- a/session/bootstrap.go +++ b/session/bootstrap.go @@ -661,7 +661,7 @@ const ( version99 = 99 // version100 converts server-memory-quota to a sysvar version100 = 100 - //version101 add mysql.plan_replayer_status table + // version101 add mysql.plan_replayer_status table version101 = 101 // version102 add mysql.plan_replayer_task table version102 = 102 From 79d92fa1aa51166d8a5a3ab7b553e66b3e07bda8 Mon Sep 17 00:00:00 2001 From: yisaer Date: Fri, 11 Nov 2022 14:31:34 +0800 Subject: [PATCH 12/20] refactor Signed-off-by: yisaer --- domain/domain.go | 10 +- domain/plan_replayer.go | 69 ++-- domain/plan_replayer_dump.go | 655 +++++++++++++++++++++++++++++++++++ executor/plan_replayer.go | 649 +--------------------------------- session/session.go | 3 +- 5 files changed, 699 insertions(+), 687 deletions(-) create mode 100644 domain/plan_replayer_dump.go diff --git a/domain/domain.go b/domain/domain.go index 13e131875db07..39d44c569674e 100644 --- a/domain/domain.go +++ b/domain/domain.go @@ -1534,8 +1534,14 @@ func (do *Domain) TelemetryRotateSubWindowLoop(ctx sessionctx.Context) { // SetupPlanReplayerHandle setup plan replayer handle func (do *Domain) SetupPlanReplayerHandle(ctx sessionctx.Context) { do.planReplayerHandle = &planReplayerHandle{} - do.planReplayerHandle.sctxMu.sctx = ctx - do.dumpFileGcChecker.setupPlanReplayerHandle(do.planReplayerHandle) + do.planReplayerHandle.planReplayerTaskCollectorHandle = &planReplayerTaskCollectorHandle{ + sctx: ctx, + } +} + +// SetupDumpFileGCChecker setup sctx +func (do *Domain) SetupDumpFileGCChecker(ctx sessionctx.Context) { + do.dumpFileGcChecker.setupSctx(ctx) } var planReplayerHandleLease = 10 * time.Second diff --git a/domain/plan_replayer.go b/domain/plan_replayer.go index 37f3a4568c880..efc2e8ad21429 100644 --- a/domain/plan_replayer.go +++ b/domain/plan_replayer.go @@ -45,9 +45,9 @@ import ( // For now it is used by `plan replayer` and `trace plan` statement type dumpFileGcChecker struct { sync.Mutex - gcLease time.Duration - paths []string - planReplayerHandle *planReplayerHandle + gcLease time.Duration + paths []string + sctx sessionctx.Context } // GetPlanReplayerDirName returns plan replayer directory path. @@ -85,8 +85,8 @@ func (p *dumpFileGcChecker) gcDumpFiles(t time.Duration) { } } -func (p *dumpFileGcChecker) setupPlanReplayerHandle(handle *planReplayerHandle) { - p.planReplayerHandle = handle +func (p *dumpFileGcChecker) setupSctx(sctx sessionctx.Context) { + p.sctx = sctx } func (p *dumpFileGcChecker) gcDumpFilesByPath(path string, t time.Duration) { @@ -113,39 +113,36 @@ func (p *dumpFileGcChecker) gcDumpFilesByPath(path string, t time.Duration) { continue } logutil.BgLogger().Info("dumpFileGcChecker successful", zap.String("filename", fileName)) - if isPlanReplayer && p.planReplayerHandle != nil { - p.planReplayerHandle.deletePlanReplayerStatus(context.Background(), fileName) + if isPlanReplayer && p.sctx != nil { + deletePlanReplayerStatus(context.Background(), p.sctx, fileName) } } } } type planReplayerHandle struct { - sctxMu struct { - sync.Mutex - sctx sessionctx.Context - } + *planReplayerTaskCollectorHandle +} +type planReplayerTaskCollectorHandle struct { taskMu struct { sync.RWMutex tasks map[PlanReplayerTaskKey]struct{} } + sctx sessionctx.Context } -// DeletePlanReplayerStatus delete mysql.plan_replayer_status record -func (h *planReplayerHandle) deletePlanReplayerStatus(ctx context.Context, token string) { +func deletePlanReplayerStatus(ctx context.Context, sctx sessionctx.Context, token string) { ctx1 := kv.WithInternalSourceType(ctx, kv.InternalTxnStats) - h.sctxMu.Lock() - defer h.sctxMu.Unlock() - exec := h.sctxMu.sctx.(sqlexec.SQLExecutor) + exec := sctx.(sqlexec.SQLExecutor) _, err := exec.ExecuteInternal(ctx1, fmt.Sprintf("delete from mysql.plan_replayer_status where token = %v", token)) if err != nil { logutil.BgLogger().Warn("delete mysql.plan_replayer_status record failed", zap.String("token", token), zap.Error(err)) } } -// InsertPlanReplayerStatus insert mysql.plan_replayer_status record -func (h *planReplayerHandle) InsertPlanReplayerStatus(ctx context.Context, records []PlanReplayerStatusRecord) { +// insertPlanReplayerStatus insert mysql.plan_replayer_status record +func insertPlanReplayerStatus(ctx context.Context, sctx sessionctx.Context, records []PlanReplayerStatusRecord) { ctx1 := kv.WithInternalSourceType(ctx, kv.InternalTxnStats) var instance string serverInfo, err := infosync.GetServerInfo() @@ -158,18 +155,16 @@ func (h *planReplayerHandle) InsertPlanReplayerStatus(ctx context.Context, recor for _, record := range records { if !record.Internal { if len(record.FailedReason) > 0 { - h.insertExternalPlanReplayerErrorStatusRecord(ctx1, instance, record) + insertExternalPlanReplayerErrorStatusRecord(ctx1, sctx, instance, record) } else { - h.insertExternalPlanReplayerSuccessStatusRecord(ctx1, instance, record) + insertExternalPlanReplayerSuccessStatusRecord(ctx1, sctx, instance, record) } } } } -func (h *planReplayerHandle) insertExternalPlanReplayerErrorStatusRecord(ctx context.Context, instance string, record PlanReplayerStatusRecord) { - h.sctxMu.Lock() - defer h.sctxMu.Unlock() - exec := h.sctxMu.sctx.(sqlexec.SQLExecutor) +func insertExternalPlanReplayerErrorStatusRecord(ctx context.Context, sctx sessionctx.Context, instance string, record PlanReplayerStatusRecord) { + exec := sctx.(sqlexec.SQLExecutor) _, err := exec.ExecuteInternal(ctx, fmt.Sprintf( "insert into mysql.plan_replayer_status (origin_sql, fail_reason, instance) values ('%s','%s','%s')", record.OriginSQL, record.FailedReason, instance)) @@ -179,10 +174,8 @@ func (h *planReplayerHandle) insertExternalPlanReplayerErrorStatusRecord(ctx con } } -func (h *planReplayerHandle) insertExternalPlanReplayerSuccessStatusRecord(ctx context.Context, instance string, record PlanReplayerStatusRecord) { - h.sctxMu.Lock() - defer h.sctxMu.Unlock() - exec := h.sctxMu.sctx.(sqlexec.SQLExecutor) +func insertExternalPlanReplayerSuccessStatusRecord(ctx context.Context, sctx sessionctx.Context, instance string, record PlanReplayerStatusRecord) { + exec := sctx.(sqlexec.SQLExecutor) _, err := exec.ExecuteInternal(ctx, fmt.Sprintf( "insert into mysql.plan_replayer_status (origin_sql, token, instance) values ('%s','%s','%s')", record.OriginSQL, record.Token, instance)) @@ -193,7 +186,7 @@ func (h *planReplayerHandle) insertExternalPlanReplayerSuccessStatusRecord(ctx c } // CollectPlanReplayerTask collects all unhandled plan replayer task -func (h *planReplayerHandle) CollectPlanReplayerTask(ctx context.Context) error { +func (h *planReplayerTaskCollectorHandle) CollectPlanReplayerTask(ctx context.Context) error { ctx1 := kv.WithInternalSourceType(ctx, kv.InternalTxnStats) allKeys, err := h.collectAllPlanReplayerTask(ctx1) if err != nil { @@ -201,7 +194,7 @@ func (h *planReplayerHandle) CollectPlanReplayerTask(ctx context.Context) error } tasks := make([]PlanReplayerTaskKey, 0) for _, key := range allKeys { - unhandled, err := h.checkUnHandledReplayerTask(ctx1, key) + unhandled, err := checkUnHandledReplayerTask(ctx1, h.sctx, key) if err != nil { return err } @@ -214,7 +207,7 @@ func (h *planReplayerHandle) CollectPlanReplayerTask(ctx context.Context) error } // GetTasks get all tasks -func (h *planReplayerHandle) GetTasks() []PlanReplayerTaskKey { +func (h *planReplayerTaskCollectorHandle) GetTasks() []PlanReplayerTaskKey { tasks := make([]PlanReplayerTaskKey, 0) h.taskMu.RLock() defer h.taskMu.RUnlock() @@ -224,7 +217,7 @@ func (h *planReplayerHandle) GetTasks() []PlanReplayerTaskKey { return tasks } -func (h *planReplayerHandle) setupTasks(tasks []PlanReplayerTaskKey) { +func (h *planReplayerTaskCollectorHandle) setupTasks(tasks []PlanReplayerTaskKey) { r := make(map[PlanReplayerTaskKey]struct{}) for _, task := range tasks { r[task] = struct{}{} @@ -234,10 +227,8 @@ func (h *planReplayerHandle) setupTasks(tasks []PlanReplayerTaskKey) { h.taskMu.tasks = r } -func (h *planReplayerHandle) collectAllPlanReplayerTask(ctx context.Context) ([]PlanReplayerTaskKey, error) { - h.sctxMu.Lock() - defer h.sctxMu.Unlock() - exec := h.sctxMu.sctx.(sqlexec.SQLExecutor) +func (h *planReplayerTaskCollectorHandle) collectAllPlanReplayerTask(ctx context.Context) ([]PlanReplayerTaskKey, error) { + exec := h.sctx.(sqlexec.SQLExecutor) rs, err := exec.ExecuteInternal(ctx, "select sql_digest, plan_digest from mysql.plan_replayer_task") if err != nil { return nil, err @@ -261,10 +252,8 @@ func (h *planReplayerHandle) collectAllPlanReplayerTask(ctx context.Context) ([] return allKeys, nil } -func (h *planReplayerHandle) checkUnHandledReplayerTask(ctx context.Context, task PlanReplayerTaskKey) (bool, error) { - h.sctxMu.Lock() - defer h.sctxMu.Unlock() - exec := h.sctxMu.sctx.(sqlexec.SQLExecutor) +func checkUnHandledReplayerTask(ctx context.Context, sctx sessionctx.Context, task PlanReplayerTaskKey) (bool, error) { + exec := sctx.(sqlexec.SQLExecutor) rs, err := exec.ExecuteInternal(ctx, fmt.Sprintf("select * from mysql.plan_replayer_status where sql_digest = '%v' and plan_digest = '%v' and fail_reason is null", task.sqlDigest, task.planDigest)) if err != nil { return false, err diff --git a/domain/plan_replayer_dump.go b/domain/plan_replayer_dump.go new file mode 100644 index 0000000000000..79fde5676023b --- /dev/null +++ b/domain/plan_replayer_dump.go @@ -0,0 +1,655 @@ +// Copyright 2022 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package domain + +import ( + "archive/zip" + "context" + "encoding/json" + "fmt" + "io" + "strconv" + "strings" + + "github.com/BurntSushi/toml" + "github.com/pingcap/errors" + "github.com/pingcap/tidb/bindinfo" + "github.com/pingcap/tidb/config" + "github.com/pingcap/tidb/infoschema" + "github.com/pingcap/tidb/parser/ast" + "github.com/pingcap/tidb/parser/model" + "github.com/pingcap/tidb/sessionctx" + "github.com/pingcap/tidb/sessionctx/variable" + "github.com/pingcap/tidb/statistics/handle" + "github.com/pingcap/tidb/util/chunk" + "github.com/pingcap/tidb/util/logutil" + "github.com/pingcap/tidb/util/printer" + "github.com/pingcap/tidb/util/sqlexec" + "go.uber.org/zap" +) + +const ( + PlanReplayerConfigFile = "config.toml" + PlanReplayerMetaFile = "meta.txt" + PlanReplayerVariablesFile = "variables.toml" + PlanReplayerTiFlashReplicasFile = "table_tiflash_replica.txt" + PlanReplayerSessionBindingFile = "session_bindings.sql" + PlanReplayerGlobalBindingFile = "global_bindings.sql" +) + +type tableNamePair struct { + DBName string + TableName string + IsView bool +} + +type tableNameExtractor struct { + ctx context.Context + executor sqlexec.RestrictedSQLExecutor + is infoschema.InfoSchema + curDB model.CIStr + names map[tableNamePair]struct{} + cteNames map[string]struct{} + err error +} + +func (tne *tableNameExtractor) Enter(in ast.Node) (ast.Node, bool) { + if _, ok := in.(*ast.TableName); ok { + return in, true + } + return in, false +} + +func (tne *tableNameExtractor) Leave(in ast.Node) (ast.Node, bool) { + if tne.err != nil { + return in, true + } + if t, ok := in.(*ast.TableName); ok { + isView, err := tne.handleIsView(t) + if err != nil { + tne.err = err + return in, true + } + tp := tableNamePair{DBName: t.Schema.L, TableName: t.Name.L, IsView: isView} + if tp.DBName == "" { + tp.DBName = tne.curDB.L + } + if _, ok := tne.names[tp]; !ok { + tne.names[tp] = struct{}{} + } + } else if s, ok := in.(*ast.SelectStmt); ok { + if s.With != nil && len(s.With.CTEs) > 0 { + for _, cte := range s.With.CTEs { + tne.cteNames[cte.Name.L] = struct{}{} + } + } + } + return in, true +} + +func (tne *tableNameExtractor) handleIsView(t *ast.TableName) (bool, error) { + schema := t.Schema + if schema.L == "" { + schema = tne.curDB + } + table := t.Name + isView := tne.is.TableIsView(schema, table) + if !isView { + return false, nil + } + viewTbl, err := tne.is.TableByName(schema, table) + if err != nil { + return false, err + } + sql := viewTbl.Meta().View.SelectStmt + node, err := tne.executor.ParseWithParams(tne.ctx, sql) + if err != nil { + return false, err + } + node.Accept(tne) + return true, nil +} + +// DumpPlanReplayerInfo will dump the information about sqls. +// The files will be organized into the following format: +/* + |-meta.txt + |-schema + | |-db1.table1.schema.txt + | |-db2.table2.schema.txt + | |-.... + |-view + | |-db1.view1.view.txt + | |-db2.view2.view.txt + | |-.... + |-stats + | |-stats1.json + | |-stats2.json + | |-.... + |-config.toml + |-table_tiflash_replica.txt + |-variables.toml + |-bindings.sql + |-sql + | |-sql1.sql + | |-sql2.sql + | |-.... + |_explain + |-explain1.txt + |-explain2.txt + |-.... +*/ +func DumpPlanReplayerInfo(ctx context.Context, sctx sessionctx.Context, + task *PlanReplayerDumpTask) (err error) { + zf := task.Zf + fileName := task.FileName + sessionVars := task.SessionVars + execStmts := task.ExecStmts + zw := zip.NewWriter(zf) + records := generateRecords(task) + defer func() { + err = zw.Close() + if err != nil { + logutil.BgLogger().Error("Closing zip writer failed", zap.Error(err), zap.String("filename", fileName)) + } + err = zf.Close() + if err != nil { + logutil.BgLogger().Error("Closing zip file failed", zap.Error(err), zap.String("filename", fileName)) + for i, record := range records { + record.FailedReason = err.Error() + records[i] = record + } + } + insertPlanReplayerStatus(ctx, sctx, records) + }() + // Dump config + if err = dumpConfig(zw); err != nil { + return err + } + + // Dump meta + if err = dumpMeta(zw); err != nil { + return err + } + // Retrieve current DB + dbName := model.NewCIStr(sessionVars.CurrentDB) + do := GetDomain(sctx) + + // Retrieve all tables + pairs, err := extractTableNames(ctx, sctx, execStmts, dbName) + if err != nil { + return errors.AddStack(fmt.Errorf("plan replayer: invalid SQL text, err: %v", err)) + } + + // Dump Schema and View + if err = dumpSchemas(sctx, zw, pairs); err != nil { + return err + } + + // Dump tables tiflash replicas + if err = dumpTiFlashReplica(sctx, zw, pairs); err != nil { + return err + } + + // Dump stats + if err = dumpStats(zw, pairs, task.TblStats, do); err != nil { + return err + } + + // Dump variables + if err = dumpVariables(sctx, sessionVars, zw); err != nil { + return err + } + + // Dump sql + if err = dumpSQLs(execStmts, zw); err != nil { + return err + } + + // Dump session bindings + if len(task.SessionBindings) > 0 { + if err = dumpSessionBindRecords(task.SessionBindings, zw); err != nil { + return err + } + } else { + if err = dumpSessionBindings(sctx, zw); err != nil { + return err + } + } + + // Dump global bindings + if err = dumpGlobalBindings(sctx, zw); err != nil { + return err + } + + if len(task.EncodedPlan) > 0 { + return dumpEncodedPlan(sctx, zw, task.EncodedPlan) + } + // Dump explain + return dumpExplain(sctx, zw, execStmts, task.Analyze) +} + +func generateRecords(task *PlanReplayerDumpTask) []PlanReplayerStatusRecord { + records := make([]PlanReplayerStatusRecord, 0) + if len(task.ExecStmts) > 0 { + for _, execStmt := range task.ExecStmts { + records = append(records, PlanReplayerStatusRecord{ + OriginSQL: execStmt.Text(), + Token: task.FileName, + Internal: false, + }) + } + } + return records +} + +func dumpConfig(zw *zip.Writer) error { + cf, err := zw.Create(PlanReplayerConfigFile) + if err != nil { + return errors.AddStack(err) + } + if err := toml.NewEncoder(cf).Encode(config.GetGlobalConfig()); err != nil { + return errors.AddStack(err) + } + return nil +} + +func dumpMeta(zw *zip.Writer) error { + mt, err := zw.Create(PlanReplayerMetaFile) + if err != nil { + return errors.AddStack(err) + } + _, err = mt.Write([]byte(printer.GetTiDBInfo())) + if err != nil { + return errors.AddStack(err) + } + return nil +} + +func dumpTiFlashReplica(ctx sessionctx.Context, zw *zip.Writer, pairs map[tableNamePair]struct{}) error { + bf, err := zw.Create(PlanReplayerTiFlashReplicasFile) + if err != nil { + return errors.AddStack(err) + } + is := GetDomain(ctx).InfoSchema() + for pair := range pairs { + dbName := model.NewCIStr(pair.DBName) + tableName := model.NewCIStr(pair.TableName) + t, err := is.TableByName(dbName, tableName) + if err != nil { + logutil.BgLogger().Warn("failed to find table info", zap.Error(err), + zap.String("dbName", dbName.L), zap.String("tableName", tableName.L)) + continue + } + if t.Meta().TiFlashReplica != nil && t.Meta().TiFlashReplica.Count > 0 { + row := []string{ + pair.DBName, pair.TableName, strconv.FormatUint(t.Meta().TiFlashReplica.Count, 10), + } + fmt.Fprintf(bf, "%s\n", strings.Join(row, "\t")) + } + } + return nil +} + +func dumpSchemas(ctx sessionctx.Context, zw *zip.Writer, pairs map[tableNamePair]struct{}) error { + for pair := range pairs { + err := getShowCreateTable(pair, zw, ctx) + if err != nil { + return err + } + } + return nil +} + +func dumpStats(zw *zip.Writer, pairs map[tableNamePair]struct{}, tblJSONStats map[int64]*handle.JSONTable, do *Domain) error { + for pair := range pairs { + if pair.IsView { + continue + } + jsonTbl, err := getStatsForTable(do, tblJSONStats, pair) + if err != nil { + return err + } + statsFw, err := zw.Create(fmt.Sprintf("stats/%v.%v.json", pair.DBName, pair.TableName)) + if err != nil { + return errors.AddStack(err) + } + data, err := json.Marshal(jsonTbl) + if err != nil { + return errors.AddStack(err) + } + _, err = statsFw.Write(data) + if err != nil { + return errors.AddStack(err) + } + } + return nil +} + +func dumpSQLs(execStmts []ast.StmtNode, zw *zip.Writer) error { + for i, stmtExec := range execStmts { + zf, err := zw.Create(fmt.Sprintf("sql/sql%v.sql", i)) + if err != nil { + return err + } + _, err = zf.Write([]byte(stmtExec.Text())) + if err != nil { + return err + } + } + return nil +} + +func dumpVariables(sctx sessionctx.Context, sessionVars *variable.SessionVars, zw *zip.Writer) error { + varMap := make(map[string]string) + for _, v := range variable.GetSysVars() { + if v.IsNoop && !variable.EnableNoopVariables.Load() { + continue + } + if infoschema.SysVarHiddenForSem(sctx, v.Name) { + continue + } + value, err := sessionVars.GetSessionOrGlobalSystemVar(context.Background(), v.Name) + if err != nil { + return errors.Trace(err) + } + varMap[v.Name] = value + } + vf, err := zw.Create(PlanReplayerVariablesFile) + if err != nil { + return errors.AddStack(err) + } + if err := toml.NewEncoder(vf).Encode(varMap); err != nil { + return errors.AddStack(err) + } + return nil +} + +func dumpSessionBindRecords(records []*bindinfo.BindRecord, zw *zip.Writer) error { + sRows := make([][]string, 0) + for _, bindData := range records { + for _, hint := range bindData.Bindings { + sRows = append(sRows, []string{ + bindData.OriginalSQL, + hint.BindSQL, + bindData.Db, + hint.Status, + hint.CreateTime.String(), + hint.UpdateTime.String(), + hint.Charset, + hint.Collation, + hint.Source, + }) + } + } + bf, err := zw.Create(PlanReplayerSessionBindingFile) + if err != nil { + return errors.AddStack(err) + } + for _, row := range sRows { + fmt.Fprintf(bf, "%s\n", strings.Join(row, "\t")) + } + return nil +} + +func dumpSessionBindings(ctx sessionctx.Context, zw *zip.Writer) error { + recordSets, err := ctx.(sqlexec.SQLExecutor).Execute(context.Background(), "show bindings") + if err != nil { + return err + } + sRows, err := resultSetToStringSlice(context.Background(), recordSets[0], true) + if err != nil { + return err + } + bf, err := zw.Create(PlanReplayerSessionBindingFile) + if err != nil { + return errors.AddStack(err) + } + for _, row := range sRows { + fmt.Fprintf(bf, "%s\n", strings.Join(row, "\t")) + } + if len(recordSets) > 0 { + if err := recordSets[0].Close(); err != nil { + return err + } + } + return nil +} + +func dumpGlobalBindings(ctx sessionctx.Context, zw *zip.Writer) error { + recordSets, err := ctx.(sqlexec.SQLExecutor).Execute(context.Background(), "show global bindings") + if err != nil { + return err + } + sRows, err := resultSetToStringSlice(context.Background(), recordSets[0], false) + if err != nil { + return err + } + bf, err := zw.Create(PlanReplayerGlobalBindingFile) + if err != nil { + return errors.AddStack(err) + } + for _, row := range sRows { + fmt.Fprintf(bf, "%s\n", strings.Join(row, "\t")) + } + if len(recordSets) > 0 { + if err := recordSets[0].Close(); err != nil { + return err + } + } + return nil +} + +func dumpEncodedPlan(ctx sessionctx.Context, zw *zip.Writer, encodedPlan string) error { + var recordSets []sqlexec.RecordSet + var err error + recordSets, err = ctx.(sqlexec.SQLExecutor).Execute(context.Background(), fmt.Sprintf("select tidb_decode_plan('%s')", encodedPlan)) + if err != nil { + return err + } + sRows, err := resultSetToStringSlice(context.Background(), recordSets[0], false) + if err != nil { + return err + } + fw, err := zw.Create("explain/sql.txt") + if err != nil { + return errors.AddStack(err) + } + for _, row := range sRows { + fmt.Fprintf(fw, "%s\n", strings.Join(row, "\t")) + } + if len(recordSets) > 0 { + if err := recordSets[0].Close(); err != nil { + return err + } + } + return nil +} + +func dumpExplain(ctx sessionctx.Context, zw *zip.Writer, execStmts []ast.StmtNode, isAnalyze bool) error { + for i, stmtExec := range execStmts { + sql := stmtExec.Text() + var recordSets []sqlexec.RecordSet + var err error + if isAnalyze { + // Explain analyze + recordSets, err = ctx.(sqlexec.SQLExecutor).Execute(context.Background(), fmt.Sprintf("explain analyze %s", sql)) + if err != nil { + return err + } + } else { + // Explain + recordSets, err = ctx.(sqlexec.SQLExecutor).Execute(context.Background(), fmt.Sprintf("explain %s", sql)) + if err != nil { + return err + } + } + sRows, err := resultSetToStringSlice(context.Background(), recordSets[0], false) + if err != nil { + return err + } + fw, err := zw.Create(fmt.Sprintf("explain/sql%v.txt", i)) + if err != nil { + return errors.AddStack(err) + } + for _, row := range sRows { + fmt.Fprintf(fw, "%s\n", strings.Join(row, "\t")) + } + if len(recordSets) > 0 { + if err := recordSets[0].Close(); err != nil { + return err + } + } + } + return nil +} + +func extractTableNames(ctx context.Context, sctx sessionctx.Context, + ExecStmts []ast.StmtNode, curDB model.CIStr) (map[tableNamePair]struct{}, error) { + tableExtractor := &tableNameExtractor{ + ctx: ctx, + executor: sctx.(sqlexec.RestrictedSQLExecutor), + is: GetDomain(sctx).InfoSchema(), + curDB: curDB, + names: make(map[tableNamePair]struct{}), + cteNames: make(map[string]struct{}), + } + for _, execStmt := range ExecStmts { + execStmt.Accept(tableExtractor) + } + if tableExtractor.err != nil { + return nil, tableExtractor.err + } + r := make(map[tableNamePair]struct{}) + for tablePair := range tableExtractor.names { + if tablePair.IsView { + r[tablePair] = struct{}{} + continue + } + // remove cte in table names + _, ok := tableExtractor.cteNames[tablePair.TableName] + if !ok { + r[tablePair] = struct{}{} + } + } + return r, nil +} + +func getStatsForTable(do *Domain, tblJSONStats map[int64]*handle.JSONTable, pair tableNamePair) (*handle.JSONTable, error) { + is := do.InfoSchema() + h := do.StatsHandle() + tbl, err := is.TableByName(model.NewCIStr(pair.DBName), model.NewCIStr(pair.TableName)) + if err != nil { + return nil, err + } + js, ok := tblJSONStats[tbl.Meta().ID] + if ok && js != nil { + return js, nil + } + js, err = h.DumpStatsToJSON(pair.DBName, tbl.Meta(), nil, true) + return js, err +} + +func getShowCreateTable(pair tableNamePair, zw *zip.Writer, ctx sessionctx.Context) error { + recordSets, err := ctx.(sqlexec.SQLExecutor).Execute(context.Background(), fmt.Sprintf("show create table `%v`.`%v`", pair.DBName, pair.TableName)) + if err != nil { + return err + } + sRows, err := resultSetToStringSlice(context.Background(), recordSets[0], false) + if err != nil { + return err + } + var fw io.Writer + if pair.IsView { + fw, err = zw.Create(fmt.Sprintf("view/%v.%v.view.txt", pair.DBName, pair.TableName)) + if err != nil { + return errors.AddStack(err) + } + if len(sRows) == 0 || len(sRows[0]) != 4 { + return fmt.Errorf("plan replayer: get create view %v.%v failed", pair.DBName, pair.TableName) + } + } else { + fw, err = zw.Create(fmt.Sprintf("schema/%v.%v.schema.txt", pair.DBName, pair.TableName)) + if err != nil { + return errors.AddStack(err) + } + if len(sRows) == 0 || len(sRows[0]) != 2 { + return fmt.Errorf("plan replayer: get create table %v.%v failed", pair.DBName, pair.TableName) + } + } + fmt.Fprintf(fw, "create database if not exists `%v`; use `%v`;", pair.DBName, pair.DBName) + fmt.Fprintf(fw, "%s", sRows[0][1]) + if len(recordSets) > 0 { + if err := recordSets[0].Close(); err != nil { + return err + } + } + return nil +} + +func resultSetToStringSlice(ctx context.Context, rs sqlexec.RecordSet, emptyAsNil bool) ([][]string, error) { + rows, err := getRows(ctx, rs) + if err != nil { + return nil, err + } + err = rs.Close() + if err != nil { + return nil, err + } + sRows := make([][]string, len(rows)) + for i, row := range rows { + iRow := make([]string, row.Len()) + for j := 0; j < row.Len(); j++ { + if row.IsNull(j) { + iRow[j] = "" + } else { + d := row.GetDatum(j, &rs.Fields()[j].Column.FieldType) + iRow[j], err = d.ToString() + if err != nil { + return nil, err + } + if len(iRow[j]) < 1 && emptyAsNil { + iRow[j] = "" + } + } + } + sRows[i] = iRow + } + return sRows, nil +} + +func getRows(ctx context.Context, rs sqlexec.RecordSet) ([]chunk.Row, error) { + if rs == nil { + return nil, nil + } + var rows []chunk.Row + req := rs.NewChunk(nil) + // Must reuse `req` for imitating server.(*clientConn).writeChunks + for { + err := rs.Next(ctx, req) + if err != nil { + return nil, err + } + if req.NumRows() == 0 { + break + } + + iter := chunk.NewIterator4Chunk(req.CopyConstruct()) + for row := iter.Begin(); row != iter.End(); row = iter.Next() { + rows = append(rows, row) + } + } + return rows, nil +} diff --git a/executor/plan_replayer.go b/executor/plan_replayer.go index c3bbbab174274..fec3de1867933 100644 --- a/executor/plan_replayer.go +++ b/executor/plan_replayer.go @@ -22,29 +22,21 @@ import ( "encoding/base64" "encoding/json" "fmt" - "io" "os" "path/filepath" - "strconv" "strings" "time" "github.com/BurntSushi/toml" "github.com/pingcap/errors" - "github.com/pingcap/tidb/bindinfo" - "github.com/pingcap/tidb/config" "github.com/pingcap/tidb/domain" "github.com/pingcap/tidb/infoschema" - "github.com/pingcap/tidb/parser" "github.com/pingcap/tidb/parser/ast" - "github.com/pingcap/tidb/parser/model" - "github.com/pingcap/tidb/privilege" "github.com/pingcap/tidb/sessionctx" "github.com/pingcap/tidb/sessionctx/variable" "github.com/pingcap/tidb/statistics/handle" "github.com/pingcap/tidb/util/chunk" "github.com/pingcap/tidb/util/logutil" - "github.com/pingcap/tidb/util/printer" "github.com/pingcap/tidb/util/sqlexec" "go.uber.org/zap" ) @@ -52,15 +44,6 @@ import ( var _ Executor = &PlanReplayerExec{} var _ Executor = &PlanReplayerLoadExec{} -const ( - configFile = "config.toml" - metaFile = "meta.txt" - variablesFile = "variables.toml" - tiFlashReplicasFile = "table_tiflash_replica.txt" - sessionBindingFile = "session_bindings.sql" - globalBindingFile = "global_bindings.sql" -) - // PlanReplayerExec represents a plan replayer executor. type PlanReplayerExec struct { baseExecutor @@ -78,79 +61,6 @@ type PlanReplayerDumpInfo struct { ctx sessionctx.Context } -type tableNamePair struct { - DBName string - TableName string - IsView bool -} - -type tableNameExtractor struct { - ctx context.Context - executor sqlexec.RestrictedSQLExecutor - is infoschema.InfoSchema - curDB model.CIStr - names map[tableNamePair]struct{} - cteNames map[string]struct{} - err error -} - -func (tne *tableNameExtractor) Enter(in ast.Node) (ast.Node, bool) { - if _, ok := in.(*ast.TableName); ok { - return in, true - } - return in, false -} - -func (tne *tableNameExtractor) Leave(in ast.Node) (ast.Node, bool) { - if tne.err != nil { - return in, true - } - if t, ok := in.(*ast.TableName); ok { - isView, err := tne.handleIsView(t) - if err != nil { - tne.err = err - return in, true - } - tp := tableNamePair{DBName: t.Schema.L, TableName: t.Name.L, IsView: isView} - if tp.DBName == "" { - tp.DBName = tne.curDB.L - } - if _, ok := tne.names[tp]; !ok { - tne.names[tp] = struct{}{} - } - } else if s, ok := in.(*ast.SelectStmt); ok { - if s.With != nil && len(s.With.CTEs) > 0 { - for _, cte := range s.With.CTEs { - tne.cteNames[cte.Name.L] = struct{}{} - } - } - } - return in, true -} - -func (tne *tableNameExtractor) handleIsView(t *ast.TableName) (bool, error) { - schema := t.Schema - if schema.L == "" { - schema = tne.curDB - } - table := t.Name - isView := tne.is.TableIsView(schema, table) - if !isView { - return false, nil - } - viewTbl, err := tne.is.TableByName(schema, table) - if err != nil { - return false, err - } - sql := viewTbl.Meta().View.SelectStmt - node, err := tne.executor.ParseWithParams(tne.ctx, sql) - if err != nil { - return false, err - } - node.Accept(tne) - return true, nil -} - // Next implements the Executor Next interface. func (e *PlanReplayerExec) Next(ctx context.Context, req *chunk.Chunk) error { req.GrowAndReset(e.maxChunkSize) @@ -234,7 +144,7 @@ func (e *PlanReplayerDumpInfo) dump(ctx context.Context) (err error) { ExecStmts: e.ExecStmts, Analyze: e.Analyze, } - err = DumpPlanReplayerInfo(ctx, e.ctx, task) + err = domain.DumpPlanReplayerInfo(ctx, e.ctx, task) if err != nil { return err } @@ -242,448 +152,6 @@ func (e *PlanReplayerDumpInfo) dump(ctx context.Context) (err error) { return nil } -// DumpPlanReplayerInfo will dump the information about sqls. -// The files will be organized into the following format: -/* - |-meta.txt - |-schema - | |-db1.table1.schema.txt - | |-db2.table2.schema.txt - | |-.... - |-view - | |-db1.view1.view.txt - | |-db2.view2.view.txt - | |-.... - |-stats - | |-stats1.json - | |-stats2.json - | |-.... - |-config.toml - |-table_tiflash_replica.txt - |-variables.toml - |-bindings.sql - |-sql - | |-sql1.sql - | |-sql2.sql - | |-.... - |_explain - |-explain1.txt - |-explain2.txt - |-.... -*/ -func DumpPlanReplayerInfo(ctx context.Context, sctx sessionctx.Context, - task *domain.PlanReplayerDumpTask) (err error) { - zf := task.Zf - fileName := task.FileName - sessionVars := task.SessionVars - execStmts := task.ExecStmts - zw := zip.NewWriter(zf) - records := generateRecords(task) - defer func() { - err = zw.Close() - if err != nil { - logutil.BgLogger().Error("Closing zip writer failed", zap.Error(err), zap.String("filename", fileName)) - } - err = zf.Close() - if err != nil { - logutil.BgLogger().Error("Closing zip file failed", zap.Error(err), zap.String("filename", fileName)) - for i, record := range records { - record.FailedReason = err.Error() - records[i] = record - } - } - domain.GetDomain(sctx).GetPlanReplayerHandle().InsertPlanReplayerStatus(ctx, records) - }() - // Dump config - if err = dumpConfig(zw); err != nil { - return err - } - - // Dump meta - if err = dumpMeta(zw); err != nil { - return err - } - // Retrieve current DB - dbName := model.NewCIStr(sessionVars.CurrentDB) - do := domain.GetDomain(sctx) - - // Retrieve all tables - pairs, err := extractTableNames(ctx, sctx, execStmts, dbName) - if err != nil { - return errors.AddStack(fmt.Errorf("plan replayer: invalid SQL text, err: %v", err)) - } - - // Dump Schema and View - if err = dumpSchemas(sctx, zw, pairs); err != nil { - return err - } - - // Dump tables tiflash replicas - if err = dumpTiFlashReplica(sctx, zw, pairs); err != nil { - return err - } - - // Dump stats - if err = dumpStats(zw, pairs, task.TblStats, do); err != nil { - return err - } - - // Dump variables - if err = dumpVariables(sctx, sessionVars, zw); err != nil { - return err - } - - // Dump sql - if err = dumpSQLs(execStmts, zw); err != nil { - return err - } - - // Dump session bindings - if len(task.SessionBindings) > 0 { - if err = dumpSessionBindRecords(sctx, task.SessionBindings, zw); err != nil { - return err - } - } else { - if err = dumpSessionBindings(sctx, zw); err != nil { - return err - } - } - - // Dump global bindings - if err = dumpGlobalBindings(sctx, zw); err != nil { - return err - } - - if len(task.EncodedPlan) > 0 { - return dumpEncodedPlan(sctx, zw, task.EncodedPlan) - } - // Dump explain - return dumpExplain(sctx, zw, execStmts, task.Analyze) -} - -func generateRecords(task *domain.PlanReplayerDumpTask) []domain.PlanReplayerStatusRecord { - records := make([]domain.PlanReplayerStatusRecord, 0) - if len(task.ExecStmts) > 0 { - for _, execStmt := range task.ExecStmts { - records = append(records, domain.PlanReplayerStatusRecord{ - OriginSQL: execStmt.Text(), - Token: task.FileName, - Internal: false, - }) - } - } - return records -} - -func dumpConfig(zw *zip.Writer) error { - cf, err := zw.Create(configFile) - if err != nil { - return errors.AddStack(err) - } - if err := toml.NewEncoder(cf).Encode(config.GetGlobalConfig()); err != nil { - return errors.AddStack(err) - } - return nil -} - -func dumpMeta(zw *zip.Writer) error { - mt, err := zw.Create(metaFile) - if err != nil { - return errors.AddStack(err) - } - _, err = mt.Write([]byte(printer.GetTiDBInfo())) - if err != nil { - return errors.AddStack(err) - } - return nil -} - -func dumpTiFlashReplica(ctx sessionctx.Context, zw *zip.Writer, pairs map[tableNamePair]struct{}) error { - bf, err := zw.Create(tiFlashReplicasFile) - if err != nil { - return errors.AddStack(err) - } - is := domain.GetDomain(ctx).InfoSchema() - for pair := range pairs { - dbName := model.NewCIStr(pair.DBName) - tableName := model.NewCIStr(pair.TableName) - t, err := is.TableByName(dbName, tableName) - if err != nil { - logutil.BgLogger().Warn("failed to find table info", zap.Error(err), - zap.String("dbName", dbName.L), zap.String("tableName", tableName.L)) - continue - } - if t.Meta().TiFlashReplica != nil && t.Meta().TiFlashReplica.Count > 0 { - row := []string{ - pair.DBName, pair.TableName, strconv.FormatUint(t.Meta().TiFlashReplica.Count, 10), - } - fmt.Fprintf(bf, "%s\n", strings.Join(row, "\t")) - } - } - return nil -} - -func dumpSchemas(ctx sessionctx.Context, zw *zip.Writer, pairs map[tableNamePair]struct{}) error { - for pair := range pairs { - err := getShowCreateTable(pair, zw, ctx) - if err != nil { - return err - } - } - return nil -} - -func dumpStats(zw *zip.Writer, pairs map[tableNamePair]struct{}, tblJSONStats map[int64]*handle.JSONTable, do *domain.Domain) error { - for pair := range pairs { - if pair.IsView { - continue - } - jsonTbl, err := getStatsForTable(do, tblJSONStats, pair) - if err != nil { - return err - } - statsFw, err := zw.Create(fmt.Sprintf("stats/%v.%v.json", pair.DBName, pair.TableName)) - if err != nil { - return errors.AddStack(err) - } - data, err := json.Marshal(jsonTbl) - if err != nil { - return errors.AddStack(err) - } - _, err = statsFw.Write(data) - if err != nil { - return errors.AddStack(err) - } - } - return nil -} - -func dumpSQLs(execStmts []ast.StmtNode, zw *zip.Writer) error { - for i, stmtExec := range execStmts { - zf, err := zw.Create(fmt.Sprintf("sql/sql%v.sql", i)) - if err != nil { - return err - } - _, err = zf.Write([]byte(stmtExec.Text())) - if err != nil { - return err - } - } - return nil -} - -func dumpVariables(sctx sessionctx.Context, sessionVars *variable.SessionVars, zw *zip.Writer) error { - varMap := make(map[string]string) - for _, v := range variable.GetSysVars() { - if v.IsNoop && !variable.EnableNoopVariables.Load() { - continue - } - if infoschema.SysVarHiddenForSem(sctx, v.Name) { - continue - } - value, err := sessionVars.GetSessionOrGlobalSystemVar(context.Background(), v.Name) - if err != nil { - return errors.Trace(err) - } - varMap[v.Name] = value - } - vf, err := zw.Create(variablesFile) - if err != nil { - return errors.AddStack(err) - } - if err := toml.NewEncoder(vf).Encode(varMap); err != nil { - return errors.AddStack(err) - } - return nil -} - -func dumpSessionBindRecords(ctx sessionctx.Context, records []*bindinfo.BindRecord, zw *zip.Writer) error { - sRows := make([][]string, 0) - is := domain.GetDomain(ctx).InfoSchema() - parser := parser.New() - for _, bindData := range records { - for _, hint := range bindData.Bindings { - stmt, err := parser.ParseOneStmt(hint.BindSQL, hint.Charset, hint.Collation) - if err != nil { - return err - } - checker := visibleChecker{ - defaultDB: bindData.Db, - ctx: ctx, - is: is, - manager: privilege.GetPrivilegeManager(ctx), - ok: true, - } - stmt.Accept(&checker) - if !checker.ok { - continue - } - sRows = append(sRows, []string{ - bindData.OriginalSQL, - hint.BindSQL, - bindData.Db, - hint.Status, - hint.CreateTime.String(), - hint.UpdateTime.String(), - hint.Charset, - hint.Collation, - hint.Source, - }) - } - } - bf, err := zw.Create(sessionBindingFile) - if err != nil { - return errors.AddStack(err) - } - for _, row := range sRows { - fmt.Fprintf(bf, "%s\n", strings.Join(row, "\t")) - } - return nil -} - -func dumpSessionBindings(ctx sessionctx.Context, zw *zip.Writer) error { - recordSets, err := ctx.(sqlexec.SQLExecutor).Execute(context.Background(), "show bindings") - if err != nil { - return err - } - sRows, err := resultSetToStringSlice(context.Background(), recordSets[0], true) - if err != nil { - return err - } - bf, err := zw.Create(sessionBindingFile) - if err != nil { - return errors.AddStack(err) - } - for _, row := range sRows { - fmt.Fprintf(bf, "%s\n", strings.Join(row, "\t")) - } - if len(recordSets) > 0 { - if err := recordSets[0].Close(); err != nil { - return err - } - } - return nil -} - -func dumpGlobalBindings(ctx sessionctx.Context, zw *zip.Writer) error { - recordSets, err := ctx.(sqlexec.SQLExecutor).Execute(context.Background(), "show global bindings") - if err != nil { - return err - } - sRows, err := resultSetToStringSlice(context.Background(), recordSets[0], false) - if err != nil { - return err - } - bf, err := zw.Create(globalBindingFile) - if err != nil { - return errors.AddStack(err) - } - for _, row := range sRows { - fmt.Fprintf(bf, "%s\n", strings.Join(row, "\t")) - } - if len(recordSets) > 0 { - if err := recordSets[0].Close(); err != nil { - return err - } - } - return nil -} - -func dumpEncodedPlan(ctx sessionctx.Context, zw *zip.Writer, encodedPlan string) error { - var recordSets []sqlexec.RecordSet - var err error - recordSets, err = ctx.(sqlexec.SQLExecutor).Execute(context.Background(), fmt.Sprintf("select tidb_decode_plan('%s')", encodedPlan)) - if err != nil { - return err - } - sRows, err := resultSetToStringSlice(context.Background(), recordSets[0], false) - if err != nil { - return err - } - fw, err := zw.Create("explain/sql.txt") - if err != nil { - return errors.AddStack(err) - } - for _, row := range sRows { - fmt.Fprintf(fw, "%s\n", strings.Join(row, "\t")) - } - if len(recordSets) > 0 { - if err := recordSets[0].Close(); err != nil { - return err - } - } - return nil -} - -func dumpExplain(ctx sessionctx.Context, zw *zip.Writer, execStmts []ast.StmtNode, isAnalyze bool) error { - for i, stmtExec := range execStmts { - sql := stmtExec.Text() - var recordSets []sqlexec.RecordSet - var err error - if isAnalyze { - // Explain analyze - recordSets, err = ctx.(sqlexec.SQLExecutor).Execute(context.Background(), fmt.Sprintf("explain analyze %s", sql)) - if err != nil { - return err - } - } else { - // Explain - recordSets, err = ctx.(sqlexec.SQLExecutor).Execute(context.Background(), fmt.Sprintf("explain %s", sql)) - if err != nil { - return err - } - } - sRows, err := resultSetToStringSlice(context.Background(), recordSets[0], false) - if err != nil { - return err - } - fw, err := zw.Create(fmt.Sprintf("explain/sql%v.txt", i)) - if err != nil { - return errors.AddStack(err) - } - for _, row := range sRows { - fmt.Fprintf(fw, "%s\n", strings.Join(row, "\t")) - } - if len(recordSets) > 0 { - if err := recordSets[0].Close(); err != nil { - return err - } - } - } - return nil -} - -func extractTableNames(ctx context.Context, sctx sessionctx.Context, - ExecStmts []ast.StmtNode, curDB model.CIStr) (map[tableNamePair]struct{}, error) { - tableExtractor := &tableNameExtractor{ - ctx: ctx, - executor: sctx.(sqlexec.RestrictedSQLExecutor), - is: domain.GetDomain(sctx).InfoSchema(), - curDB: curDB, - names: make(map[tableNamePair]struct{}), - cteNames: make(map[string]struct{}), - } - for _, execStmt := range ExecStmts { - execStmt.Accept(tableExtractor) - } - if tableExtractor.err != nil { - return nil, tableExtractor.err - } - r := make(map[tableNamePair]struct{}) - for tablePair := range tableExtractor.names { - if tablePair.IsView { - r[tablePair] = struct{}{} - continue - } - // remove cte in table names - _, ok := tableExtractor.cteNames[tablePair.TableName] - if !ok { - r[tablePair] = struct{}{} - } - } - return r, nil -} - func (e *PlanReplayerExec) prepare() error { val := e.ctx.Value(PlanReplayerDumpVarKey) if val != nil { @@ -712,113 +180,6 @@ func (e *PlanReplayerDumpInfo) DumpSQLsFromFile(ctx context.Context, b []byte) e return e.dump(ctx) } -func getStatsForTable(do *domain.Domain, tblJSONStats map[int64]*handle.JSONTable, pair tableNamePair) (*handle.JSONTable, error) { - is := do.InfoSchema() - h := do.StatsHandle() - tbl, err := is.TableByName(model.NewCIStr(pair.DBName), model.NewCIStr(pair.TableName)) - if err != nil { - return nil, err - } - js, ok := tblJSONStats[tbl.Meta().ID] - if ok && js != nil { - return js, nil - } - js, err = h.DumpStatsToJSON(pair.DBName, tbl.Meta(), nil, true) - return js, err -} - -func getShowCreateTable(pair tableNamePair, zw *zip.Writer, ctx sessionctx.Context) error { - recordSets, err := ctx.(sqlexec.SQLExecutor).Execute(context.Background(), fmt.Sprintf("show create table `%v`.`%v`", pair.DBName, pair.TableName)) - if err != nil { - return err - } - sRows, err := resultSetToStringSlice(context.Background(), recordSets[0], false) - if err != nil { - return err - } - var fw io.Writer - if pair.IsView { - fw, err = zw.Create(fmt.Sprintf("view/%v.%v.view.txt", pair.DBName, pair.TableName)) - if err != nil { - return errors.AddStack(err) - } - if len(sRows) == 0 || len(sRows[0]) != 4 { - return fmt.Errorf("plan replayer: get create view %v.%v failed", pair.DBName, pair.TableName) - } - } else { - fw, err = zw.Create(fmt.Sprintf("schema/%v.%v.schema.txt", pair.DBName, pair.TableName)) - if err != nil { - return errors.AddStack(err) - } - if len(sRows) == 0 || len(sRows[0]) != 2 { - return fmt.Errorf("plan replayer: get create table %v.%v failed", pair.DBName, pair.TableName) - } - } - fmt.Fprintf(fw, "create database if not exists `%v`; use `%v`;", pair.DBName, pair.DBName) - fmt.Fprintf(fw, "%s", sRows[0][1]) - if len(recordSets) > 0 { - if err := recordSets[0].Close(); err != nil { - return err - } - } - return nil -} - -func resultSetToStringSlice(ctx context.Context, rs sqlexec.RecordSet, emptyAsNil bool) ([][]string, error) { - rows, err := getRows(ctx, rs) - if err != nil { - return nil, err - } - err = rs.Close() - if err != nil { - return nil, err - } - sRows := make([][]string, len(rows)) - for i, row := range rows { - iRow := make([]string, row.Len()) - for j := 0; j < row.Len(); j++ { - if row.IsNull(j) { - iRow[j] = "" - } else { - d := row.GetDatum(j, &rs.Fields()[j].Column.FieldType) - iRow[j], err = d.ToString() - if err != nil { - return nil, err - } - if len(iRow[j]) < 1 && emptyAsNil { - iRow[j] = "" - } - } - } - sRows[i] = iRow - } - return sRows, nil -} - -func getRows(ctx context.Context, rs sqlexec.RecordSet) ([]chunk.Row, error) { - if rs == nil { - return nil, nil - } - var rows []chunk.Row - req := rs.NewChunk(nil) - // Must reuse `req` for imitating server.(*clientConn).writeChunks - for { - err := rs.Next(ctx, req) - if err != nil { - return nil, err - } - if req.NumRows() == 0 { - break - } - - iter := chunk.NewIterator4Chunk(req.CopyConstruct()) - for row := iter.Begin(); row != iter.End(); row = iter.Next() { - rows = append(rows, row) - } - } - return rows, nil -} - // PlanReplayerLoadExec represents a plan replayer load executor. type PlanReplayerLoadExec struct { baseExecutor @@ -866,7 +227,7 @@ func (e *PlanReplayerLoadExec) Next(ctx context.Context, req *chunk.Chunk) error func loadSetTiFlashReplica(ctx sessionctx.Context, z *zip.Reader) error { for _, zipFile := range z.File { - if strings.Compare(zipFile.Name, tiFlashReplicasFile) == 0 { + if strings.Compare(zipFile.Name, domain.PlanReplayerTiFlashReplicasFile) == 0 { v, err := zipFile.Open() if err != nil { return errors.AddStack(err) @@ -904,12 +265,12 @@ func loadSetTiFlashReplica(ctx sessionctx.Context, z *zip.Reader) error { func loadAllBindings(ctx sessionctx.Context, z *zip.Reader) error { for _, f := range z.File { - if strings.Compare(f.Name, sessionBindingFile) == 0 { + if strings.Compare(f.Name, domain.PlanReplayerSessionBindingFile) == 0 { err := loadBindings(ctx, f, true) if err != nil { return err } - } else if strings.Compare(f.Name, globalBindingFile) == 0 { + } else if strings.Compare(f.Name, domain.PlanReplayerGlobalBindingFile) == 0 { err := loadBindings(ctx, f, false) if err != nil { return err @@ -963,7 +324,7 @@ func loadBindings(ctx sessionctx.Context, f *zip.File, isSession bool) error { func loadVariables(ctx sessionctx.Context, z *zip.Reader) error { unLoadVars := make([]string, 0) for _, zipFile := range z.File { - if strings.Compare(zipFile.Name, variablesFile) == 0 { + if strings.Compare(zipFile.Name, domain.PlanReplayerVariablesFile) == 0 { varMap := make(map[string]string) v, err := zipFile.Open() if err != nil { diff --git a/session/session.go b/session/session.go index d2f4afd10b679..9a00827b5e973 100644 --- a/session/session.go +++ b/session/session.go @@ -2895,7 +2895,7 @@ func BootstrapSession(store kv.Storage) (*domain.Domain, error) { analyzeConcurrencyQuota := int(config.GetGlobalConfig().Performance.AnalyzePartitionConcurrencyQuota) concurrency := int(config.GetGlobalConfig().Performance.StatsLoadConcurrency) - ses, err := createSessions(store, 7) + ses, err := createSessions(store, 8) if err != nil { return nil, err } @@ -2973,6 +2973,7 @@ func BootstrapSession(store kv.Storage) (*domain.Domain, error) { dom.SetupPlanReplayerHandle(ses[6]) dom.StartPlanReplayerHandle() // setup dumpFileGcChecker + dom.SetupDumpFileGCChecker(ses[7]) dom.DumpFileGcCheckerLoop() // A sub context for update table stats, and other contexts for concurrent stats loading. From 22a955db9a0f788c642c81a28c170534ccf54b6e Mon Sep 17 00:00:00 2001 From: yisaer Date: Fri, 11 Nov 2022 15:37:18 +0800 Subject: [PATCH 13/20] support capture task Signed-off-by: yisaer --- domain/domain.go | 13 ++- domain/plan_replayer.go | 158 ++++++++++++++++++++++++++++------- domain/plan_replayer_dump.go | 9 +- executor/adapter.go | 4 + executor/compiler.go | 36 ++++++++ executor/plan_replayer.go | 42 +--------- session/session.go | 6 +- 7 files changed, 189 insertions(+), 79 deletions(-) diff --git a/domain/domain.go b/domain/domain.go index 39d44c569674e..004103fc5ec2d 100644 --- a/domain/domain.go +++ b/domain/domain.go @@ -1532,10 +1532,17 @@ func (do *Domain) TelemetryRotateSubWindowLoop(ctx sessionctx.Context) { } // SetupPlanReplayerHandle setup plan replayer handle -func (do *Domain) SetupPlanReplayerHandle(ctx sessionctx.Context) { +func (do *Domain) SetupPlanReplayerHandle(collectorSctx, dumperSctx sessionctx.Context) { + ctx := kv.WithInternalSourceType(context.Background(), kv.InternalTxnStats) do.planReplayerHandle = &planReplayerHandle{} do.planReplayerHandle.planReplayerTaskCollectorHandle = &planReplayerTaskCollectorHandle{ - sctx: ctx, + ctx: ctx, + sctx: collectorSctx, + } + do.planReplayerHandle.planReplayerTaskDumpHandle = &planReplayerTaskDumpHandle{ + ctx: ctx, + sctx: dumperSctx, + taskCH: make(chan *PlanReplayerDumpTask, 16), } } @@ -1569,7 +1576,7 @@ func (do *Domain) StartPlanReplayerHandle() { case <-do.exit: return case <-tikcer.C: - err := do.planReplayerHandle.CollectPlanReplayerTask(context.Background()) + err := do.planReplayerHandle.CollectPlanReplayerTask() if err != nil { logutil.BgLogger().Warn("plan replayer handle collect tasks failed", zap.Error(err)) } diff --git a/domain/plan_replayer.go b/domain/plan_replayer.go index efc2e8ad21429..82ce3ba56a4f3 100644 --- a/domain/plan_replayer.go +++ b/domain/plan_replayer.go @@ -16,8 +16,12 @@ package domain import ( "context" + "encoding/base64" "fmt" + "github.com/pingcap/tidb/sessionctx/stmtctx" + "github.com/pingcap/tidb/statistics" "io/ioutil" + "math/rand" "os" "path/filepath" "strconv" @@ -120,18 +124,6 @@ func (p *dumpFileGcChecker) gcDumpFilesByPath(path string, t time.Duration) { } } -type planReplayerHandle struct { - *planReplayerTaskCollectorHandle -} - -type planReplayerTaskCollectorHandle struct { - taskMu struct { - sync.RWMutex - tasks map[PlanReplayerTaskKey]struct{} - } - sctx sessionctx.Context -} - func deletePlanReplayerStatus(ctx context.Context, sctx sessionctx.Context, token string) { ctx1 := kv.WithInternalSourceType(ctx, kv.InternalTxnStats) exec := sctx.(sqlexec.SQLExecutor) @@ -153,17 +145,15 @@ func insertPlanReplayerStatus(ctx context.Context, sctx sessionctx.Context, reco instance = fmt.Sprintf("%s:%d", serverInfo.IP, serverInfo.Port) } for _, record := range records { - if !record.Internal { - if len(record.FailedReason) > 0 { - insertExternalPlanReplayerErrorStatusRecord(ctx1, sctx, instance, record) - } else { - insertExternalPlanReplayerSuccessStatusRecord(ctx1, sctx, instance, record) - } + if len(record.FailedReason) > 0 { + insertPlanReplayerErrorStatusRecord(ctx1, sctx, instance, record) + } else { + insertPlanReplayerSuccessStatusRecord(ctx1, sctx, instance, record) } } } -func insertExternalPlanReplayerErrorStatusRecord(ctx context.Context, sctx sessionctx.Context, instance string, record PlanReplayerStatusRecord) { +func insertPlanReplayerErrorStatusRecord(ctx context.Context, sctx sessionctx.Context, instance string, record PlanReplayerStatusRecord) { exec := sctx.(sqlexec.SQLExecutor) _, err := exec.ExecuteInternal(ctx, fmt.Sprintf( "insert into mysql.plan_replayer_status (origin_sql, fail_reason, instance) values ('%s','%s','%s')", @@ -174,7 +164,7 @@ func insertExternalPlanReplayerErrorStatusRecord(ctx context.Context, sctx sessi } } -func insertExternalPlanReplayerSuccessStatusRecord(ctx context.Context, sctx sessionctx.Context, instance string, record PlanReplayerStatusRecord) { +func insertPlanReplayerSuccessStatusRecord(ctx context.Context, sctx sessionctx.Context, instance string, record PlanReplayerStatusRecord) { exec := sctx.(sqlexec.SQLExecutor) _, err := exec.ExecuteInternal(ctx, fmt.Sprintf( "insert into mysql.plan_replayer_status (origin_sql, token, instance) values ('%s','%s','%s')", @@ -185,8 +175,23 @@ func insertExternalPlanReplayerSuccessStatusRecord(ctx context.Context, sctx ses } } +type planReplayerHandle struct { + *planReplayerTaskCollectorHandle + *planReplayerTaskDumpHandle +} + +type planReplayerTaskCollectorHandle struct { + ctx context.Context + taskMu struct { + sync.RWMutex + tasks map[PlanReplayerTaskKey]struct{} + } + sctx sessionctx.Context +} + // CollectPlanReplayerTask collects all unhandled plan replayer task -func (h *planReplayerTaskCollectorHandle) CollectPlanReplayerTask(ctx context.Context) error { +func (h *planReplayerTaskCollectorHandle) CollectPlanReplayerTask() error { + ctx := h.ctx ctx1 := kv.WithInternalSourceType(ctx, kv.InternalTxnStats) allKeys, err := h.collectAllPlanReplayerTask(ctx1) if err != nil { @@ -245,16 +250,71 @@ func (h *planReplayerTaskCollectorHandle) collectAllPlanReplayerTask(ctx context for _, row := range rows { sqlDigest, planDigest := row.GetString(0), row.GetString(1) allKeys = append(allKeys, PlanReplayerTaskKey{ - sqlDigest: sqlDigest, - planDigest: planDigest, + SqlDigest: sqlDigest, + PlanDigest: planDigest, }) } return allKeys, nil } +type planReplayerTaskDumpHandle struct { + ctx context.Context + sctx sessionctx.Context + taskCH chan *PlanReplayerDumpTask +} + +func (h *planReplayerTaskDumpHandle) DrainTask() *PlanReplayerDumpTask { + select { + case task := <-h.taskCH: + return task + default: + } + return nil +} + +func (h *planReplayerTaskDumpHandle) HandlePlanReplayerDumpTask(task *PlanReplayerDumpTask) { + file, fileName, err := GeneratePlanReplayerFile() + if err != nil { + // TODO + } + task.Zf = file + task.FileName = fileName + task.EncodedPlan, _ = task.EncodePlan(task.SessionVars.StmtCtx, false) + jsStats := make(map[int64]*handle.JSONTable) + is := GetDomain(h.sctx).InfoSchema() + for tblID, stat := range task.TblStats { + tbl, ok := is.TableByID(tblID) + if !ok { + return + } + schema, ok := is.SchemaByTable(tbl.Meta()) + if !ok { + return + } + r, err := handle.GenJSONTableFromStats(schema.Name.String(), tbl.Meta(), stat.(*statistics.Table)) + if err != nil { + // TODO + return + } + jsStats[tblID] = r + } + err = DumpPlanReplayerInfo(h.ctx, h.sctx, task) + if err != nil { + // TODO + } +} + +func (h *planReplayerTaskDumpHandle) SendTask(task *PlanReplayerDumpTask) { + select { + case h.taskCH <- task: + default: + // TODO + } +} + func checkUnHandledReplayerTask(ctx context.Context, sctx sessionctx.Context, task PlanReplayerTaskKey) (bool, error) { exec := sctx.(sqlexec.SQLExecutor) - rs, err := exec.ExecuteInternal(ctx, fmt.Sprintf("select * from mysql.plan_replayer_status where sql_digest = '%v' and plan_digest = '%v' and fail_reason is null", task.sqlDigest, task.planDigest)) + rs, err := exec.ExecuteInternal(ctx, fmt.Sprintf("select * from mysql.plan_replayer_status where sql_digest = '%v' and plan_digest = '%v' and fail_reason is null", task.SqlDigest, task.PlanDigest)) if err != nil { return false, err } @@ -274,7 +334,8 @@ func checkUnHandledReplayerTask(ctx context.Context, sctx sessionctx.Context, ta // PlanReplayerStatusRecord indicates record in mysql.plan_replayer_status type PlanReplayerStatusRecord struct { - Internal bool + SQLDigest string + PlanDigest string OriginSQL string Token string FailedReason string @@ -282,18 +343,55 @@ type PlanReplayerStatusRecord struct { // PlanReplayerTaskKey indicates key of a plan replayer task type PlanReplayerTaskKey struct { - sqlDigest string - planDigest string + SqlDigest string + PlanDigest string } // PlanReplayerDumpTask wrap the params for plan replayer dump type PlanReplayerDumpTask struct { + PlanReplayerTaskKey + + EncodePlan func(*stmtctx.StatementContext, bool) (string, string) + TblStats map[int64]interface{} + SessionBindings []*bindinfo.BindRecord EncodedPlan string - FileName string - Zf *os.File SessionVars *variable.SessionVars - TblStats map[int64]*handle.JSONTable + JSONTblStats map[int64]*handle.JSONTable ExecStmts []ast.StmtNode Analyze bool + + FileName string + Zf *os.File +} + +// GeneratePlanReplayerFile generates plan replayer file +func GeneratePlanReplayerFile() (*os.File, string, error) { + path := GetPlanReplayerDirName() + err := os.MkdirAll(path, os.ModePerm) + if err != nil { + return nil, "", errors.AddStack(err) + } + fileName, err := generatePlanReplayerFileName() + if err != nil { + return nil, "", errors.AddStack(err) + } + zf, err := os.Create(filepath.Join(path, fileName)) + if err != nil { + return nil, "", errors.AddStack(err) + } + return zf, fileName, err +} + +func generatePlanReplayerFileName() (string, error) { + // Generate key and create zip file + time := time.Now().UnixNano() + b := make([]byte, 16) + //nolint: gosec + _, err := rand.Read(b) + if err != nil { + return "", err + } + key := base64.URLEncoding.EncodeToString(b) + return fmt.Sprintf("replayer_%v_%v.zip", key, time), nil } diff --git a/domain/plan_replayer_dump.go b/domain/plan_replayer_dump.go index 79fde5676023b..53226d9873e18 100644 --- a/domain/plan_replayer_dump.go +++ b/domain/plan_replayer_dump.go @@ -204,7 +204,7 @@ func DumpPlanReplayerInfo(ctx context.Context, sctx sessionctx.Context, } // Dump stats - if err = dumpStats(zw, pairs, task.TblStats, do); err != nil { + if err = dumpStats(zw, pairs, task.JSONTblStats, do); err != nil { return err } @@ -246,9 +246,10 @@ func generateRecords(task *PlanReplayerDumpTask) []PlanReplayerStatusRecord { if len(task.ExecStmts) > 0 { for _, execStmt := range task.ExecStmts { records = append(records, PlanReplayerStatusRecord{ - OriginSQL: execStmt.Text(), - Token: task.FileName, - Internal: false, + SQLDigest: task.SqlDigest, + PlanDigest: task.PlanDigest, + OriginSQL: execStmt.Text(), + Token: task.FileName, }) } } diff --git a/executor/adapter.go b/executor/adapter.go index db9fbbaa929e0..ec328fd0efb7d 100644 --- a/executor/adapter.go +++ b/executor/adapter.go @@ -1629,6 +1629,10 @@ func getPlanDigest(stmtCtx *stmtctx.StatementContext) (string, *parser.Digest) { return normalized, planDigest } +func GetEncodedPlan(stmtCtx *stmtctx.StatementContext, genHint bool) (encodedPlan, hintStr string) { + return getEncodedPlan(stmtCtx, genHint) +} + // getEncodedPlan gets the encoded plan, and generates the hint string if indicated. func getEncodedPlan(stmtCtx *stmtctx.StatementContext, genHint bool) (encodedPlan, hintStr string) { var hintSet bool diff --git a/executor/compiler.go b/executor/compiler.go index 241b15874e1e2..7bfbb54bf02d4 100644 --- a/executor/compiler.go +++ b/executor/compiler.go @@ -16,6 +16,9 @@ package executor import ( "context" + "github.com/pingcap/tidb/bindinfo" + "github.com/pingcap/tidb/domain" + "github.com/pingcap/tidb/sessionctx/variable" "strings" "github.com/opentracing/opentracing-go" @@ -154,9 +157,42 @@ func (c *Compiler) Compile(ctx context.Context, stmtNode ast.StmtNode) (_ *ExecS } } } + if variable.EnablePlanReplayerCapture.Load() { + checkPlanReplayerCaptureTask(c.Ctx, stmtNode) + } + return stmt, nil } +func checkPlanReplayerCaptureTask(sctx sessionctx.Context, stmtNode ast.StmtNode) { + tasks := domain.GetDomain(sctx).GetPlanReplayerHandle().GetTasks() + _, sqlDigest := sctx.GetSessionVars().StmtCtx.SQLDigest() + _, planDigest := sctx.GetSessionVars().StmtCtx.GetPlanDigest() + for _, task := range tasks { + if task.SqlDigest == sqlDigest.String() && task.PlanDigest == planDigest.String() { + sendPlanReplayerDumpTask(sqlDigest.String(), planDigest.String(), sctx, stmtNode) + } + } +} + +func sendPlanReplayerDumpTask(sqlDigest, planDigest string, sctx sessionctx.Context, stmtNode ast.StmtNode) { + stmtCtx := sctx.GetSessionVars().StmtCtx + handle := sctx.Value(bindinfo.SessionBindInfoKeyType).(*bindinfo.SessionHandle) + dumpTask := &domain.PlanReplayerDumpTask{ + PlanReplayerTaskKey: domain.PlanReplayerTaskKey{ + SqlDigest: sqlDigest, + PlanDigest: planDigest, + }, + EncodePlan: GetEncodedPlan, + TblStats: stmtCtx.TableStats, + SessionBindings: handle.GetAllBindRecord(), + SessionVars: sctx.GetSessionVars(), + ExecStmts: []ast.StmtNode{stmtNode}, + Analyze: false, + } + domain.GetDomain(sctx).GetPlanReplayerHandle().SendTask(dumpTask) +} + // needLowerPriority checks whether it's needed to lower the execution priority // of a query. // If the estimated output row count of any operator in the physical plan tree diff --git a/executor/plan_replayer.go b/executor/plan_replayer.go index fec3de1867933..1a66064777e5a 100644 --- a/executor/plan_replayer.go +++ b/executor/plan_replayer.go @@ -18,15 +18,8 @@ import ( "archive/zip" "bytes" "context" - "crypto/rand" - "encoding/base64" "encoding/json" "fmt" - "os" - "path/filepath" - "strings" - "time" - "github.com/BurntSushi/toml" "github.com/pingcap/errors" "github.com/pingcap/tidb/domain" @@ -39,6 +32,8 @@ import ( "github.com/pingcap/tidb/util/logutil" "github.com/pingcap/tidb/util/sqlexec" "go.uber.org/zap" + "os" + "strings" ) var _ Executor = &PlanReplayerExec{} @@ -95,44 +90,13 @@ func (e *PlanReplayerExec) Next(ctx context.Context, req *chunk.Chunk) error { func (e *PlanReplayerExec) createFile() error { var err error - e.DumpInfo.File, e.DumpInfo.FileName, err = GeneratePlanReplayerFile() + e.DumpInfo.File, e.DumpInfo.FileName, err = domain.GeneratePlanReplayerFile() if err != nil { return err } return nil } -// GeneratePlanReplayerFile generates plan replayer file -func GeneratePlanReplayerFile() (*os.File, string, error) { - path := domain.GetPlanReplayerDirName() - err := os.MkdirAll(path, os.ModePerm) - if err != nil { - return nil, "", errors.AddStack(err) - } - fileName, err := generatePlanReplayerFileName() - if err != nil { - return nil, "", errors.AddStack(err) - } - zf, err := os.Create(filepath.Join(path, fileName)) - if err != nil { - return nil, "", errors.AddStack(err) - } - return zf, fileName, err -} - -func generatePlanReplayerFileName() (string, error) { - // Generate key and create zip file - time := time.Now().UnixNano() - b := make([]byte, 16) - //nolint: gosec - _, err := rand.Read(b) - if err != nil { - return "", err - } - key := base64.URLEncoding.EncodeToString(b) - return fmt.Sprintf("replayer_%v_%v.zip", key, time), nil -} - func (e *PlanReplayerDumpInfo) dump(ctx context.Context) (err error) { fileName := e.FileName zf := e.File diff --git a/session/session.go b/session/session.go index 9a00827b5e973..ff4f81d6f36fa 100644 --- a/session/session.go +++ b/session/session.go @@ -2895,7 +2895,7 @@ func BootstrapSession(store kv.Storage) (*domain.Domain, error) { analyzeConcurrencyQuota := int(config.GetGlobalConfig().Performance.AnalyzePartitionConcurrencyQuota) concurrency := int(config.GetGlobalConfig().Performance.StatsLoadConcurrency) - ses, err := createSessions(store, 8) + ses, err := createSessions(store, 9) if err != nil { return nil, err } @@ -2970,10 +2970,10 @@ func BootstrapSession(store kv.Storage) (*domain.Domain, error) { } // setup plan replayer handle - dom.SetupPlanReplayerHandle(ses[6]) + dom.SetupPlanReplayerHandle(ses[6], ses[7]) dom.StartPlanReplayerHandle() // setup dumpFileGcChecker - dom.SetupDumpFileGCChecker(ses[7]) + dom.SetupDumpFileGCChecker(ses[8]) dom.DumpFileGcCheckerLoop() // A sub context for update table stats, and other contexts for concurrent stats loading. From d013a4a0dd40088f753fd12672bc451edbcedbda Mon Sep 17 00:00:00 2001 From: yisaer Date: Mon, 14 Nov 2022 11:59:36 +0800 Subject: [PATCH 14/20] support Signed-off-by: yisaer --- domain/domain.go | 23 +++++++-- domain/plan_replayer.go | 76 ++++++++++++++++++++--------- domain/plan_replayer_handle_test.go | 13 +++-- executor/adapter.go | 1 + executor/compiler.go | 9 ++-- 5 files changed, 88 insertions(+), 34 deletions(-) diff --git a/domain/domain.go b/domain/domain.go index 7f062d07c6222..1dcc8ac201148 100644 --- a/domain/domain.go +++ b/domain/domain.go @@ -1563,14 +1563,14 @@ func (do *Domain) StartPlanReplayerHandle() { if planReplayerHandleLease < 1 { return } - do.wg.Add(1) + do.wg.Add(2) go func() { tikcer := time.NewTicker(planReplayerHandleLease) defer func() { tikcer.Stop() do.wg.Done() - logutil.BgLogger().Info("PlanReplayerHandle exited.") - util.Recover(metrics.LabelDomain, "PlanReplayerHandle", nil, false) + logutil.BgLogger().Info("PlanReplayerTaskCollectHandle exited.") + util.Recover(metrics.LabelDomain, "PlanReplayerTaskCollectHandle", nil, false) }() for { select { @@ -1581,6 +1581,23 @@ func (do *Domain) StartPlanReplayerHandle() { if err != nil { logutil.BgLogger().Warn("plan replayer handle collect tasks failed", zap.Error(err)) } + case task := <-do.planReplayerHandle.planReplayerTaskDumpHandle.taskCH: + do.planReplayerHandle.HandlePlanReplayerDumpTask(task) + } + } + }() + go func() { + defer func() { + do.wg.Done() + logutil.BgLogger().Info("PlanReplayerTaskDumpHandle exited.") + util.Recover(metrics.LabelDomain, "PlanReplayerTaskDumpHandle", nil, false) + }() + for { + select { + case <-do.exit: + return + case task := <-do.planReplayerHandle.planReplayerTaskDumpHandle.taskCH: + do.planReplayerHandle.HandlePlanReplayerDumpTask(task) } } }() diff --git a/domain/plan_replayer.go b/domain/plan_replayer.go index b65af88ec6ab9..899a8a40e160d 100644 --- a/domain/plan_replayer.go +++ b/domain/plan_replayer.go @@ -18,8 +18,6 @@ import ( "context" "encoding/base64" "fmt" - "github.com/pingcap/tidb/sessionctx/stmtctx" - "github.com/pingcap/tidb/statistics" "io/ioutil" "math/rand" "os" @@ -37,7 +35,9 @@ import ( "github.com/pingcap/tidb/parser/ast" "github.com/pingcap/tidb/parser/terror" "github.com/pingcap/tidb/sessionctx" + "github.com/pingcap/tidb/sessionctx/stmtctx" "github.com/pingcap/tidb/sessionctx/variable" + "github.com/pingcap/tidb/statistics" "github.com/pingcap/tidb/statistics/handle" "github.com/pingcap/tidb/util/chunk" "github.com/pingcap/tidb/util/logutil" @@ -124,6 +124,23 @@ func (p *dumpFileGcChecker) gcDumpFilesByPath(path string, t time.Duration) { } } +type planReplayerHandle struct { + *planReplayerTaskCollectorHandle + *planReplayerTaskDumpHandle +} + +func (h *planReplayerHandle) handlePlanReplayerDumpTask(task *PlanReplayerDumpTask) { +} + +type planReplayerTaskCollectorHandle struct { + taskMu struct { + sync.RWMutex + tasks map[PlanReplayerTaskKey]struct{} + } + ctx context.Context + sctx sessionctx.Context +} + func deletePlanReplayerStatus(ctx context.Context, sctx sessionctx.Context, token string) { ctx1 := kv.WithInternalSourceType(ctx, kv.InternalTxnStats) exec := sctx.(sqlexec.SQLExecutor) @@ -175,20 +192,6 @@ func insertPlanReplayerSuccessStatusRecord(ctx context.Context, sctx sessionctx. } } -type planReplayerHandle struct { - *planReplayerTaskCollectorHandle - *planReplayerTaskDumpHandle -} - -type planReplayerTaskCollectorHandle struct { - taskMu struct { - sync.RWMutex - tasks map[PlanReplayerTaskKey]struct{} - } - ctx context.Context - sctx sessionctx.Context -} - // CollectPlanReplayerTask collects all unhandled plan replayer task func (h *planReplayerTaskCollectorHandle) CollectPlanReplayerTask() error { allKeys, err := h.collectAllPlanReplayerTask(h.ctx) @@ -261,19 +264,37 @@ type planReplayerTaskDumpHandle struct { taskCH chan *PlanReplayerDumpTask } +// DrainTask drain a task func (h *planReplayerTaskDumpHandle) DrainTask() *PlanReplayerDumpTask { select { case task := <-h.taskCH: return task - default: } - return nil } +// HandlePlanReplayerDumpTask handled the task func (h *planReplayerTaskDumpHandle) HandlePlanReplayerDumpTask(task *PlanReplayerDumpTask) { + taskKey := task.PlanReplayerTaskKey + unhandled, err := checkUnHandledReplayerTask(h.ctx, h.sctx, taskKey) + if err != nil { + logutil.BgLogger().Warn("check plan replayer capture task failed", + zap.String("sqlDigest", taskKey.SqlDigest), + zap.String("planDigest", taskKey.PlanDigest), + zap.Error(err)) + return + } + // the task is processed, thus we directly skip it. + if !unhandled { + return + } + file, fileName, err := GeneratePlanReplayerFile() if err != nil { - // TODO + logutil.BgLogger().Warn("generate plan replayer capture task file failed", + zap.String("sqlDigest", taskKey.SqlDigest), + zap.String("planDigest", taskKey.PlanDigest), + zap.Error(err)) + return } task.Zf = file task.FileName = fileName @@ -291,22 +312,31 @@ func (h *planReplayerTaskDumpHandle) HandlePlanReplayerDumpTask(task *PlanReplay } r, err := handle.GenJSONTableFromStats(schema.Name.String(), tbl.Meta(), stat.(*statistics.Table)) if err != nil { - // TODO + logutil.BgLogger().Warn("generate plan replayer capture task json stats failed", + zap.String("sqlDigest", taskKey.SqlDigest), + zap.String("planDigest", taskKey.PlanDigest), + zap.Error(err)) return } jsStats[tblID] = r } err = DumpPlanReplayerInfo(h.ctx, h.sctx, task) if err != nil { - // TODO + logutil.BgLogger().Warn("dump plan replayer capture task result failed", + zap.String("sqlDigest", taskKey.SqlDigest), + zap.String("planDigest", taskKey.PlanDigest), + zap.Error(err)) + return } } +// SendTask send dumpTask in background task handler func (h *planReplayerTaskDumpHandle) SendTask(task *PlanReplayerDumpTask) { select { case h.taskCH <- task: default: - // TODO + // TODO: add metrics here + // directly discard the task if the task channel is full in order not to block the query process } } @@ -349,9 +379,11 @@ type PlanReplayerTaskKey struct { type PlanReplayerDumpTask struct { PlanReplayerTaskKey + // tmp variables stored during the query EncodePlan func(*stmtctx.StatementContext, bool) (string, string) TblStats map[int64]interface{} + // variables used to dump the plan SessionBindings []*bindinfo.BindRecord EncodedPlan string SessionVars *variable.SessionVars diff --git a/domain/plan_replayer_handle_test.go b/domain/plan_replayer_handle_test.go index 2c25f56e15045..db0e8f48eb63f 100644 --- a/domain/plan_replayer_handle_test.go +++ b/domain/plan_replayer_handle_test.go @@ -15,7 +15,6 @@ package domain_test import ( - "context" "testing" "github.com/pingcap/tidb/testkit" @@ -31,14 +30,14 @@ func TestPlanReplayerHandleCollectTask(t *testing.T) { tk.MustExec("delete from mysql.plan_replayer_task") tk.MustExec("delete from mysql.plan_replayer_status") tk.MustExec("insert into mysql.plan_replayer_task (sql_digest, plan_digest) values ('123','123');") - err := prHandle.CollectPlanReplayerTask(context.Background()) + err := prHandle.CollectPlanReplayerTask() require.NoError(t, err) require.Len(t, prHandle.GetTasks(), 1) // assert no task tk.MustExec("delete from mysql.plan_replayer_task") tk.MustExec("delete from mysql.plan_replayer_status") - err = prHandle.CollectPlanReplayerTask(context.Background()) + err = prHandle.CollectPlanReplayerTask() require.NoError(t, err) require.Len(t, prHandle.GetTasks(), 0) @@ -48,7 +47,7 @@ func TestPlanReplayerHandleCollectTask(t *testing.T) { tk.MustExec("insert into mysql.plan_replayer_task (sql_digest, plan_digest) values ('123','123');") tk.MustExec("insert into mysql.plan_replayer_task (sql_digest, plan_digest) values ('345','345');") tk.MustExec("insert into mysql.plan_replayer_status(sql_digest, plan_digest, token, instance) values ('123','123','123','123')") - err = prHandle.CollectPlanReplayerTask(context.Background()) + err = prHandle.CollectPlanReplayerTask() require.NoError(t, err) require.Len(t, prHandle.GetTasks(), 1) @@ -58,7 +57,11 @@ func TestPlanReplayerHandleCollectTask(t *testing.T) { tk.MustExec("insert into mysql.plan_replayer_task (sql_digest, plan_digest) values ('123','123');") tk.MustExec("insert into mysql.plan_replayer_task (sql_digest, plan_digest) values ('345','345');") tk.MustExec("insert into mysql.plan_replayer_status(sql_digest, plan_digest, fail_reason, instance) values ('123','123','123','123')") - err = prHandle.CollectPlanReplayerTask(context.Background()) + err = prHandle.CollectPlanReplayerTask() require.NoError(t, err) require.Len(t, prHandle.GetTasks(), 2) } + +func TestPlanReplayerHandleDumpTask(t *testing.T) { + +} diff --git a/executor/adapter.go b/executor/adapter.go index ec328fd0efb7d..3dd0e0ce0877e 100644 --- a/executor/adapter.go +++ b/executor/adapter.go @@ -1629,6 +1629,7 @@ func getPlanDigest(stmtCtx *stmtctx.StatementContext) (string, *parser.Digest) { return normalized, planDigest } +// GetEncodedPlan returned same as getEncodedPlan func GetEncodedPlan(stmtCtx *stmtctx.StatementContext, genHint bool) (encodedPlan, hintStr string) { return getEncodedPlan(stmtCtx, genHint) } diff --git a/executor/compiler.go b/executor/compiler.go index 7bfbb54bf02d4..4c18caf556f5a 100644 --- a/executor/compiler.go +++ b/executor/compiler.go @@ -16,21 +16,21 @@ package executor import ( "context" - "github.com/pingcap/tidb/bindinfo" - "github.com/pingcap/tidb/domain" - "github.com/pingcap/tidb/sessionctx/variable" "strings" "github.com/opentracing/opentracing-go" "github.com/pingcap/errors" "github.com/pingcap/failpoint" + "github.com/pingcap/tidb/bindinfo" "github.com/pingcap/tidb/config" + "github.com/pingcap/tidb/domain" "github.com/pingcap/tidb/metrics" "github.com/pingcap/tidb/parser/ast" "github.com/pingcap/tidb/parser/mysql" "github.com/pingcap/tidb/planner" plannercore "github.com/pingcap/tidb/planner/core" "github.com/pingcap/tidb/sessionctx" + "github.com/pingcap/tidb/sessionctx/variable" "github.com/pingcap/tidb/sessiontxn" "github.com/pingcap/tidb/sessiontxn/staleread" "github.com/pingcap/tidb/util/logutil" @@ -157,7 +157,8 @@ func (c *Compiler) Compile(ctx context.Context, stmtNode ast.StmtNode) (_ *ExecS } } } - if variable.EnablePlanReplayerCapture.Load() { + enabledPlanReplayerCapture := variable.EnablePlanReplayerCapture.Load() + if enabledPlanReplayerCapture { checkPlanReplayerCaptureTask(c.Ctx, stmtNode) } From 9ee8a50f152cae4c649ecf186638a351cd75b06e Mon Sep 17 00:00:00 2001 From: yisaer Date: Mon, 14 Nov 2022 13:36:22 +0800 Subject: [PATCH 15/20] support Signed-off-by: yisaer --- domain/domain.go | 2 -- domain/plan_replayer.go | 37 ++++++++++++++++++--------- domain/plan_replayer_handle_test.go | 39 +++++++++++++++++++++++++++++ executor/compiler.go | 7 +++++- 4 files changed, 70 insertions(+), 15 deletions(-) diff --git a/domain/domain.go b/domain/domain.go index 1dcc8ac201148..b900cf3eb8d3a 100644 --- a/domain/domain.go +++ b/domain/domain.go @@ -1581,8 +1581,6 @@ func (do *Domain) StartPlanReplayerHandle() { if err != nil { logutil.BgLogger().Warn("plan replayer handle collect tasks failed", zap.Error(err)) } - case task := <-do.planReplayerHandle.planReplayerTaskDumpHandle.taskCH: - do.planReplayerHandle.HandlePlanReplayerDumpTask(task) } } }() diff --git a/domain/plan_replayer.go b/domain/plan_replayer.go index 899a8a40e160d..442b4e41749fb 100644 --- a/domain/plan_replayer.go +++ b/domain/plan_replayer.go @@ -129,7 +129,13 @@ type planReplayerHandle struct { *planReplayerTaskDumpHandle } -func (h *planReplayerHandle) handlePlanReplayerDumpTask(task *PlanReplayerDumpTask) { +// HandlePlanReplayerDumpTask handle dump task +func (h *planReplayerHandle) HandlePlanReplayerDumpTask(task *PlanReplayerDumpTask) bool { + success := h.dumpPlanReplayerDumpTask(task) + if success { + h.removeTask(task.PlanReplayerTaskKey) + } + return success } type planReplayerTaskCollectorHandle struct { @@ -173,8 +179,8 @@ func insertPlanReplayerStatus(ctx context.Context, sctx sessionctx.Context, reco func insertPlanReplayerErrorStatusRecord(ctx context.Context, sctx sessionctx.Context, instance string, record PlanReplayerStatusRecord) { exec := sctx.(sqlexec.SQLExecutor) _, err := exec.ExecuteInternal(ctx, fmt.Sprintf( - "insert into mysql.plan_replayer_status (origin_sql, fail_reason, instance) values ('%s','%s','%s')", - record.OriginSQL, record.FailedReason, instance)) + "insert into mysql.plan_replayer_status (sql_digest, plan_digest, origin_sql, fail_reason, instance) values ('%s','%s','%s','%s','%s')", + record.SQLDigest, record.PlanDigest, record.OriginSQL, record.FailedReason, instance)) if err != nil { logutil.BgLogger().Warn("insert mysql.plan_replayer_status record failed", zap.Error(err)) @@ -184,8 +190,8 @@ func insertPlanReplayerErrorStatusRecord(ctx context.Context, sctx sessionctx.Co func insertPlanReplayerSuccessStatusRecord(ctx context.Context, sctx sessionctx.Context, instance string, record PlanReplayerStatusRecord) { exec := sctx.(sqlexec.SQLExecutor) _, err := exec.ExecuteInternal(ctx, fmt.Sprintf( - "insert into mysql.plan_replayer_status (origin_sql, token, instance) values ('%s','%s','%s')", - record.OriginSQL, record.Token, instance)) + "insert into mysql.plan_replayer_status (sql_digest, plan_digest, origin_sql, token, instance) values ('%s','%s','%s','%s','%s')", + record.SQLDigest, record.PlanDigest, record.OriginSQL, record.Token, instance)) if err != nil { logutil.BgLogger().Warn("insert mysql.plan_replayer_status record failed", zap.Error(err)) @@ -233,6 +239,12 @@ func (h *planReplayerTaskCollectorHandle) setupTasks(tasks []PlanReplayerTaskKey h.taskMu.tasks = r } +func (h *planReplayerTaskCollectorHandle) removeTask(taskKey PlanReplayerTaskKey) { + h.taskMu.Lock() + defer h.taskMu.Unlock() + delete(h.taskMu.tasks, taskKey) +} + func (h *planReplayerTaskCollectorHandle) collectAllPlanReplayerTask(ctx context.Context) ([]PlanReplayerTaskKey, error) { exec := h.sctx.(sqlexec.SQLExecutor) rs, err := exec.ExecuteInternal(ctx, "select sql_digest, plan_digest from mysql.plan_replayer_task") @@ -273,7 +285,7 @@ func (h *planReplayerTaskDumpHandle) DrainTask() *PlanReplayerDumpTask { } // HandlePlanReplayerDumpTask handled the task -func (h *planReplayerTaskDumpHandle) HandlePlanReplayerDumpTask(task *PlanReplayerDumpTask) { +func (h *planReplayerTaskDumpHandle) dumpPlanReplayerDumpTask(task *PlanReplayerDumpTask) (success bool) { taskKey := task.PlanReplayerTaskKey unhandled, err := checkUnHandledReplayerTask(h.ctx, h.sctx, taskKey) if err != nil { @@ -281,11 +293,11 @@ func (h *planReplayerTaskDumpHandle) HandlePlanReplayerDumpTask(task *PlanReplay zap.String("sqlDigest", taskKey.SqlDigest), zap.String("planDigest", taskKey.PlanDigest), zap.Error(err)) - return + return false } // the task is processed, thus we directly skip it. if !unhandled { - return + return true } file, fileName, err := GeneratePlanReplayerFile() @@ -304,11 +316,11 @@ func (h *planReplayerTaskDumpHandle) HandlePlanReplayerDumpTask(task *PlanReplay for tblID, stat := range task.TblStats { tbl, ok := is.TableByID(tblID) if !ok { - return + return false } schema, ok := is.SchemaByTable(tbl.Meta()) if !ok { - return + return false } r, err := handle.GenJSONTableFromStats(schema.Name.String(), tbl.Meta(), stat.(*statistics.Table)) if err != nil { @@ -316,7 +328,7 @@ func (h *planReplayerTaskDumpHandle) HandlePlanReplayerDumpTask(task *PlanReplay zap.String("sqlDigest", taskKey.SqlDigest), zap.String("planDigest", taskKey.PlanDigest), zap.Error(err)) - return + return false } jsStats[tblID] = r } @@ -326,8 +338,9 @@ func (h *planReplayerTaskDumpHandle) HandlePlanReplayerDumpTask(task *PlanReplay zap.String("sqlDigest", taskKey.SqlDigest), zap.String("planDigest", taskKey.PlanDigest), zap.Error(err)) - return + return false } + return true } // SendTask send dumpTask in background task handler diff --git a/domain/plan_replayer_handle_test.go b/domain/plan_replayer_handle_test.go index db0e8f48eb63f..c6c37195517f1 100644 --- a/domain/plan_replayer_handle_test.go +++ b/domain/plan_replayer_handle_test.go @@ -15,8 +15,10 @@ package domain_test import ( + "fmt" "testing" + "github.com/pingcap/failpoint" "github.com/pingcap/tidb/testkit" "github.com/stretchr/testify/require" ) @@ -63,5 +65,42 @@ func TestPlanReplayerHandleCollectTask(t *testing.T) { } func TestPlanReplayerHandleDumpTask(t *testing.T) { + store, dom := testkit.CreateMockStoreAndDomain(t) + tk := testkit.NewTestKit(t, store) + prHandle := dom.GetPlanReplayerHandle() + tk.MustExec("use test") + tk.MustExec("create table t(a int)") + tk.MustQuery("select * from t;") + _, d := tk.Session().GetSessionVars().StmtCtx.SQLDigest() + _, pd := tk.Session().GetSessionVars().StmtCtx.GetPlanDigest() + sqlDigest := d.String() + planDigest := pd.String() + // register task + tk.MustExec("delete from mysql.plan_replayer_task") + tk.MustExec("delete from mysql.plan_replayer_status") + tk.MustExec(fmt.Sprintf("insert into mysql.plan_replayer_task (sql_digest, plan_digest) values ('%v','%v');", sqlDigest, planDigest)) + err := prHandle.CollectPlanReplayerTask() + require.NoError(t, err) + require.Len(t, prHandle.GetTasks(), 1) + + // enable capture + failpoint.Enable("github.com/pingcap/tidb/executor/enablePlanReplayerCapture", "return(true)") + defer func() { + failpoint.Disable("github.com/pingcap/tidb/executor/enablePlanReplayerCapture") + }() + + // capture task and dump + tk.MustQuery("select * from t;") + task := prHandle.DrainTask() + require.NotNil(t, task) + success := prHandle.HandlePlanReplayerDumpTask(task) + require.True(t, success) + // assert memory task consumed + require.Len(t, prHandle.GetTasks(), 0) + + // assert collect task again and no more memory task + err = prHandle.CollectPlanReplayerTask() + require.NoError(t, err) + require.Len(t, prHandle.GetTasks(), 0) } diff --git a/executor/compiler.go b/executor/compiler.go index 4c18caf556f5a..a0f547cabf902 100644 --- a/executor/compiler.go +++ b/executor/compiler.go @@ -158,6 +158,11 @@ func (c *Compiler) Compile(ctx context.Context, stmtNode ast.StmtNode) (_ *ExecS } } enabledPlanReplayerCapture := variable.EnablePlanReplayerCapture.Load() + failpoint.Inject("enablePlanReplayerCapture", func(val failpoint.Value) { + if val.(bool) { + enabledPlanReplayerCapture = true + } + }) if enabledPlanReplayerCapture { checkPlanReplayerCaptureTask(c.Ctx, stmtNode) } @@ -168,7 +173,7 @@ func (c *Compiler) Compile(ctx context.Context, stmtNode ast.StmtNode) (_ *ExecS func checkPlanReplayerCaptureTask(sctx sessionctx.Context, stmtNode ast.StmtNode) { tasks := domain.GetDomain(sctx).GetPlanReplayerHandle().GetTasks() _, sqlDigest := sctx.GetSessionVars().StmtCtx.SQLDigest() - _, planDigest := sctx.GetSessionVars().StmtCtx.GetPlanDigest() + _, planDigest := getPlanDigest(sctx.GetSessionVars().StmtCtx) for _, task := range tasks { if task.SqlDigest == sqlDigest.String() && task.PlanDigest == planDigest.String() { sendPlanReplayerDumpTask(sqlDigest.String(), planDigest.String(), sctx, stmtNode) From 8f0009e672a55d34e77bda63c74455fae06d4aaf Mon Sep 17 00:00:00 2001 From: yisaer Date: Tue, 15 Nov 2022 12:14:01 +0800 Subject: [PATCH 16/20] fix lint Signed-off-by: yisaer --- domain/plan_replayer.go | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/domain/plan_replayer.go b/domain/plan_replayer.go index 442b4e41749fb..df0e39cc37537 100644 --- a/domain/plan_replayer.go +++ b/domain/plan_replayer.go @@ -276,12 +276,9 @@ type planReplayerTaskDumpHandle struct { taskCH chan *PlanReplayerDumpTask } -// DrainTask drain a task +// DrainTask drain a task for unit test func (h *planReplayerTaskDumpHandle) DrainTask() *PlanReplayerDumpTask { - select { - case task := <-h.taskCH: - return task - } + return <-h.taskCH } // HandlePlanReplayerDumpTask handled the task From 85119012ea59eb2fdf0b96f5ff7b9a9a566a7750 Mon Sep 17 00:00:00 2001 From: yisaer Date: Tue, 15 Nov 2022 12:45:43 +0800 Subject: [PATCH 17/20] address the comment Signed-off-by: yisaer --- domain/plan_replayer_handle_test.go | 7 +------ executor/compiler.go | 9 +-------- planner/core/collect_column_stats_usage.go | 7 +++---- planner/core/plan_replayer_capture_test.go | 2 +- sessionctx/variable/session.go | 3 +++ sessionctx/variable/sysvar.go | 16 +++++++++------- sessionctx/variable/tidb_vars.go | 5 ++--- 7 files changed, 20 insertions(+), 29 deletions(-) diff --git a/domain/plan_replayer_handle_test.go b/domain/plan_replayer_handle_test.go index c6c37195517f1..5a824ef4eeeb6 100644 --- a/domain/plan_replayer_handle_test.go +++ b/domain/plan_replayer_handle_test.go @@ -18,7 +18,6 @@ import ( "fmt" "testing" - "github.com/pingcap/failpoint" "github.com/pingcap/tidb/testkit" "github.com/stretchr/testify/require" ) @@ -84,11 +83,7 @@ func TestPlanReplayerHandleDumpTask(t *testing.T) { require.NoError(t, err) require.Len(t, prHandle.GetTasks(), 1) - // enable capture - failpoint.Enable("github.com/pingcap/tidb/executor/enablePlanReplayerCapture", "return(true)") - defer func() { - failpoint.Disable("github.com/pingcap/tidb/executor/enablePlanReplayerCapture") - }() + tk.MustExec("SET @@tidb_enable_plan_replayer_capture = ON;") // capture task and dump tk.MustQuery("select * from t;") diff --git a/executor/compiler.go b/executor/compiler.go index a0f547cabf902..508d88d003cd2 100644 --- a/executor/compiler.go +++ b/executor/compiler.go @@ -30,7 +30,6 @@ import ( "github.com/pingcap/tidb/planner" plannercore "github.com/pingcap/tidb/planner/core" "github.com/pingcap/tidb/sessionctx" - "github.com/pingcap/tidb/sessionctx/variable" "github.com/pingcap/tidb/sessiontxn" "github.com/pingcap/tidb/sessiontxn/staleread" "github.com/pingcap/tidb/util/logutil" @@ -157,13 +156,7 @@ func (c *Compiler) Compile(ctx context.Context, stmtNode ast.StmtNode) (_ *ExecS } } } - enabledPlanReplayerCapture := variable.EnablePlanReplayerCapture.Load() - failpoint.Inject("enablePlanReplayerCapture", func(val failpoint.Value) { - if val.(bool) { - enabledPlanReplayerCapture = true - } - }) - if enabledPlanReplayerCapture { + if c.Ctx.GetSessionVars().EnablePlanReplayerCapture { checkPlanReplayerCaptureTask(c.Ctx, stmtNode) } diff --git a/planner/core/collect_column_stats_usage.go b/planner/core/collect_column_stats_usage.go index 49fffb149b85d..4a351e60a9018 100644 --- a/planner/core/collect_column_stats_usage.go +++ b/planner/core/collect_column_stats_usage.go @@ -17,7 +17,6 @@ package core import ( "github.com/pingcap/tidb/expression" "github.com/pingcap/tidb/parser/model" - "github.com/pingcap/tidb/sessionctx/variable" ) const ( @@ -51,7 +50,7 @@ type columnStatsUsageCollector struct { visitedtbls map[int64]struct{} } -func newColumnStatsUsageCollector(collectMode uint64) *columnStatsUsageCollector { +func newColumnStatsUsageCollector(collectMode uint64, enabledPlanCapture bool) *columnStatsUsageCollector { collector := &columnStatsUsageCollector{ collectMode: collectMode, // Pre-allocate a slice to reduce allocation, 8 doesn't have special meaning. @@ -64,7 +63,7 @@ func newColumnStatsUsageCollector(collectMode uint64) *columnStatsUsageCollector if collectMode&collectHistNeededColumns != 0 { collector.histNeededCols = make(map[model.TableItemID]struct{}) } - if variable.EnablePlanReplayerCapture.Load() { + if enabledPlanCapture { collector.collectVisitedTable = true collector.visitedtbls = map[int64]struct{}{} } @@ -300,7 +299,7 @@ func CollectColumnStatsUsage(lp LogicalPlan, predicate, histNeeded bool) ([]mode if histNeeded { mode |= collectHistNeededColumns } - collector := newColumnStatsUsageCollector(mode) + collector := newColumnStatsUsageCollector(mode, lp.SCtx().GetSessionVars().EnablePlanReplayerCapture) collector.collectFromPlan(lp) if collector.collectVisitedTable { recordTableRuntimeStats(lp.SCtx(), collector.visitedtbls) diff --git a/planner/core/plan_replayer_capture_test.go b/planner/core/plan_replayer_capture_test.go index 2e88f090bd784..66e93033c92e4 100644 --- a/planner/core/plan_replayer_capture_test.go +++ b/planner/core/plan_replayer_capture_test.go @@ -35,7 +35,7 @@ func TestPlanReplayerCaptureRecordJsonStats(t *testing.T) { tk.MustExec("use test") tk.MustExec("create table t1(a int)") tk.MustExec("create table t2(a int)") - tk.MustExec("SET global tidb_enable_plan_replayer_capture = ON;") + tk.MustExec("SET @@tidb_enable_plan_replayer_capture = ON;") tk.MustExec("analyze table t1") tk.MustExec("analyze table t2") testcases := []struct { diff --git a/sessionctx/variable/session.go b/sessionctx/variable/session.go index b11b727079630..b8fbcf54848e1 100644 --- a/sessionctx/variable/session.go +++ b/sessionctx/variable/session.go @@ -1305,6 +1305,9 @@ type SessionVars struct { // preuseChunkAlloc indicates whether pre statement use chunk alloc // like select @@last_sql_use_alloc preUseChunkAlloc bool + + // EnablePlanReplayerCapture indicates whether enabled plan replayer capture + EnablePlanReplayerCapture bool } // GetNewChunkWithCapacity Attempt to request memory from the chunk pool diff --git a/sessionctx/variable/sysvar.go b/sessionctx/variable/sysvar.go index a592e7a7e8831..d73885663d957 100644 --- a/sessionctx/variable/sysvar.go +++ b/sessionctx/variable/sysvar.go @@ -1039,15 +1039,17 @@ var defaultSysVars = []*SysVar{ }, GetGlobal: func(_ context.Context, s *SessionVars) (string, error) { return fmt.Sprintf("%d", MemoryUsageAlarmKeepRecordNum.Load()), nil }}, - {Scope: ScopeGlobal, Name: TiDBEnablePlanReplayerCapture, Value: BoolToOnOff(false), Type: TypeBool, - SetGlobal: func(ctx context.Context, s *SessionVars, val string) error { - EnablePlanReplayerCapture.Store(TiDBOptOn(val)) - return nil - }, GetGlobal: func(ctx context.Context, vars *SessionVars) (string, error) { - return strconv.FormatBool(EnablePlanReplayerCapture.Load()), nil - }}, /* The system variables below have GLOBAL and SESSION scope */ + {Scope: ScopeGlobal | ScopeSession, Name: TiDBEnablePlanReplayerCapture, Value: BoolToOnOff(false), Type: TypeBool, + SetSession: func(s *SessionVars, val string) error { + s.EnablePlanReplayerCapture = TiDBOptOn(val) + return nil + }, + GetSession: func(vars *SessionVars) (string, error) { + return strconv.FormatBool(vars.EnablePlanReplayerCapture), nil + }, + }, {Scope: ScopeGlobal | ScopeSession, Name: TiDBRowFormatVersion, Value: strconv.Itoa(DefTiDBRowFormatV1), Type: TypeUnsigned, MinValue: 1, MaxValue: 2, SetGlobal: func(_ context.Context, s *SessionVars, val string) error { SetDDLReorgRowFormat(TidbOptInt64(val, DefTiDBRowFormatV2)) return nil diff --git a/sessionctx/variable/tidb_vars.go b/sessionctx/variable/tidb_vars.go index 80c9b41f4cc6e..3511775de08f1 100644 --- a/sessionctx/variable/tidb_vars.go +++ b/sessionctx/variable/tidb_vars.go @@ -1148,9 +1148,8 @@ var ( // DefTiDBServerMemoryLimit indicates the default value of TiDBServerMemoryLimit(TotalMem * 80%). // It should be a const and shouldn't be modified after tidb is started. - DefTiDBServerMemoryLimit = serverMemoryLimitDefaultValue() - GOGCTunerThreshold = atomic.NewFloat64(DefTiDBGOGCTunerThreshold) - EnablePlanReplayerCapture = atomic.NewBool(DefTiDBEnablePlanReplayerCapture) + DefTiDBServerMemoryLimit = serverMemoryLimitDefaultValue() + GOGCTunerThreshold = atomic.NewFloat64(DefTiDBGOGCTunerThreshold) ) var ( From 26d7691ab43770a7c9fd276d523fc9cf640a1ddd Mon Sep 17 00:00:00 2001 From: yisaer Date: Tue, 15 Nov 2022 12:53:47 +0800 Subject: [PATCH 18/20] fix lint Signed-off-by: yisaer --- domain/plan_replayer.go | 14 +++++++------- domain/plan_replayer_dump.go | 2 +- executor/compiler.go | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/domain/plan_replayer.go b/domain/plan_replayer.go index df0e39cc37537..faab592950c64 100644 --- a/domain/plan_replayer.go +++ b/domain/plan_replayer.go @@ -263,7 +263,7 @@ func (h *planReplayerTaskCollectorHandle) collectAllPlanReplayerTask(ctx context for _, row := range rows { sqlDigest, planDigest := row.GetString(0), row.GetString(1) allKeys = append(allKeys, PlanReplayerTaskKey{ - SqlDigest: sqlDigest, + SQLDigest: sqlDigest, PlanDigest: planDigest, }) } @@ -287,7 +287,7 @@ func (h *planReplayerTaskDumpHandle) dumpPlanReplayerDumpTask(task *PlanReplayer unhandled, err := checkUnHandledReplayerTask(h.ctx, h.sctx, taskKey) if err != nil { logutil.BgLogger().Warn("check plan replayer capture task failed", - zap.String("sqlDigest", taskKey.SqlDigest), + zap.String("sqlDigest", taskKey.SQLDigest), zap.String("planDigest", taskKey.PlanDigest), zap.Error(err)) return false @@ -300,7 +300,7 @@ func (h *planReplayerTaskDumpHandle) dumpPlanReplayerDumpTask(task *PlanReplayer file, fileName, err := GeneratePlanReplayerFile() if err != nil { logutil.BgLogger().Warn("generate plan replayer capture task file failed", - zap.String("sqlDigest", taskKey.SqlDigest), + zap.String("sqlDigest", taskKey.SQLDigest), zap.String("planDigest", taskKey.PlanDigest), zap.Error(err)) return @@ -322,7 +322,7 @@ func (h *planReplayerTaskDumpHandle) dumpPlanReplayerDumpTask(task *PlanReplayer r, err := handle.GenJSONTableFromStats(schema.Name.String(), tbl.Meta(), stat.(*statistics.Table)) if err != nil { logutil.BgLogger().Warn("generate plan replayer capture task json stats failed", - zap.String("sqlDigest", taskKey.SqlDigest), + zap.String("sqlDigest", taskKey.SQLDigest), zap.String("planDigest", taskKey.PlanDigest), zap.Error(err)) return false @@ -332,7 +332,7 @@ func (h *planReplayerTaskDumpHandle) dumpPlanReplayerDumpTask(task *PlanReplayer err = DumpPlanReplayerInfo(h.ctx, h.sctx, task) if err != nil { logutil.BgLogger().Warn("dump plan replayer capture task result failed", - zap.String("sqlDigest", taskKey.SqlDigest), + zap.String("sqlDigest", taskKey.SQLDigest), zap.String("planDigest", taskKey.PlanDigest), zap.Error(err)) return false @@ -352,7 +352,7 @@ func (h *planReplayerTaskDumpHandle) SendTask(task *PlanReplayerDumpTask) { func checkUnHandledReplayerTask(ctx context.Context, sctx sessionctx.Context, task PlanReplayerTaskKey) (bool, error) { exec := sctx.(sqlexec.SQLExecutor) - rs, err := exec.ExecuteInternal(ctx, fmt.Sprintf("select * from mysql.plan_replayer_status where sql_digest = '%v' and plan_digest = '%v' and fail_reason is null", task.SqlDigest, task.PlanDigest)) + rs, err := exec.ExecuteInternal(ctx, fmt.Sprintf("select * from mysql.plan_replayer_status where sql_digest = '%v' and plan_digest = '%v' and fail_reason is null", task.SQLDigest, task.PlanDigest)) if err != nil { return false, err } @@ -381,7 +381,7 @@ type PlanReplayerStatusRecord struct { // PlanReplayerTaskKey indicates key of a plan replayer task type PlanReplayerTaskKey struct { - SqlDigest string + SQLDigest string PlanDigest string } diff --git a/domain/plan_replayer_dump.go b/domain/plan_replayer_dump.go index 3e13f3c15113e..195dae7b4a0b1 100644 --- a/domain/plan_replayer_dump.go +++ b/domain/plan_replayer_dump.go @@ -252,7 +252,7 @@ func generateRecords(task *PlanReplayerDumpTask) []PlanReplayerStatusRecord { if len(task.ExecStmts) > 0 { for _, execStmt := range task.ExecStmts { records = append(records, PlanReplayerStatusRecord{ - SQLDigest: task.SqlDigest, + SQLDigest: task.SQLDigest, PlanDigest: task.PlanDigest, OriginSQL: execStmt.Text(), Token: task.FileName, diff --git a/executor/compiler.go b/executor/compiler.go index 508d88d003cd2..4ddb6208a445c 100644 --- a/executor/compiler.go +++ b/executor/compiler.go @@ -168,7 +168,7 @@ func checkPlanReplayerCaptureTask(sctx sessionctx.Context, stmtNode ast.StmtNode _, sqlDigest := sctx.GetSessionVars().StmtCtx.SQLDigest() _, planDigest := getPlanDigest(sctx.GetSessionVars().StmtCtx) for _, task := range tasks { - if task.SqlDigest == sqlDigest.String() && task.PlanDigest == planDigest.String() { + if task.SQLDigest == sqlDigest.String() && task.PlanDigest == planDigest.String() { sendPlanReplayerDumpTask(sqlDigest.String(), planDigest.String(), sctx, stmtNode) } } @@ -179,7 +179,7 @@ func sendPlanReplayerDumpTask(sqlDigest, planDigest string, sctx sessionctx.Cont handle := sctx.Value(bindinfo.SessionBindInfoKeyType).(*bindinfo.SessionHandle) dumpTask := &domain.PlanReplayerDumpTask{ PlanReplayerTaskKey: domain.PlanReplayerTaskKey{ - SqlDigest: sqlDigest, + SQLDigest: sqlDigest, PlanDigest: planDigest, }, EncodePlan: GetEncodedPlan, From dcf128d42742c88739349363832dd116af947104 Mon Sep 17 00:00:00 2001 From: yisaer Date: Tue, 15 Nov 2022 13:04:40 +0800 Subject: [PATCH 19/20] fix lint Signed-off-by: yisaer --- domain/BUILD.bazel | 2 ++ 1 file changed, 2 insertions(+) diff --git a/domain/BUILD.bazel b/domain/BUILD.bazel index f7ef9baba6907..4b575f4dc63e0 100644 --- a/domain/BUILD.bazel +++ b/domain/BUILD.bazel @@ -42,7 +42,9 @@ go_library( "//privilege/privileges", "//sessionctx", "//sessionctx/sessionstates", + "//sessionctx/stmtctx", "//sessionctx/variable", + "//statistics", "//statistics/handle", "//telemetry", "//types", From f75da426ca8372ad949024d5a5bb0897d5d12d99 Mon Sep 17 00:00:00 2001 From: yisaer Date: Tue, 15 Nov 2022 13:27:58 +0800 Subject: [PATCH 20/20] fix test Signed-off-by: yisaer --- planner/core/plan_replayer_capture_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/planner/core/plan_replayer_capture_test.go b/planner/core/plan_replayer_capture_test.go index 66e93033c92e4..6778cdba20bbf 100644 --- a/planner/core/plan_replayer_capture_test.go +++ b/planner/core/plan_replayer_capture_test.go @@ -35,7 +35,6 @@ func TestPlanReplayerCaptureRecordJsonStats(t *testing.T) { tk.MustExec("use test") tk.MustExec("create table t1(a int)") tk.MustExec("create table t2(a int)") - tk.MustExec("SET @@tidb_enable_plan_replayer_capture = ON;") tk.MustExec("analyze table t1") tk.MustExec("analyze table t2") testcases := []struct { @@ -68,6 +67,7 @@ func getTableStats(sql string, t *testing.T, ctx sessionctx.Context, dom *domain err = core.Preprocess(context.Background(), ctx, stmt, core.WithPreprocessorReturn(&core.PreprocessorReturn{InfoSchema: dom.InfoSchema()})) require.NoError(t, err) sctx := core.MockContext() + sctx.GetSessionVars().EnablePlanReplayerCapture = true builder, _ := core.NewPlanBuilder().Init(sctx, dom.InfoSchema(), &hint.BlockHintProcessor{}) domain.GetDomain(sctx).MockInfoCacheAndLoadInfoSchema(dom.InfoSchema()) plan, err := builder.Build(context.TODO(), stmt)