Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
106155: sql/sqlstats: avoid heap allocations when using stats iterators r=maryliag a=nvanbenschoten

This PR contains two changes that each avoid per-transaction heap allocations in the `sqlstats` package.

### sql/sqlstats: return iterators by value from constructors

This commit switches to returning sqlstats iterations by value from their constructors, instead of by pointer. This avoids causing the iterators to escape and needing to allocate on the heap. Instead, they can be kept on the stack, avoiding heap allocations in methods like `IterateStatementStats`.

```sh
➜ benchdiff --run='BenchmarkKV/./SQL/rows=1$$' --count=25 ./pkg/sql/tests

name                     old time/op    new time/op    delta
KV/Insert/SQL/rows=1-10     173µs ±13%     172µs ±15%    ~     (p=0.939 n=25+25)
KV/Update/SQL/rows=1-10     285µs ±16%     280µs ±11%    ~     (p=0.356 n=25+23)
KV/Delete/SQL/rows=1-10     212µs ±25%     221µs ±19%    ~     (p=0.345 n=25+25)
KV/Scan/SQL/rows=1-10       127µs ±11%     127µs ±11%    ~     (p=0.878 n=25+24)

name                     old alloc/op   new alloc/op   delta
KV/Scan/SQL/rows=1-10      32.2kB ± 1%    32.0kB ± 1%  -0.36%  (p=0.006 n=24+24)
KV/Insert/SQL/rows=1-10    59.0kB ± 1%    58.9kB ± 1%    ~     (p=0.140 n=25+25)
KV/Update/SQL/rows=1-10    70.6kB ± 1%    70.4kB ± 1%    ~     (p=0.050 n=24+24)
KV/Delete/SQL/rows=1-10    84.9kB ± 1%    84.9kB ± 1%    ~     (p=0.859 n=25+25)

name                     old allocs/op  new allocs/op  delta
KV/Scan/SQL/rows=1-10         368 ± 1%       366 ± 1%  -0.62%  (p=0.001 n=24+24)
KV/Update/SQL/rows=1-10       726 ± 1%       723 ± 2%  -0.45%  (p=0.013 n=23+24)
KV/Insert/SQL/rows=1-10       492 ± 2%       490 ± 1%  -0.41%  (p=0.044 n=25+25)
KV/Delete/SQL/rows=1-10       613 ± 3%       613 ± 3%    ~     (p=0.179 n=25+25)
```

### sql/sqlstats: pass `sqlstats.IteratorOptions` by value, not pointer

This commit switches from passing the `sqlstats.IteratorOptions` around by pointer to passing it by value. The struct is 2 bytes large (8 when padded), so there's little benefit to passing it around by pointer. Meanwhile, passing the object by pointer through interface methods prevents escape analysis from keeping it on the stack, forcing a heap allocation.

```
➜ benchdiff --run='BenchmarkKV/./SQL/rows=1$$' --count=25 ./pkg/sql/tests

name                     old time/op    new time/op    delta
KV/Insert/SQL/rows=1-10     169µs ±13%     168µs ±17%    ~     (p=0.603 n=25+25)
KV/Update/SQL/rows=1-10     282µs ± 9%     282µs ± 8%    ~     (p=0.788 n=25+25)
KV/Delete/SQL/rows=1-10     227µs ±26%     206µs ±27%    ~     (p=0.074 n=25+25)
KV/Scan/SQL/rows=1-10       126µs ±12%     127µs ±16%    ~     (p=0.908 n=25+25)

name                     old alloc/op   new alloc/op   delta
KV/Delete/SQL/rows=1-10    84.9kB ± 1%    84.5kB ± 1%  -0.50%  (p=0.008 n=25+21)
KV/Insert/SQL/rows=1-10    58.9kB ± 1%    58.7kB ± 1%  -0.23%  (p=0.020 n=25+23)
KV/Update/SQL/rows=1-10    70.5kB ± 1%    70.5kB ± 1%    ~     (p=0.894 n=24+24)
KV/Scan/SQL/rows=1-10      32.0kB ± 1%    32.0kB ± 1%    ~     (p=0.631 n=25+24)

name                     old allocs/op  new allocs/op  delta
KV/Delete/SQL/rows=1-10       613 ± 3%       604 ± 1%  -1.52%  (p=0.000 n=25+21)
KV/Insert/SQL/rows=1-10       489 ± 1%       486 ± 1%  -0.69%  (p=0.001 n=25+23)
KV/Scan/SQL/rows=1-10         365 ± 1%       363 ± 1%  -0.42%  (p=0.024 n=25+25)
KV/Update/SQL/rows=1-10       724 ± 2%       722 ± 2%    ~     (p=0.302 n=25+24)
```

