From d3d249959c72831f77957b9649ecde8d09a0dd1f Mon Sep 17 00:00:00 2001 From: Thomas Hardy Date: Thu, 29 Jun 2023 14:50:36 -0400 Subject: [PATCH] sqlstats, builtins: add builtin to reset activity tables This change introduces `crdb_internal.reset_activity_tables` to reset the statistics in the `system.{statement|transaction}_activity` tables. This is useful as an escape hatch for cases when we have stale/inaccurate statistics in the tables (i.e. if the activity job was cancelled). Users need `admin` to use this builtin. Release note (sql change): Add `crdb_internal.reset_activity_tables` builtin to allow users to reset the statistics in the `system.{statement|transaction}_activity` tables. Users require `admin` to use this builtin. --- docs/generated/sql/functions.md | 2 + pkg/sql/sem/builtins/builtins.go | 28 ++++++ pkg/sql/sem/builtins/fixed_oids.go | 1 + pkg/sql/sem/eval/deps.go | 1 + .../sqlstats/persistedsqlstats/controller.go | 34 ++++--- .../persistedsqlstats/controller_test.go | 98 +++++++++++++++++++ 6 files changed, 150 insertions(+), 14 deletions(-) diff --git a/docs/generated/sql/functions.md b/docs/generated/sql/functions.md index 78a152771fe4..c3e6cbcf3d2d 100644 --- a/docs/generated/sql/functions.md +++ b/docs/generated/sql/functions.md @@ -3279,6 +3279,8 @@ that has execution latency greater than the ‘minExecutionLatency’. If the ‘expiresAfter’ argument is empty, then the statement bundle request never expires until the statement bundle is collected

Volatile +crdb_internal.reset_activity_tables() → bool

This function is used to clear the {statement|transaction} activity system tables.

+
Volatile crdb_internal.reset_index_usage_stats() → bool

This function is used to clear the collected index usage statistics.

Volatile crdb_internal.reset_sql_stats() → bool

This function is used to clear the collected SQL statistics.

