Skip to content

Commit

Permalink
roachpb/server: add new endpoint for getting a table's index stats
Browse files Browse the repository at this point in the history
Partially addresses cockroachdb#67647

Previously, we had an RPC endpoint for retrieving all index usage
statistics. However, this endpoint should only be used for internal
purposes. We needed an endpoint that can be exposed to the frontend, for
use in the Index Stats table (which will be created in a following
commit).

Hence, this commit creates a new RPC endpoint and functions that read
the required index usage statistics directly from the database using
SQL. Instead of returning all index usage statistics, we only retrieve
the indexes for a specified database and table.

Release note (api change): Add new api endpoint for getting a table's
index statistics.
  • Loading branch information
lindseyjin committed Nov 16, 2021
1 parent eb026ff commit 9f0bd08
Show file tree
Hide file tree
Showing 9 changed files with 1,676 additions and 518 deletions.
65 changes: 63 additions & 2 deletions docs/generated/http/full.md
Original file line number Diff line number Diff line change
Expand Up @@ -4006,6 +4006,67 @@ Response object returned by ResetIndexUsageStatsRequest.



## TableIndexStats

`GET /_status/databases/{database}/tables/{table}/indexstats`

TableIndexStats retrieves index stats for a table.

Support status: [reserved](#support-status)

#### Request Parameters




Request object for issuing TableIndexStatsRequest request.


| Field | Type | Label | Description | Support status |
| ----- | ---- | ----- | ----------- | -------------- |
| database | [string](#cockroach.server.serverpb.TableIndexStatsRequest-string) | | database is the name of the database that contains the table we're interested in. | [reserved](#support-status) |
| table | [string](#cockroach.server.serverpb.TableIndexStatsRequest-string) | | table is the name of the table that we're querying. Table may be schema-qualified (schema.table) and each name component that contains sql unsafe characters such as . or uppercase letters must be surrounded in double quotes like "naughty schema".table. | [reserved](#support-status) |







#### Response Parameters




Response object returned by TableIndexStatsResponse.


| Field | Type | Label | Description | Support status |
| ----- | ---- | ----- | ----------- | -------------- |
| statistics | [TableIndexStatsResponse.ExtendedCollectedIndexUsageStatistics](#cockroach.server.serverpb.TableIndexStatsResponse-cockroach.server.serverpb.TableIndexStatsResponse.ExtendedCollectedIndexUsageStatistics) | repeated | | [reserved](#support-status) |
| last_reset | [google.protobuf.Timestamp](#cockroach.server.serverpb.TableIndexStatsResponse-google.protobuf.Timestamp) | | Timestamp of the last index usage stats reset. | [reserved](#support-status) |






<a name="cockroach.server.serverpb.TableIndexStatsResponse-cockroach.server.serverpb.TableIndexStatsResponse.ExtendedCollectedIndexUsageStatistics"></a>
#### TableIndexStatsResponse.ExtendedCollectedIndexUsageStatistics



| Field | Type | Label | Description | Support status |
| ----- | ---- | ----- | ----------- | -------------- |
| statistics | [cockroach.sql.CollectedIndexUsageStatistics](#cockroach.server.serverpb.TableIndexStatsResponse-cockroach.sql.CollectedIndexUsageStatistics) | | | [reserved](#support-status) |
| index_name | [string](#cockroach.server.serverpb.TableIndexStatsResponse-string) | | IndexName is the name of the index. | [reserved](#support-status) |
| index_type | [string](#cockroach.server.serverpb.TableIndexStatsResponse-string) | | IndexType is the type of the index i.e. primary, secondary. | [reserved](#support-status) |






## RequestCA

`GET /_join/v1/ca`
Expand Down Expand Up @@ -4302,7 +4363,7 @@ TableDetailsRequest is a request for detailed information about a table.

| Field | Type | Label | Description | Support status |
| ----- | ---- | ----- | ----------- | -------------- |
| database | [string](#cockroach.server.serverpb.TableDetailsRequest-string) | | database is the database that contains the table we're interested in. | [reserved](#support-status) |
| database | [string](#cockroach.server.serverpb.TableDetailsRequest-string) | | database is the name of the database that contains the table we're interested in. | [reserved](#support-status) |
| table | [string](#cockroach.server.serverpb.TableDetailsRequest-string) | | table is the name of the table that we're querying. Table may be schema-qualified (schema.table) and each name component that contains sql unsafe characters such as . or uppercase letters must be surrounded in double quotes like "naughty schema".table. | [reserved](#support-status) |


Expand Down Expand Up @@ -4408,7 +4469,7 @@ information about a table.

| Field | Type | Label | Description | Support status |
| ----- | ---- | ----- | ----------- | -------------- |
| database | [string](#cockroach.server.serverpb.TableStatsRequest-string) | | database is the database that contains the table we're interested in. | [reserved](#support-status) |
| database | [string](#cockroach.server.serverpb.TableStatsRequest-string) | | database is the name of the database that contains the table we're interested in. | [reserved](#support-status) |
| table | [string](#cockroach.server.serverpb.TableStatsRequest-string) | | table is the name of the table that we're querying. Table may be schema-qualified (schema.table) and each name component that contains sql unsafe characters such as . or uppercase letters must be surrounded in double quotes like "naughty schema".table. | [reserved](#support-status) |


Expand Down
168 changes: 168 additions & 0 deletions pkg/server/index_usage_stats.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,16 @@ package server
import (
"context"
"fmt"
"strings"
"time"

"github.com/cockroachdb/cockroach/pkg/roachpb"
"github.com/cockroachdb/cockroach/pkg/security"
"github.com/cockroachdb/cockroach/pkg/server/serverpb"
"github.com/cockroachdb/cockroach/pkg/sql"
"github.com/cockroachdb/cockroach/pkg/sql/idxusage"
"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
"github.com/cockroachdb/cockroach/pkg/sql/sessiondata"
"github.com/cockroachdb/errors"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
Expand Down Expand Up @@ -116,6 +122,8 @@ func indexUsageStatsLocal(
return resp, nil
}

// ResetIndexUsageStats is the gRPC handler for resetting index usage stats.
// This endpoint resets index usage statistics for all tables.
func (s *statusServer) ResetIndexUsageStats(
ctx context.Context, req *serverpb.ResetIndexUsageStatsRequest,
) (*serverpb.ResetIndexUsageStatsResponse, error) {
Expand Down Expand Up @@ -176,3 +184,163 @@ func (s *statusServer) ResetIndexUsageStats(

return resp, nil
}

// TableIndexStats is the gRPC handler for retrieving index usage statistics
// by table. This function reads index usage statistics directly from the
// database and is meant for external usage.
func (s *statusServer) TableIndexStats(
ctx context.Context, req *serverpb.TableIndexStatsRequest,
) (*serverpb.TableIndexStatsResponse, error) {
ctx = propagateGatewayMetadata(ctx)
ctx = s.AnnotateCtx(ctx)

if _, err := s.privilegeChecker.requireAdminUser(ctx); err != nil {
return nil, err
}
return getTableIndexUsageStats(ctx, req, s.sqlServer.pgServer.SQLServer.GetLocalIndexStatistics(),
s.sqlServer.internalExecutor)
}

// getTableIndexUsageStats is a helper function that reads the indexes
// and their usage stats for a given database and table. This is meant
// for external usages e.g. front-end.
func getTableIndexUsageStats(
ctx context.Context,
req *serverpb.TableIndexStatsRequest,
idxUsageStatsProvider *idxusage.LocalIndexUsageStats,
ie *sql.InternalExecutor,
) (*serverpb.TableIndexStatsResponse, error) {

tableID, err := getTableIDFromDatabaseAndTableName(ctx, req.Database, req.Table, ie)

if err != nil {
return nil, err
}

q := makeSQLQuery()
q.Append(`
SELECT
ti.index_id,
ti.index_name,
ti.index_type,
total_reads,
last_read
FROM crdb_internal.index_usage_statistics AS us
JOIN crdb_internal.table_indexes ti
ON us.index_id = ti.index_id
AND us.table_id = ti.descriptor_id
WHERE ti.descriptor_id = $`,
tableID,
)

const expectedNumDatums = 5

it, err := ie.QueryIteratorEx(ctx, "index-usage-stats", nil,
sessiondata.InternalExecutorOverride{
User: security.NodeUserName(),
Database: req.Database,
}, q.String(), q.QueryArguments()...)

if err != nil {
return nil, err
}

var idxUsageStats []*serverpb.TableIndexStatsResponse_ExtendedCollectedIndexUsageStatistics
var ok bool

// We have to make sure to close the iterator since we might return from the
// for loop early (before Next() returns false).
defer func() { err = errors.CombineErrors(err, it.Close()) }()

for ok, err = it.Next(ctx); ok; ok, err = it.Next(ctx) {
var row tree.Datums
if row = it.Cur(); row == nil {
return nil, errors.New("unexpected null row")
}

if row.Len() != expectedNumDatums {
return nil, errors.Newf("expected %d columns, received %d", expectedNumDatums, row.Len())
}

indexID := tree.MustBeDInt(row[0])
indexName := tree.MustBeDString(row[1])
indexType := tree.MustBeDString(row[2])
totalReads := uint64(tree.MustBeDInt(row[3]))
lastRead := time.Time{}
if row[4] != tree.DNull {
lastRead = tree.MustBeDTimestampTZ(row[4]).Time
}

if err != nil {
return nil, err
}

idxStatsRow := &serverpb.TableIndexStatsResponse_ExtendedCollectedIndexUsageStatistics{
Statistics: &roachpb.CollectedIndexUsageStatistics{
Key: roachpb.IndexUsageKey{
TableID: roachpb.TableID(tableID),
IndexID: roachpb.IndexID(indexID),
},
Stats: roachpb.IndexUsageStatistics{
TotalReadCount: totalReads,
LastRead: lastRead,
},
},
IndexName: string(indexName),
IndexType: string(indexType),
}

idxUsageStats = append(idxUsageStats, idxStatsRow)
}

lastReset := idxUsageStatsProvider.GetLastReset()

resp := &serverpb.TableIndexStatsResponse{
Statistics: idxUsageStats,
LastReset: &lastReset,
}

return resp, nil
}

func getTableIDFromDatabaseAndTableName(
ctx context.Context, database string, table string, ie *sql.InternalExecutor,
) (int, error) {
// Fully qualified table name is either database.table or database.schema.table
fqtName, err := getFullyQualifiedTableName(database, table)
if err != nil {
return 0, err
}
names := strings.Split(fqtName, ".")

q := makeSQLQuery()
q.Append(`SELECT table_id FROM crdb_internal.tables `)
q.Append(`WHERE database_name = $ `, names[0])

if len(names) == 2 {
q.Append(`AND name = $`, names[1])
} else if len(names) == 3 {
q.Append(`AND schema_name = $ AND name = $`, names[1], names[2])
} else {
return 0, errors.Newf("expected array length 2 or 3, received %d", len(names))
}
if len(q.Errors()) > 0 {
return 0, combineAllErrors(q.Errors())
}

datums, err := ie.QueryRowEx(ctx, "get-table-id", nil,
sessiondata.InternalExecutorOverride{
User: security.NodeUserName(),
Database: database,
}, q.String(), q.QueryArguments()...)

if err != nil {
return 0, err
}
if datums == nil {
return 0, errors.Newf("expected to find table ID for table %s, but found nothing", fqtName)
}

tableID := int(tree.MustBeDInt(datums[0]))
return tableID, nil
}
6 changes: 4 additions & 2 deletions pkg/server/serverpb/admin.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions pkg/server/serverpb/admin.proto
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,8 @@ message DatabaseDetailsResponse {

// TableDetailsRequest is a request for detailed information about a table.
message TableDetailsRequest {
// database is the database that contains the table we're interested in.
// database is the name of the database that contains the table we're
// interested in.
string database = 1;

// table is the name of the table that we're querying. Table may be
Expand Down Expand Up @@ -213,7 +214,8 @@ message TableDetailsResponse {
// TableStatsRequest is a request for detailed, computationally expensive
// information about a table.
message TableStatsRequest {
// database is the database that contains the table we're interested in.
// database is the name of the database that contains the table we're
// interested in.
string database = 1;
// table is the name of the table that we're querying. Table may be
// schema-qualified (schema.table) and each name component that contains
Expand Down
3 changes: 2 additions & 1 deletion pkg/server/serverpb/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,14 @@ type SQLStatusServer interface {
ListContentionEvents(context.Context, *ListContentionEventsRequest) (*ListContentionEventsResponse, error)
ListLocalContentionEvents(context.Context, *ListContentionEventsRequest) (*ListContentionEventsResponse, error)
ResetSQLStats(context.Context, *ResetSQLStatsRequest) (*ResetSQLStatsResponse, error)
Statements(context.Context, *StatementsRequest) (*StatementsResponse, error)
CombinedStatementStats(context.Context, *CombinedStatementsStatsRequest) (*StatementsResponse, error)
Statements(context.Context, *StatementsRequest) (*StatementsResponse, error)
ListDistSQLFlows(context.Context, *ListDistSQLFlowsRequest) (*ListDistSQLFlowsResponse, error)
ListLocalDistSQLFlows(context.Context, *ListDistSQLFlowsRequest) (*ListDistSQLFlowsResponse, error)
Profile(ctx context.Context, request *ProfileRequest) (*JSONResponse, error)
IndexUsageStatistics(context.Context, *IndexUsageStatisticsRequest) (*IndexUsageStatisticsResponse, error)
ResetIndexUsageStats(context.Context, *ResetIndexUsageStatsRequest) (*ResetIndexUsageStatsResponse, error)
TableIndexStats(context.Context, *TableIndexStatsRequest) (*TableIndexStatsResponse, error)
}

// OptionalNodesStatusServer is a StatusServer that is only optionally present
Expand Down
Loading

0 comments on commit 9f0bd08

Please sign in to comment.