Skip to content

Commit

Permalink
server: update hot ranges api/v2 version
Browse files Browse the repository at this point in the history
Before, `listHotRanges` request handler of `apiV2Server` relied
on `HotRanges` service that is now should be replaced by new
`HotRangesV2` implementation.

Current change reuses HotRangeV2 service in `api/v2/ranges/hot`
api. It allows to share the same logic between REST and gRPC
endpoints and gradually migrate to new version of API.

Release note: None

Release justification: bug fixes and low-risk updates
to new functionality
  • Loading branch information
koorosh committed Mar 2, 2022
1 parent c248ac6 commit af84b8a
Show file tree
Hide file tree
Showing 6 changed files with 135 additions and 66 deletions.
5 changes: 3 additions & 2 deletions docs/generated/http/full.md
Original file line number Diff line number Diff line change
Expand Up @@ -3248,7 +3248,7 @@ of ranges currently considered “hot” by the node(s).




HotRangesResponseV2 is a response payload returned by `HotRangesV2` service.


| Field | Type | Label | Description | Support status |
Expand All @@ -3263,7 +3263,7 @@ of ranges currently considered “hot” by the node(s).
<a name="cockroach.server.serverpb.HotRangesResponseV2-cockroach.server.serverpb.HotRangesResponseV2.HotRange"></a>
#### HotRangesResponseV2.HotRange


HotRange message describes a single hot range, ie its QPS, node ID it belongs to, etc.