Epic: None
Release note: None

106157:  sql: add tests for CTAS, CMVAS with every vtable r=chengxiong-ruan a=ecwall

Informs #105895

Adds tests for CREATE TABLE AS, CREATE MATERIALIZED VIEW AS sourcing from
all vtables.

Release note: None

Co-authored-by: Nathan VanBenschoten <[email protected]>
Co-authored-by: Evan Wall <[email protected]>
  • Loading branch information
3 people committed Jul 5, 2023
3 parents a343d0a + b7dd103 + 68a9c38 commit e337ad7
Show file tree
Hide file tree
Showing 22 changed files with 227 additions and 88 deletions.
1 change: 1 addition & 0 deletions pkg/sql/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -616,6 +616,7 @@ go_test(
"copy_test.go",
"copy_to_test.go",
"crdb_internal_test.go",
"create_as_test.go",
"create_function_test.go",
"create_stats_test.go",
"create_test.go",
Expand Down
9 changes: 3 additions & 6 deletions pkg/sql/conn_executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -678,8 +678,7 @@ func (s *Server) GetUnscrubbedStmtStats(
stmtStats = append(stmtStats, *stat)
return nil
}
err :=
s.sqlStats.GetLocalMemProvider().IterateStatementStats(ctx, &sqlstats.IteratorOptions{}, stmtStatsVisitor)
err := s.sqlStats.GetLocalMemProvider().IterateStatementStats(ctx, sqlstats.IteratorOptions{}, stmtStatsVisitor)

if err != nil {
return nil, errors.Wrap(err, "failed to fetch statement stats")
Expand All @@ -698,8 +697,7 @@ func (s *Server) GetUnscrubbedTxnStats(
txnStats = append(txnStats, *stat)
return nil
}
err :=
s.sqlStats.GetLocalMemProvider().IterateTransactionStats(ctx, &sqlstats.IteratorOptions{}, txnStatsVisitor)
err := s.sqlStats.GetLocalMemProvider().IterateTransactionStats(ctx, sqlstats.IteratorOptions{}, txnStatsVisitor)

if err != nil {
return nil, errors.Wrap(err, "failed to fetch statement stats")
Expand Down Expand Up @@ -748,8 +746,7 @@ func (s *Server) getScrubbedStmtStats(
return nil
}

err :=
statsProvider.IterateStatementStats(ctx, &sqlstats.IteratorOptions{}, stmtStatsVisitor)
err := statsProvider.IterateStatementStats(ctx, sqlstats.IteratorOptions{}, stmtStatsVisitor)

if err != nil {
return nil, errors.Wrap(err, "failed to fetch scrubbed statement stats")
Expand Down
10 changes: 5 additions & 5 deletions pkg/sql/crdb_internal.go
Original file line number Diff line number Diff line change
Expand Up @@ -1659,7 +1659,7 @@ CREATE TABLE crdb_internal.node_statement_statistics (
return nil
}

return sqlStats.GetLocalMemProvider().IterateStatementStats(ctx, &sqlstats.IteratorOptions{
return sqlStats.GetLocalMemProvider().IterateStatementStats(ctx, sqlstats.IteratorOptions{
SortedAppNames: true,
SortedKey: true,
}, statementVisitor)
Expand Down Expand Up @@ -1817,7 +1817,7 @@ CREATE TABLE crdb_internal.node_transaction_statistics (
return nil
}

return sqlStats.GetLocalMemProvider().IterateTransactionStats(ctx, &sqlstats.IteratorOptions{
return sqlStats.GetLocalMemProvider().IterateTransactionStats(ctx, sqlstats.IteratorOptions{
SortedAppNames: true,
SortedKey: true,
}, transactionVisitor)
Expand Down Expand Up @@ -1857,7 +1857,7 @@ CREATE TABLE crdb_internal.node_txn_stats (
)
}

return sqlStats.IterateAggregatedTransactionStats(ctx, &sqlstats.IteratorOptions{
return sqlStats.IterateAggregatedTransactionStats(ctx, sqlstats.IteratorOptions{
SortedAppNames: true,
}, appTxnStatsVisitor)
},
Expand Down Expand Up @@ -6396,7 +6396,7 @@ CREATE TABLE crdb_internal.cluster_statement_statistics (

row := make(tree.Datums, 9 /* number of columns for this virtual table */)
worker := func(ctx context.Context, pusher rowPusher) error {
return memSQLStats.IterateStatementStats(ctx, &sqlstats.IteratorOptions{
return memSQLStats.IterateStatementStats(ctx, sqlstats.IteratorOptions{
SortedAppNames: true,
SortedKey: true,
}, func(ctx context.Context, statistics *appstatspb.CollectedStatementStatistics) error {
Expand Down Expand Up @@ -6798,7 +6798,7 @@ CREATE TABLE crdb_internal.cluster_transaction_statistics (

row := make(tree.Datums, 5 /* number of columns for this virtual table */)
worker := func(ctx context.Context, pusher rowPusher) error {
return memSQLStats.IterateTransactionStats(ctx, &sqlstats.IteratorOptions{
return memSQLStats.IterateTransactionStats(ctx, sqlstats.IteratorOptions{
SortedAppNames: true,
SortedKey: true,
}, func(
Expand Down
126 changes: 126 additions & 0 deletions pkg/sql/create_as_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
// Copyright 2023 The Cockroach Authors.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.

package sql

import (
"context"
"fmt"
"strings"
"testing"

"github.com/cockroachdb/cockroach/pkg/base"
"github.com/cockroachdb/cockroach/pkg/sql/parser"
"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
"github.com/cockroachdb/cockroach/pkg/sql/types"
"github.com/cockroachdb/cockroach/pkg/testutils/serverutils"
"github.com/cockroachdb/cockroach/pkg/testutils/sqlutils"
"github.com/cockroachdb/cockroach/pkg/util/leaktest"
"github.com/stretchr/testify/require"
)

// TestCreateAsVTable verifies that all vtables can be used as the source of
// CREATE TABLE AS and CREATE MATERIALIZED VIEW AS.
func TestCreateAsVTable(t *testing.T) {
defer leaktest.AfterTest(t)()

// These are the vtables that need to be fixed.
// The map should be empty if all vtables are supported.
brokenTables := map[string]struct{}{
// TODO(sql-foundations): Fix nil pointer dereference.
// See https://github.com/cockroachdb/cockroach/issues/106166.
`pg_catalog.pg_prepared_statements`: {},
// TODO(sql-foundations): Fix nil pointer dereference.
// See https://github.com/cockroachdb/cockroach/issues/106167.
`pg_catalog.pg_cursors`: {},
// TODO(sql-foundations): Fix nil pointer dereference.
// See https://github.com/cockroachdb/cockroach/issues/106168.
`"".crdb_internal.create_statements`: {},
}

ctx := context.Background()
testCluster := serverutils.StartNewTestCluster(t, 1, base.TestClusterArgs{})
defer testCluster.Stopper().Stop(ctx)
sqlRunner := sqlutils.MakeSQLRunner(testCluster.ServerConn(0))
var p parser.Parser

i := 0
for _, vSchema := range virtualSchemas {
for _, vSchemaDef := range vSchema.tableDefs {
if vSchemaDef.isUnimplemented() {
continue
}

var name tree.TableName
var ctasColumns []string
schema := vSchemaDef.getSchema()
statements, err := p.Parse(schema)
require.NoErrorf(t, err, schema)
require.Lenf(t, statements, 1, schema)
switch stmt := statements[0].AST.(type) {
case *tree.CreateTable:
name = stmt.Table
for _, def := range stmt.Defs {
if colDef, ok := def.(*tree.ColumnTableDef); ok {
if colDef.Hidden {
continue
}
// Filter out vector columns to prevent error in CTAS:
// "VECTOR column types are unsupported".
if colDef.Type == types.Int2Vector || colDef.Type == types.OidVector {
continue
}
ctasColumns = append(ctasColumns, colDef.Name.String())
}
}
case *tree.CreateView:
name = stmt.Name
ctasColumns = []string{"*"}
default:
require.Failf(t, "missing case", "unexpected type %T for schema %s", stmt, schema)
}

fqName := name.FQString()
if _, ok := brokenTables[fqName]; ok {
continue
}

// Filter by trace_id to prevent error when selecting from
// crdb_internal.cluster_inflight_traces:
// "pq: a trace_id value needs to be specified".
var where string
if fqName == `"".crdb_internal.cluster_inflight_traces` {
where = " WHERE trace_id = 1"
}

createTableStmt := fmt.Sprintf(
"CREATE TABLE test_table_%d AS SELECT %s FROM %s%s",
i, strings.Join(ctasColumns, ", "), fqName, where,
)
sqlRunner.Exec(t, createTableStmt)
createViewStmt := fmt.Sprintf(
"CREATE MATERIALIZED VIEW test_view_%d AS SELECT * FROM %s%s",
i, fqName, where,
)
sqlRunner.Exec(t, createViewStmt)
i++
}
}

waitForJobsSuccess(t, sqlRunner)
}

func waitForJobsSuccess(t *testing.T, sqlRunner *sqlutils.SQLRunner) {
query := `SELECT job_id, status, error, description
FROM [SHOW JOBS]
WHERE job_type IN ('SCHEMA CHANGE', 'NEW SCHEMA CHANGE')
AND status != 'succeeded'`
sqlRunner.CheckQueryResultsRetry(t, query, [][]string{})
}
4 changes: 2 additions & 2 deletions pkg/sql/instrumentation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ func TestSampledStatsCollection(t *testing.T) {
GetLocalMemProvider().
IterateStatementStats(
ctx,
&sqlstats.IteratorOptions{},
sqlstats.IteratorOptions{},
func(ctx context.Context, statistics *appstatspb.CollectedStatementStatistics) error {
if statistics.Key.Query == key.Query &&
statistics.Key.ImplicitTxn == key.ImplicitTxn &&
Expand Down Expand Up @@ -89,7 +89,7 @@ func TestSampledStatsCollection(t *testing.T) {
GetLocalMemProvider().
IterateTransactionStats(
ctx,
&sqlstats.IteratorOptions{},
sqlstats.IteratorOptions{},
func(ctx context.Context, statistics *appstatspb.CollectedTransactionStatistics) error {
if statistics.TransactionFingerprintID == key {
stats = statistics
Expand Down
11 changes: 9 additions & 2 deletions pkg/sql/sem/tree/table_name.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

package tree

import "github.com/cockroachdb/cockroach/pkg/sql/sem/catconstants"

// TableName corresponds to the name of a table in a FROM clause,
// INSERT or UPDATE statement, etc.
//
Expand Down Expand Up @@ -46,8 +48,13 @@ func (t *TableName) objectName() {}
// schema and catalog names. Suitable for logging, etc.
func (t *TableName) FQString() string {
ctx := NewFmtCtx(FmtSimple)
ctx.FormatNode(&t.CatalogName)
ctx.WriteByte('.')
schemaName := t.SchemaName.String()
// The pg_catalog and pg_extension schemas cannot be referenced from inside
// an anonymous ("") database. This makes their FQ string always relative.
if schemaName != catconstants.PgCatalogName && schemaName != catconstants.PgExtensionSchemaName {
ctx.FormatNode(&t.CatalogName)
ctx.WriteByte('.')
}
ctx.FormatNode(&t.SchemaName)
ctx.WriteByte('.')
ctx.FormatNode(&t.ObjectName)
Expand Down
16 changes: 8 additions & 8 deletions pkg/sql/sqlstats/persistedsqlstats/combined_iterator.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ type CombinedStmtStatsIterator struct {
mem struct {
canBeAdvanced bool
paused bool
it *memStmtStatsIterator
it memStmtStatsIterator
}

disk struct {
Expand All @@ -42,9 +42,9 @@ type CombinedStmtStatsIterator struct {
// NewCombinedStmtStatsIterator returns a new instance of
// CombinedStmtStatsIterator.
func NewCombinedStmtStatsIterator(
memIter *memStmtStatsIterator, diskIter isql.Rows, expectedColCnt int,
) *CombinedStmtStatsIterator {
c := &CombinedStmtStatsIterator{
memIter memStmtStatsIterator, diskIter isql.Rows, expectedColCnt int,
) CombinedStmtStatsIterator {
c := CombinedStmtStatsIterator{
expectedColCnt: expectedColCnt,
}

Expand Down Expand Up @@ -215,7 +215,7 @@ type CombinedTxnStatsIterator struct {
mem struct {
canBeAdvanced bool
paused bool
it *memTxnStatsIterator
it memTxnStatsIterator
}

disk struct {
Expand All @@ -228,9 +228,9 @@ type CombinedTxnStatsIterator struct {
// NewCombinedTxnStatsIterator returns a new instance of
// CombinedTxnStatsIterator.
func NewCombinedTxnStatsIterator(
memIter *memTxnStatsIterator, diskIter isql.Rows, expectedColCnt int,
) *CombinedTxnStatsIterator {
c := &CombinedTxnStatsIterator{
memIter memTxnStatsIterator, diskIter isql.Rows, expectedColCnt int,
) CombinedTxnStatsIterator {
c := CombinedTxnStatsIterator{
expectedColCnt: expectedColCnt,
}

Expand Down
4 changes: 2 additions & 2 deletions pkg/sql/sqlstats/persistedsqlstats/flush.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ FROM
func (s *PersistedSQLStats) flushStmtStats(ctx context.Context, aggregatedTs time.Time) {
// s.doFlush directly logs errors if they are encountered. Therefore,
// no error is returned here.
_ = s.SQLStats.IterateStatementStats(ctx, &sqlstats.IteratorOptions{},
_ = s.SQLStats.IterateStatementStats(ctx, sqlstats.IteratorOptions{},
func(ctx context.Context, statistics *appstatspb.CollectedStatementStatistics) error {
s.doFlush(ctx, func() error {
return s.doFlushSingleStmtStats(ctx, statistics, aggregatedTs)
Expand All @@ -160,7 +160,7 @@ func (s *PersistedSQLStats) flushStmtStats(ctx context.Context, aggregatedTs tim
}

func (s *PersistedSQLStats) flushTxnStats(ctx context.Context, aggregatedTs time.Time) {
_ = s.SQLStats.IterateTransactionStats(ctx, &sqlstats.IteratorOptions{},
_ = s.SQLStats.IterateTransactionStats(ctx, sqlstats.IteratorOptions{},
func(ctx context.Context, statistics *appstatspb.CollectedTransactionStatistics) error {
s.doFlush(ctx, func() error {
return s.doFlushSingleTxnStats(ctx, statistics, aggregatedTs)
Expand Down
4 changes: 2 additions & 2 deletions pkg/sql/sqlstats/persistedsqlstats/flush_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -651,7 +651,7 @@ func verifyInMemoryStatsCorrectness(
t *testing.T, tcs []testCase, statsProvider *persistedsqlstats.PersistedSQLStats,
) {
for _, tc := range tcs {
err := statsProvider.SQLStats.IterateStatementStats(context.Background(), &sqlstats.IteratorOptions{}, func(ctx context.Context, statistics *appstatspb.CollectedStatementStatistics) error {
err := statsProvider.SQLStats.IterateStatementStats(context.Background(), sqlstats.IteratorOptions{}, func(ctx context.Context, statistics *appstatspb.CollectedStatementStatistics) error {
if tc.fingerprint == statistics.Key.Query {
require.Equal(t, tc.count, statistics.Stats.Count, "fingerprint: %s", tc.fingerprint)
}
Expand All @@ -678,7 +678,7 @@ func verifyInMemoryStatsEmpty(
t *testing.T, tcs []testCase, statsProvider *persistedsqlstats.PersistedSQLStats,
) {
for _, tc := range tcs {
err := statsProvider.SQLStats.IterateStatementStats(context.Background(), &sqlstats.IteratorOptions{}, func(ctx context.Context, statistics *appstatspb.CollectedStatementStatistics) error {
err := statsProvider.SQLStats.IterateStatementStats(context.Background(), sqlstats.IteratorOptions{}, func(ctx context.Context, statistics *appstatspb.CollectedStatementStatistics) error {
if tc.fingerprint == statistics.Key.Query {
require.Equal(t, 0 /* expected */, statistics.Stats.Count, "fingerprint: %s", tc.fingerprint)
}
Expand Down
16 changes: 8 additions & 8 deletions pkg/sql/sqlstats/persistedsqlstats/mem_iterator.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,18 @@ import (
// and aggregation_interval fields on the returning
// appstatspb.CollectedStatementStatistics.
type memStmtStatsIterator struct {
*sslocal.StmtStatsIterator
sslocal.StmtStatsIterator
aggregatedTs time.Time
aggInterval time.Duration
}

func newMemStmtStatsIterator(
stats *sslocal.SQLStats,
options *sqlstats.IteratorOptions,
options sqlstats.IteratorOptions,
aggregatedTS time.Time,
aggInterval time.Duration,
) *memStmtStatsIterator {
return &memStmtStatsIterator{
) memStmtStatsIterator {
return memStmtStatsIterator{
StmtStatsIterator: stats.StmtStatsIterator(options),
aggregatedTs: aggregatedTS,
aggInterval: aggInterval,
Expand All @@ -59,18 +59,18 @@ func (m *memStmtStatsIterator) Cur() *appstatspb.CollectedStatementStatistics {
// aggregatoin_interval fields fields on the returning
// appstatspb.CollectedTransactionStatistics.
type memTxnStatsIterator struct {
*sslocal.TxnStatsIterator
sslocal.TxnStatsIterator
aggregatedTs time.Time
aggInterval time.Duration
}

func newMemTxnStatsIterator(
stats *sslocal.SQLStats,
options *sqlstats.IteratorOptions,
options sqlstats.IteratorOptions,
aggregatedTS time.Time,
aggInterval time.Duration,
) *memTxnStatsIterator {
return &memTxnStatsIterator{
) memTxnStatsIterator {
return memTxnStatsIterator{
TxnStatsIterator: stats.TxnStatsIterator(options),
aggregatedTs: aggregatedTS,
aggInterval: aggInterval,
Expand Down
Loading

0 comments on commit e337ad7

Please sign in to comment.