diff --git a/pkg/sql/sem/builtins/builtins.go b/pkg/sql/sem/builtins/builtins.go index 26dabe32a348..cfb20f446b68 100644 --- a/pkg/sql/sem/builtins/builtins.go +++ b/pkg/sql/sem/builtins/builtins.go @@ -6996,6 +6996,34 @@ table's zone configuration this will return NULL.`, Volatility: volatility.Volatile, }, ), + "crdb_internal.reset_activity_tables": makeBuiltin( + tree.FunctionProperties{ + Category: builtinconstants.CategorySystemInfo, + DistsqlBlocklist: true, // applicable only on the gateway + }, + tree.Overload{ + Types: tree.ParamTypes{}, + ReturnType: tree.FixedReturnType(types.Bool), + Fn: func(ctx context.Context, evalCtx *eval.Context, args tree.Datums) (tree.Datum, error) { + isAdmin, err := evalCtx.SessionAccessor.HasAdminRole(ctx) + if err != nil { + return nil, err + } + if !isAdmin { + return nil, errors.New("crdb_internal.reset_activity_tables() requires admin privilege") + } + if evalCtx.SQLStatsController == nil { + return nil, errors.AssertionFailedf("sql stats controller not set") + } + if err := evalCtx.SQLStatsController.ResetActivityTables(ctx); err != nil { + return nil, err + } + return tree.MakeDBool(true), nil + }, + Info: `This function is used to clear the {statement|transaction} activity system tables.`, + Volatility: volatility.Volatile, + }, + ), // Deletes the underlying spans backing a table, only // if the user provides explicit acknowledgement of the // form "I acknowledge this will irrevocably delete all revisions diff --git a/pkg/sql/sem/builtins/fixed_oids.go b/pkg/sql/sem/builtins/fixed_oids.go index 58e4a4d447ac..a050093b42ed 100644 --- a/pkg/sql/sem/builtins/fixed_oids.go +++ b/pkg/sql/sem/builtins/fixed_oids.go @@ -2423,6 +2423,7 @@ var builtinOidsArray = []string{ 2450: `st_asmvtgeom(geometry: geometry, bbox: box2d, extent: int) -> geometry`, 2451: `st_asmvtgeom(geometry: geometry, bbox: box2d) -> geometry`, 2452: `crdb_internal.repaired_descriptor(descriptor: bytes, valid_descriptor_ids: int[], valid_job_ids: int[]) -> bytes`, + 2453: `crdb_internal.reset_activity_tables() -> bool`, } var builtinOidsBySignature map[string]oid.Oid diff --git a/pkg/sql/sem/eval/deps.go b/pkg/sql/sem/eval/deps.go index 7920720041aa..e861641c64f5 100644 --- a/pkg/sql/sem/eval/deps.go +++ b/pkg/sql/sem/eval/deps.go @@ -631,6 +631,7 @@ type GossipOperator interface { // to avoid circular dependency. type SQLStatsController interface { ResetClusterSQLStats(ctx context.Context) error + ResetActivityTables(ctx context.Context) error CreateSQLStatsCompactionSchedule(ctx context.Context) error } diff --git a/pkg/sql/sqlstats/persistedsqlstats/controller.go b/pkg/sql/sqlstats/persistedsqlstats/controller.go index 77d725496675..ec6b7e961eb0 100644 --- a/pkg/sql/sqlstats/persistedsqlstats/controller.go +++ b/pkg/sql/sqlstats/persistedsqlstats/controller.go @@ -59,32 +59,38 @@ func (s *Controller) ResetClusterSQLStats(ctx context.Context) error { return err } - resetSysTableStats := func(tableName string) (err error) { - ex := s.db.Executor() - _, err = ex.ExecEx( - ctx, - "reset-sql-stats", - nil, /* txn */ - sessiondata.NodeUserSessionDataOverride, - "TRUNCATE "+tableName) + if err := s.resetSysTableStats(ctx, "system.statement_statistics"); err != nil { return err } - if err := resetSysTableStats("system.statement_statistics"); err != nil { + if err := s.resetSysTableStats(ctx, "system.transaction_statistics"); err != nil { return err } - if err := resetSysTableStats("system.transaction_statistics"); err != nil { - return err - } + return s.ResetActivityTables(ctx) +} +// ResetActivityTables implements the tree.SQLStatsController interface. This +// method resets the {statement|transaction}_activity system tables. +func (s *Controller) ResetActivityTables(ctx context.Context) error { if !s.st.Version.IsActive(ctx, clusterversion.V23_1CreateSystemActivityUpdateJob) { return nil } - if err := resetSysTableStats("system.statement_activity"); err != nil { + if err := s.resetSysTableStats(ctx, "system.statement_activity"); err != nil { return err } - return resetSysTableStats("system.transaction_activity") + return s.resetSysTableStats(ctx, "system.transaction_activity") +} + +func (s *Controller) resetSysTableStats(ctx context.Context, tableName string) (err error) { + ex := s.db.Executor() + _, err = ex.ExecEx( + ctx, + "reset-sql-stats", + nil, /* txn */ + sessiondata.NodeUserSessionDataOverride, + "TRUNCATE "+tableName) + return err } diff --git a/pkg/sql/sqlstats/persistedsqlstats/controller_test.go b/pkg/sql/sqlstats/persistedsqlstats/controller_test.go index 2538a77e7e2f..85eb6753219f 100644 --- a/pkg/sql/sqlstats/persistedsqlstats/controller_test.go +++ b/pkg/sql/sqlstats/persistedsqlstats/controller_test.go @@ -21,10 +21,12 @@ import ( "github.com/cockroachdb/cockroach/pkg/sql" "github.com/cockroachdb/cockroach/pkg/sql/sqlstats/persistedsqlstats" "github.com/cockroachdb/cockroach/pkg/sql/tests" + "github.com/cockroachdb/cockroach/pkg/testutils" "github.com/cockroachdb/cockroach/pkg/testutils/serverutils" "github.com/cockroachdb/cockroach/pkg/testutils/sqlutils" "github.com/cockroachdb/cockroach/pkg/util/leaktest" "github.com/cockroachdb/cockroach/pkg/util/log" + "github.com/cockroachdb/errors" "github.com/stretchr/testify/require" ) @@ -162,3 +164,99 @@ WHERE app_name = $1 `expected %s to be found in txn stats, but it was not.`, query) } } + +func TestActivityTablesReset(t *testing.T) { + defer leaktest.AfterTest(t)() + defer log.Scope(t).Close(t) + + s, db, _ := serverutils.StartServer(t, base.TestServerArgs{}) + sqlDB := sqlutils.MakeSQLRunner(db) + defer s.Stopper().Stop(context.Background()) + + // Disable sql activity flush job. + sqlDB.Exec(t, `SET CLUSTER SETTING sql.stats.activity.flush.enabled = false`) + + testutils.SucceedsSoon(t, func() error { + var enabled bool + sqlDB.QueryRow(t, + "SHOW CLUSTER SETTING sql.stats.activity.flush.enabled", + ).Scan(&enabled) + if enabled == true { + return errors.Newf("waiting for sql activity job to be disabled") + } + return nil + }) + + // Give the query runner privilege to insert into the activity tables. + sqlDB.Exec(t, "INSERT INTO system.users VALUES ('node', NULL, true, 3)") + sqlDB.Exec(t, "GRANT node TO root") + + // Insert into system.statement_activity table + sqlDB.Exec(t, ` + INSERT INTO system.public.statement_activity (aggregated_ts, fingerprint_id, transaction_fingerprint_id, plan_hash, app_name, + agg_interval, metadata, statistics, plan, index_recommendations, execution_count, + execution_total_seconds, execution_total_cluster_seconds, + contention_time_avg_seconds, + cpu_sql_avg_nanos, + service_latency_avg_seconds, service_latency_p99_seconds) + VALUES ( + '2023-06-29 15:00:00+00', + '\x125167e869920859', + '\xbd32daa4ef93bf86', + '\x0fa54115f7caf3e6', + 'activity_tables_reset', + '01:00:00', + '{"db": "", "distsql": false, "failed": false, "fullScan": false, "implicitTxn": true, "query": "SELECT id FROM system.jobs", "querySummary": "SELECT id FROM system.jobs", "stmtType": "TypeDML", "vec": true}', + '{"execution_statistics": {"cnt": 1, "contentionTime": {"mean": 0, "sqDiff": 0}, "cpuSQLNanos": {"mean": 133623, "sqDiff": 0}, "maxDiskUsage": {"mean": 0, "sqDiff": 0}, "maxMemUsage": {"mean": 3.072E+4, "sqDiff": 0}, "mvccIteratorStats": {"blockBytes": {"mean": 28086, "sqDiff": 0}, "blockBytesInCache": {"mean": 0, "sqDiff": 0}, "keyBytes": {"mean": 0, "sqDiff": 0}, "pointCount": {"mean": 6, "sqDiff": 0}, "pointsCoveredByRangeTombstones": {"mean": 0, "sqDiff": 0}, "rangeKeyContainedPoints": {"mean": 0, "sqDiff": 0}, "rangeKeyCount": {"mean": 0, "sqDiff": 0}, "rangeKeySkippedPoints": {"mean": 0, "sqDiff": 0}, "seekCount": {"mean": 2, "sqDiff": 0}, "seekCountInternal": {"mean": 2, "sqDiff": 0}, "stepCount": {"mean": 6, "sqDiff": 0}, "stepCountInternal": {"mean": 6, "sqDiff": 0}, "valueBytes": {"mean": 39, "sqDiff": 0}}, "networkBytes": {"mean": 0, "sqDiff": 0}, "networkMsgs": {"mean": 0, "sqDiff": 0}}, "index_recommendations": [], "statistics": {"bytesRead": {"mean": 432, "sqDiff": 0}, "cnt": 1, "firstAttemptCnt": 1, "idleLat": {"mean": 0, "sqDiff": 0}, "indexes": ["15@4"], "lastErrorCode": "", "lastExecAt": "2023-06-29T15:33:11.042902Z", "latencyInfo": {"max": 0.00142586, "min": 0.00142586, "p50": 0, "p90": 0, "p99": 0}, "maxRetries": 0, "nodes": [3], "numRows": {"mean": 3, "sqDiff": 0}, "ovhLat": {"mean": 0.000012357999999999857, "sqDiff": 0}, "parseLat": {"mean": 0, "sqDiff": 0}, "planGists": ["AgEeCACHDwIAAAMFAgYC"], "planLat": {"mean": 0.000655272, "sqDiff": 0}, "regions": ["us-east1"], "rowsRead": {"mean": 3, "sqDiff": 0}, "rowsWritten": {"mean": 0, "sqDiff": 0}, "runLat": {"mean": 0.00075823, "sqDiff": 0}, "svcLat": {"mean": 0.00142586, "sqDiff": 0}}}', + '{"Children": [], "Name": ""}', + '{}', + 1, + 1, + 1, + 1, + 1, + 1, + 1 + ) + `) + // Insert into system.transaction_activity table + sqlDB.Exec(t, ` + INSERT INTO system.public.transaction_activity (aggregated_ts, fingerprint_id, app_name, agg_interval, metadata, + statistics, query, execution_count, execution_total_seconds, + execution_total_cluster_seconds, contention_time_avg_seconds, + cpu_sql_avg_nanos, service_latency_avg_seconds, service_latency_p99_seconds) + VALUES ( + '2023-06-29 15:00:00+00', + '\x125167e869920859', + 'activity_tables_reset', + '01:00:00', + '{"db": "", "distsql": false, "failed": false, "fullScan": false, "implicitTxn": true, "query": "SELECT id FROM system.jobs", "querySummary": "SELECT id FROM system.jobs", "stmtType": "TypeDML", "vec": true}', + '{"execution_statistics": {"cnt": 1, "contentionTime": {"mean": 0, "sqDiff": 0}, "cpuSQLNanos": {"mean": 133623, "sqDiff": 0}, "maxDiskUsage": {"mean": 0, "sqDiff": 0}, "maxMemUsage": {"mean": 3.072E+4, "sqDiff": 0}, "mvccIteratorStats": {"blockBytes": {"mean": 28086, "sqDiff": 0}, "blockBytesInCache": {"mean": 0, "sqDiff": 0}, "keyBytes": {"mean": 0, "sqDiff": 0}, "pointCount": {"mean": 6, "sqDiff": 0}, "pointsCoveredByRangeTombstones": {"mean": 0, "sqDiff": 0}, "rangeKeyContainedPoints": {"mean": 0, "sqDiff": 0}, "rangeKeyCount": {"mean": 0, "sqDiff": 0}, "rangeKeySkippedPoints": {"mean": 0, "sqDiff": 0}, "seekCount": {"mean": 2, "sqDiff": 0}, "seekCountInternal": {"mean": 2, "sqDiff": 0}, "stepCount": {"mean": 6, "sqDiff": 0}, "stepCountInternal": {"mean": 6, "sqDiff": 0}, "valueBytes": {"mean": 39, "sqDiff": 0}}, "networkBytes": {"mean": 0, "sqDiff": 0}, "networkMsgs": {"mean": 0, "sqDiff": 0}}, "index_recommendations": [], "statistics": {"bytesRead": {"mean": 432, "sqDiff": 0}, "cnt": 1, "firstAttemptCnt": 1, "idleLat": {"mean": 0, "sqDiff": 0}, "indexes": ["15@4"], "lastErrorCode": "", "lastExecAt": "2023-06-29T15:33:11.042902Z", "latencyInfo": {"max": 0.00142586, "min": 0.00142586, "p50": 0, "p90": 0, "p99": 0}, "maxRetries": 0, "nodes": [3], "numRows": {"mean": 3, "sqDiff": 0}, "ovhLat": {"mean": 0.000012357999999999857, "sqDiff": 0}, "parseLat": {"mean": 0, "sqDiff": 0}, "planGists": ["AgEeCACHDwIAAAMFAgYC"], "planLat": {"mean": 0.000655272, "sqDiff": 0}, "regions": ["us-east1"], "rowsRead": {"mean": 3, "sqDiff": 0}, "rowsWritten": {"mean": 0, "sqDiff": 0}, "runLat": {"mean": 0.00075823, "sqDiff": 0}, "svcLat": {"mean": 0.00142586, "sqDiff": 0}}}', + 'SELECT id FROM system.jobs', + 1, + 1, + 1, + 1, + 1, + 1, + 1 + ) + `) + + // Check that system.{statement|transaction} activity tables both have 1 row. + var count int + sqlDB.QueryRow(t, "SELECT count(*) FROM system.statement_activity").Scan(&count) + require.Equal(t, 1 /* expected */, count) + + sqlDB.QueryRow(t, "SELECT count(*) FROM system.transaction_activity").Scan(&count) + require.Equal(t, 1 /* expected */, count) + + // Flush the tables. + sqlDB.QueryRow(t, "SELECT crdb_internal.reset_activity_tables()") + + sqlDB.QueryRow(t, "SELECT count(*) FROM system.statement_activity").Scan(&count) + require.Equal(t, 0 /* expected */, count) + + sqlDB.QueryRow(t, "SELECT count(*) FROM system.transaction_activity").Scan(&count) + require.Equal(t, 0 /* expected */, count) +}