| Field | Type | Label | Description | Support status |
| ----- | ---- | ----- | ----------- | -------------- |
Expand All @@ -3275,6 +3275,7 @@ of ranges currently considered “hot” by the node(s).
| index_name | [string](#cockroach.server.serverpb.HotRangesResponseV2-string) | | index_name indicates the index name for current range | [reserved](#support-status) |
| replica_node_ids | [int32](#cockroach.server.serverpb.HotRangesResponseV2-int32) | repeated | replica_node_ids specifies the list of node ids that contain replicas with current hot range | [reserved](#support-status) |
| leaseholder_node_id | [int32](#cockroach.server.serverpb.HotRangesResponseV2-int32) | | leaseholder_node_id indicates on Node ID that contains replica that is a leaseholder | [reserved](#support-status) |
| schema_name | [string](#cockroach.server.serverpb.HotRangesResponseV2-string) | | schema_name provides the name of schema (if exists) for table in current range | [reserved](#support-status) |



Expand Down
64 changes: 56 additions & 8 deletions docs/generated/swagger/spec.json
Original file line number Diff line number Diff line change
Expand Up @@ -1023,6 +1023,12 @@
},
"x-go-package": "github.com/cockroachdb/cockroach/pkg/util/metric"
},
"RangeID": {
"type": "integer",
"format": "int64",
"title": "A RangeID is a unique ID associated to a Raft consensus group.",
"x-go-package": "github.com/cockroachdb/cockroach/pkg/roachpb"
},
"RangeProblems": {
"type": "object",
"title": "RangeProblems describes issues reported by a range. For internal use only.",
Expand Down Expand Up @@ -1788,6 +1794,51 @@
},
"x-go-package": "github.com/cockroachdb/cockroach/pkg/server"
},
"hotRangeInfo": {
"description": "(ie its range ID, QPS, table name, etc.).",
"type": "object",
"title": "Hot range details struct describes common information about hot range,",
"properties": {
"database_name": {
"type": "string",
"x-go-name": "DatabaseName"
},
"index_name": {
"type": "string",
"x-go-name": "IndexName"
},
"leaseholder_node_id": {
"$ref": "#/definitions/NodeID"
},
"node_id": {
"$ref": "#/definitions/NodeID"
},
"qps": {
"type": "number",
"format": "double",
"x-go-name": "QPS"
},
"range_id": {
"$ref": "#/definitions/RangeID"
},
"replica_node_ids": {
"type": "array",
"items": {
"$ref": "#/definitions/NodeID"
},
"x-go-name": "ReplicaNodeIDs"
},
"schema_name": {
"type": "string",
"x-go-name": "SchemaName"
},
"table_name": {
"type": "string",
"x-go-name": "TableName"
}
},
"x-go-package": "github.com/cockroachdb/cockroach/pkg/server"
},
"hotRangesResponse": {
"type": "object",
"title": "Response struct for listHotRanges.",
Expand All @@ -1797,15 +1848,12 @@
"type": "string",
"x-go-name": "Next"
},
"ranges_by_node_id": {
"type": "object",
"additionalProperties": {
"type": "array",
"items": {
"$ref": "#/definitions/rangeDescriptorInfo"
}
"ranges": {
"type": "array",
"items": {
"$ref": "#/definitions/hotRangeInfo"
},
"x-go-name": "RangesByNodeID"
"x-go-name": "Ranges"
},
"response_error": {
"type": "array",
Expand Down
60 changes: 35 additions & 25 deletions pkg/server/api_v2_ranges.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import (
"context"
"fmt"
"net/http"
"sort"
"strconv"
"strings"

Expand Down Expand Up @@ -415,13 +414,29 @@ type responseError struct {
//
// swagger:model hotRangesResponse
type hotRangesResponse struct {
RangesByNodeID map[string][]rangeDescriptorInfo `json:"ranges_by_node_id"`
Errors []responseError `json:"response_error,omitempty"`
Ranges []hotRangeInfo `json:"ranges"`
Errors []responseError `json:"response_error,omitempty"`
// Continuation token for the next paginated call. Use as the `start`
// parameter.
Next string `json:"next,omitempty"`
}

// Hot range details struct describes common information about hot range,
// (ie its range ID, QPS, table name, etc.).
//
// swagger:model hotRangeInfo
type hotRangeInfo struct {
RangeID roachpb.RangeID `json:"range_id"`
NodeID roachpb.NodeID `json:"node_id"`
QPS float64 `json:"qps"`
LeaseholderNodeID roachpb.NodeID `json:"leaseholder_node_id"`
TableName string `json:"table_name"`
DatabaseName string `json:"database_name"`
IndexName string `json:"index_name"`
SchemaName string `json:"schema_name"`
ReplicaNodeIDs []roachpb.NodeID `json:"replica_node_ids"`
}

// swagger:operation GET /ranges/hot/ listHotRanges
//
// List hot ranges
Expand Down Expand Up @@ -464,9 +479,7 @@ func (a *apiV2Server) listHotRanges(w http.ResponseWriter, r *http.Request) {
nodeIDStr := r.URL.Query().Get("node_id")
limit, start := getRPCPaginationValues(r)

response := &hotRangesResponse{
RangesByNodeID: make(map[string][]rangeDescriptorInfo),
}
response := &hotRangesResponse{}
var requestedNodes []roachpb.NodeID
if len(nodeIDStr) > 0 {
requestedNodeID, _, err := a.status.parseNodeID(nodeIDStr)
Expand All @@ -484,32 +497,29 @@ func (a *apiV2Server) listHotRanges(w http.ResponseWriter, r *http.Request) {
remoteRequest := serverpb.HotRangesRequest{NodeID: "local"}
nodeFn := func(ctx context.Context, client interface{}, nodeID roachpb.NodeID) (interface{}, error) {
status := client.(serverpb.StatusClient)
resp, err := status.HotRanges(ctx, &remoteRequest)
resp, err := status.HotRangesV2(ctx, &remoteRequest)
if err != nil || resp == nil {
return nil, err
}
rangeDescriptorInfos := make([]rangeDescriptorInfo, 0)
for _, store := range resp.HotRangesByNodeID[nodeID].Stores {
for _, hotRange := range store.HotRanges {
var r rangeDescriptorInfo
r.init(&hotRange.Desc)
r.StoreID = int32(store.StoreID)
r.QueriesPerSecond = hotRange.QueriesPerSecond
rangeDescriptorInfos = append(rangeDescriptorInfos, r)

var hotRangeInfos = make([]hotRangeInfo, len(resp.Ranges))
for i, r := range resp.Ranges {
hotRangeInfos[i] = hotRangeInfo{
RangeID: r.RangeID,
NodeID: r.NodeID,
QPS: r.QPS,
LeaseholderNodeID: r.LeaseholderNodeID,
TableName: r.TableName,
DatabaseName: r.DatabaseName,
IndexName: r.IndexName,
ReplicaNodeIDs: r.ReplicaNodeIds,
SchemaName: r.SchemaName,
}
}
sort.Slice(rangeDescriptorInfos, func(i, j int) bool {
if rangeDescriptorInfos[i].StoreID == rangeDescriptorInfos[j].StoreID {
return rangeDescriptorInfos[i].RangeID < rangeDescriptorInfos[j].RangeID
}
return rangeDescriptorInfos[i].StoreID < rangeDescriptorInfos[j].StoreID
})
return rangeDescriptorInfos, nil
return hotRangeInfos, nil
}
responseFn := func(nodeID roachpb.NodeID, resp interface{}) {
if hotRangesResp, ok := resp.([]rangeDescriptorInfo); ok {
response.RangesByNodeID[nodeID.String()] = hotRangesResp
}
response.Ranges = append(response.Ranges, resp.([]hotRangeInfo)...)
}
errorFn := func(nodeID roachpb.NodeID, err error) {
response.Errors = append(response.Errors, responseError{
Expand Down
17 changes: 4 additions & 13 deletions pkg/server/api_v2_ranges_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,25 +46,16 @@ func TestHotRangesV2(t *testing.T) {
require.NoError(t, json.NewDecoder(resp.Body).Decode(&hotRangesResp))
require.NoError(t, resp.Body.Close())

if len(hotRangesResp.RangesByNodeID) == 0 {
if len(hotRangesResp.Ranges) == 0 {
t.Fatalf("didn't get hot range responses from any nodes")
}
if len(hotRangesResp.Errors) > 0 {
t.Errorf("got an error in hot range response from n%d: %v",
hotRangesResp.Errors[0].NodeID, hotRangesResp.Errors[0].ErrorMessage)
}

for nodeID, nodeResp := range hotRangesResp.RangesByNodeID {
if len(nodeResp) == 0 {
t.Fatalf("didn't get hot range response from node n%s", nodeID)
}
// We don't check for ranges being sorted by QPS, as this hot ranges
// report does not use that as its sort key (for stability across multiple
// pagination calls).
for _, r := range nodeResp {
if r.RangeID == 0 || (len(r.StartKey) == 0 && len(r.EndKey) == 0) {
t.Errorf("unexpected empty/unpopulated range descriptor: %+v", r)
}
for _, r := range hotRangesResp.Ranges {
if r.RangeID == 0 || r.NodeID == 0 {
t.Errorf("unexpected empty/unpopulated range descriptor: %+v", r)
}
}
}
Expand Down
4 changes: 4 additions & 0 deletions pkg/server/serverpb/status.proto
Original file line number Diff line number Diff line change
Expand Up @@ -1194,7 +1194,9 @@ message HotRangesResponse {
];
}

// HotRangesResponseV2 is a response payload returned by `HotRangesV2` service.
message HotRangesResponseV2 {
// HotRange message describes a single hot range, ie its QPS, node ID it belongs to, etc.
message HotRange {
// range_id indicates Range ID that's identified as hot range
int32 range_id = 1 [
Expand Down Expand Up @@ -1228,6 +1230,8 @@ message HotRangesResponseV2 {
(gogoproto.casttype) =
"github.com/cockroachdb/cockroach/pkg/roachpb.NodeID"
];
// schema_name provides the name of schema (if exists) for table in current range
string schema_name = 9;
}
// ranges contain list of hot ranges info that has highest number of QPS
repeated HotRange ranges = 1;
Expand Down
51 changes: 33 additions & 18 deletions pkg/server/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -2107,6 +2107,17 @@ func (s *statusServer) HotRanges(
return response, nil
}

type hotRangeReportMeta struct {
dbName string
tableName string
schemaName string
indexNames map[uint32]string
parentID uint32
schemaParentID uint32
}

// HotRangesV2 returns hot ranges from all stores on requested node or all nodes in case
// request message doesn't include specific node ID.
func (s *statusServer) HotRangesV2(
ctx context.Context, req *serverpb.HotRangesRequest,
) (*serverpb.HotRangesResponseV2, error) {
Expand All @@ -2115,11 +2126,7 @@ func (s *statusServer) HotRangesV2(
return nil, err
}

dbNames := make(map[uint32]string)
tableNames := make(map[uint32]string)
indexNames := make(map[uint32]map[uint32]string)
parents := make(map[uint32]uint32)

rangeReportMetas := make(map[uint32]hotRangeReportMeta)
var descrs []catalog.Descriptor
if err = s.sqlServer.distSQLServer.CollectionFactory.Txn(
ctx, s.sqlServer.internalExecutor, s.db,
Expand All @@ -2136,42 +2143,49 @@ func (s *statusServer) HotRangesV2(

for _, desc := range descrs {
id := uint32(desc.GetID())
meta := hotRangeReportMeta{
indexNames: map[uint32]string{},
}
switch desc := desc.(type) {
case catalog.TableDescriptor:
parents[id] = uint32(desc.GetParentID())
tableNames[id] = desc.GetName()
indexNames[id] = make(map[uint32]string)
meta.tableName = desc.GetName()
meta.parentID = uint32(desc.GetParentID())
meta.schemaParentID = uint32(desc.GetParentSchemaID())
for _, idx := range desc.AllIndexes() {
indexNames[id][uint32(idx.GetID())] = idx.GetName()
meta.indexNames[uint32(idx.GetID())] = idx.GetName()
}
case catalog.SchemaDescriptor:
meta.schemaName = desc.GetName()
case catalog.DatabaseDescriptor:
dbNames[id] = desc.GetName()
meta.dbName = desc.GetName()
}
rangeReportMetas[id] = meta
}

var ranges []*serverpb.HotRangesResponseV2_HotRange
// TODO (koorosh): how to flatten triple nested loop?
for nodeID, hr := range resp.HotRangesByNodeID {
for _, store := range hr.Stores {
for _, r := range store.HotRanges {
var (
dbName, tableName, indexName string
replicaNodeIDs []roachpb.NodeID
dbName, tableName, indexName, schemaName string
replicaNodeIDs []roachpb.NodeID
)
_, tableID, err := s.sqlServer.execCfg.Codec.DecodeTablePrefix(r.Desc.StartKey.AsRawKey())
if err != nil {
continue
}
parent := parents[tableID]
parent := rangeReportMetas[tableID].parentID
if parent != 0 {
tableName = tableNames[tableID]
dbName = dbNames[parent]
tableName = rangeReportMetas[tableID].tableName
dbName = rangeReportMetas[parent].dbName
} else {
dbName = dbNames[tableID]
dbName = rangeReportMetas[tableID].dbName
}
schemaParent := rangeReportMetas[tableID].schemaParentID
schemaName = rangeReportMetas[schemaParent].schemaName
_, _, idxID, err := s.sqlServer.execCfg.Codec.DecodeIndexPrefix(r.Desc.StartKey.AsRawKey())
if err == nil {
indexName = indexNames[tableID][idxID]
indexName = rangeReportMetas[tableID].indexNames[idxID]
}
for _, repl := range r.Desc.Replicas().Descriptors() {
replicaNodeIDs = append(replicaNodeIDs, repl.NodeID)
Expand All @@ -2181,6 +2195,7 @@ func (s *statusServer) HotRangesV2(
NodeID: nodeID,
QPS: r.QueriesPerSecond,
TableName: tableName,
SchemaName: schemaName,
DatabaseName: dbName,
IndexName: indexName,
ReplicaNodeIds: replicaNodeIDs,
Expand Down

0 comments on commit af84b8a

Please sign in to comment.