Skip to content

Commit

Permalink
ui: add statement fingerprint to insights
Browse files Browse the repository at this point in the history
1. Removes contention events from insights. This avoids
tracking and storing duplicate information.

2. Add database_name, schema_name, table_name, index_name,
contending_pretty_key to crdb_internal.transaction_contention_events.
This avoids needing to join with multiple tables and makes it easier for
users to understand.

3. Add waiting statement info to the insights transaction details page.
This way if users have a large transaction with multiple statements with
multiple contention events they can see which specific statement was
waited on.

4. Add blocking transaction fingerprint to the insight statement details
page. The user can now see which transaction is blocking the statement
,and it has a link to the blocking transaction activity page.

closes: #91665

Release note (ui change): Add waiting statement id and fingerprint to
insights transaction details page. Add blocking transaction id and
fingerprint to the insights statement page.
  • Loading branch information
j82w committed Feb 10, 2023
1 parent 7b272ff commit e135c67
Show file tree
Hide file tree
Showing 18 changed files with 467 additions and 371 deletions.
129 changes: 62 additions & 67 deletions pkg/sql/crdb_internal.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ import (
"github.com/cockroachdb/cockroach/pkg/sql/catalog/systemschema"
"github.com/cockroachdb/cockroach/pkg/sql/catalog/typedesc"
"github.com/cockroachdb/cockroach/pkg/sql/clusterunique"
"github.com/cockroachdb/cockroach/pkg/sql/contentionpb"
"github.com/cockroachdb/cockroach/pkg/sql/idxusage"
"github.com/cockroachdb/cockroach/pkg/sql/isql"
"github.com/cockroachdb/cockroach/pkg/sql/parser"
Expand Down Expand Up @@ -6473,9 +6474,15 @@ CREATE TABLE crdb_internal.transaction_contention_events (
contention_duration INTERVAL NOT NULL,
contending_key BYTES NOT NULL,
waiting_stmt_id string NOT NULL,
waiting_stmt_fingerprint_id BYTES NOT NULL
contending_pretty_key STRING NOT NULL,
waiting_stmt_id string NOT NULL,
waiting_stmt_fingerprint_id BYTES NOT NULL,
database_name STRING NOT NULL,
schema_name STRING NOT NULL,
table_name STRING NOT NULL,
index_name STRING
);`,
generator: func(ctx context.Context, p *planner, db catalog.DatabaseDescriptor, stopper *stop.Stopper) (virtualTableGenerator, cleanupFunc, error) {
// Check permission first before making RPC fanout.
Expand Down Expand Up @@ -6541,28 +6548,41 @@ CREATE TABLE crdb_internal.transaction_contention_events (
types.DefaultIntervalTypeMetadata,
)

contendingPrettyKey := tree.NewDString("")
contendingKey := tree.NewDBytes("")
if !shouldRedactContendingKey {
decodedKey, _, _ := keys.DecodeTenantPrefix(resp.Events[i].BlockingEvent.Key)
contendingPrettyKey = tree.NewDString(keys.PrettyPrint(nil /* valDirs */, decodedKey))
contendingKey = tree.NewDBytes(
tree.DBytes(resp.Events[i].BlockingEvent.Key))
tree.DBytes(decodedKey))
}

waitingStmtFingerprintID := tree.NewDBytes(
tree.DBytes(sqlstatsutil.EncodeUint64ToBytes(uint64(resp.Events[i].WaitingStmtFingerprintID))))

waitingStmtId := tree.NewDString(hex.EncodeToString(resp.Events[i].WaitingStmtID.GetBytes()))

schemaName, dbName, tableName, indexName, err := getContentionEventInfo(ctx, p, resp.Events[i])
if err != nil {
return err
}

row = row[:0]
row = append(row,
collectionTs, // collection_ts
tree.NewDUuid(tree.DUuid{UUID: resp.Events[i].BlockingEvent.TxnMeta.ID}), // blocking_txn_id
blockingFingerprintID, // blocking_fingerprint_id
tree.NewDUuid(tree.DUuid{UUID: resp.Events[i].WaitingTxnID}), // waiting_txn_id
waitingFingerprintID, // waiting_fingerprint_id
contentionDuration, // contention_duration
contendingKey, // contending_key,
waitingStmtId, // waiting_stmt_id
waitingStmtFingerprintID, // waiting_stmt_fingerprint_id
waitingFingerprintID, // waiting_fingerprint_id
contentionDuration, // contention_duration
contendingKey, // contending_key,
contendingPrettyKey, // contending_pretty_key
waitingStmtId, // waiting_stmt_id
waitingStmtFingerprintID, // waiting_stmt_fingerprint_id
tree.NewDString(dbName), // database_name
tree.NewDString(schemaName), // schema_name
tree.NewDString(tableName), // table_name
tree.NewDString(indexName), // index_name
)

if err = pusher.pushRow(row...); err != nil {
Expand Down Expand Up @@ -7207,7 +7227,6 @@ CREATE TABLE crdb_internal.%s (
last_retry_reason STRING,
exec_node_ids INT[] NOT NULL,
contention INTERVAL,
contention_events JSONB,
index_recommendations STRING[] NOT NULL,
implicit_txn BOOL NOT NULL,
cpu_sql_nanos INT8
Expand Down Expand Up @@ -7295,17 +7314,6 @@ func populateStmtInsights(
)
}

contentionEvents := tree.DNull
if len(s.ContentionEvents) > 0 {
var contentionEventsJSON json.JSON
contentionEventsJSON, err = convertContentionEventsToJSON(ctx, p, s.ContentionEvents)
if err != nil {
return err
}

contentionEvents = tree.NewDJSON(contentionEventsJSON)
}

indexRecommendations := tree.NewDArray(types.String)
for _, recommendation := range s.IndexRecommendations {
if err = indexRecommendations.Append(tree.NewDString(recommendation)); err != nil {
Expand Down Expand Up @@ -7337,7 +7345,6 @@ func populateStmtInsights(
autoRetryReason,
execNodeIDs,
contentionTime,
contentionEvents,
indexRecommendations,
tree.MakeDBool(tree.DBool(insight.Transaction.ImplicitTxn)),
tree.NewDInt(tree.DInt(s.CPUSQLNanos)),
Expand All @@ -7347,57 +7354,45 @@ func populateStmtInsights(
return
}

func convertContentionEventsToJSON(
ctx context.Context, p *planner, contentionEvents []roachpb.ContentionEvent,
) (json json.JSON, err error) {
func getContentionEventInfo(
ctx context.Context, p *planner, contentionEvent contentionpb.ExtendedContentionEvent,
) (schemaName, dbName, tableName, indexName string, err error) {

eventWithNames := make([]sqlstatsutil.ContentionEventWithNames, len(contentionEvents))
for i, contentionEvent := range contentionEvents {
_, tableID, err := p.ExecCfg().Codec.DecodeTablePrefix(contentionEvent.Key)
if err != nil {
return nil, err
}
_, _, indexID, err := p.ExecCfg().Codec.DecodeIndexPrefix(contentionEvent.Key)
if err != nil {
return nil, err
}

desc := p.Descriptors()
var tableDesc catalog.TableDescriptor
tableDesc, err = desc.ByIDWithLeased(p.txn).WithoutNonPublic().Get().Table(ctx, descpb.ID(tableID))
if err != nil {
return nil, err
}
_, tableID, err := p.ExecCfg().Codec.DecodeTablePrefix(contentionEvent.BlockingEvent.Key)
if err != nil {
return "", "", "", "", err
}
_, _, indexID, err := p.ExecCfg().Codec.DecodeIndexPrefix(contentionEvent.BlockingEvent.Key)
if err != nil {
return "", "", "", "", err
}

idxDesc, err := catalog.MustFindIndexByID(tableDesc, descpb.IndexID(indexID))
if err != nil {
return nil, err
}
desc := p.Descriptors()
var tableDesc catalog.TableDescriptor
tableDesc, err = desc.ByIDWithLeased(p.txn).WithoutNonPublic().Get().Table(ctx, descpb.ID(tableID))
if err != nil {
return "", "", "", "", err
}

dbDesc, err := desc.ByIDWithLeased(p.txn).WithoutNonPublic().Get().Database(ctx, tableDesc.GetParentID())
if err != nil {
return nil, err
}
idxDesc, err := catalog.MustFindIndexByID(tableDesc, descpb.IndexID(indexID))
if err != nil {
return "", "", "", "", err
}

schemaDesc, err := desc.ByIDWithLeased(p.txn).WithoutNonPublic().Get().Schema(ctx, tableDesc.GetParentSchemaID())
if err != nil {
return nil, err
}
dbDesc, err := desc.ByIDWithLeased(p.txn).WithoutNonPublic().Get().Database(ctx, tableDesc.GetParentID())
if err != nil {
return "", "", "", "", err
}

var idxName string
if idxDesc != nil {
idxName = idxDesc.GetName()
}
schemaDesc, err := desc.ByIDWithLeased(p.txn).WithoutNonPublic().Get().Schema(ctx, tableDesc.GetParentSchemaID())
if err != nil {
return "", "", "", "", err
}

eventWithNames[i] = sqlstatsutil.ContentionEventWithNames{
BlockingTransactionID: contentionEvent.TxnMeta.ID.String(),
SchemaName: schemaDesc.GetName(),
DatabaseName: dbDesc.GetName(),
TableName: tableDesc.GetName(),
IndexName: idxName,
DurationInMs: float64(contentionEvent.Duration) / float64(time.Millisecond),
}
var idxName string
if idxDesc != nil {
idxName = idxDesc.GetName()
}

return sqlstatsutil.BuildContentionEventsJSON(eventWithNames)
return schemaDesc.GetName(), dbDesc.GetName(), tableDesc.GetName(), idxName, nil
}
30 changes: 26 additions & 4 deletions pkg/sql/crdb_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -982,7 +982,12 @@ func TestTxnContentionEventsTable(t *testing.T) {
waiting_stmt_id,
encode(
waiting_txn_fingerprint_id, 'hex'
) AS waiting_txn_fingerprint_id
) AS waiting_txn_fingerprint_id,
contending_pretty_key,
database_name,
schema_name,
table_name,
index_name
FROM crdb_internal.transaction_contention_events tce
inner join (
select
Expand All @@ -1001,7 +1006,8 @@ func TestTxnContentionEventsTable(t *testing.T) {
rowCount++

var blockingTxnId, waitingTxnId, waitingStmtId, waitingStmtFingerprint string
errVerify = rows.Scan(&blockingTxnId, &waitingTxnId, &waitingStmtId, &waitingStmtFingerprint)
var prettyKey, dbName, schemaName, tableName, indexName string
errVerify = rows.Scan(&blockingTxnId, &waitingTxnId, &waitingStmtId, &waitingStmtFingerprint, &prettyKey, &dbName, &schemaName, &tableName, &indexName)
if errVerify != nil {
return errVerify
}
Expand All @@ -1015,8 +1021,24 @@ func TestTxnContentionEventsTable(t *testing.T) {
return fmt.Errorf("transaction_contention_events had default waiting txn id %s, blocking txn id %s", waitingTxnId, blockingTxnId)
}

if waitingStmtId == defaultIdString {
return fmt.Errorf("transaction_contention_events had default waiting stmt id %s, blocking txn id %s, waiting txn id %s", waitingStmtId, blockingTxnId, waitingTxnId)
if !strings.HasPrefix(prettyKey, "/Table/") {
return fmt.Errorf("prettyKey should be defaultdb: %s, %s, %s, %s, %s", prettyKey, dbName, schemaName, tableName, indexName)
}

if dbName != "defaultdb" {
return fmt.Errorf("dbName should be defaultdb: %s, %s, %s, %s, %s", prettyKey, dbName, schemaName, tableName, indexName)
}

if schemaName != "public" {
return fmt.Errorf("schemaName should be public: %s, %s, %s, %s, %s", prettyKey, dbName, schemaName, tableName, indexName)
}

if tableName != "t" {
return fmt.Errorf("tableName should be t: %s, %s, %s, %s, %s", prettyKey, dbName, schemaName, tableName, indexName)
}

if indexName != "t_pkey" {
return fmt.Errorf("indexName should be t_pkey: %s, %s, %s, %s, %s", prettyKey, dbName, schemaName, tableName, indexName)
}
}

Expand Down
Loading

0 comments on commit e135c67

Please sign in to comment.