Skip to content

Commit

Permalink
server: update hot ranges api/v2 version
Browse files Browse the repository at this point in the history
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
  • Loading branch information
koorosh committed Jan 10, 2022
1 parent 8fc6c1a commit 76d43e6
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 47 deletions.
59 changes: 51 additions & 8 deletions docs/generated/swagger/spec.json
Original file line number Diff line number Diff line change
Expand Up @@ -1018,6 +1018,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 @@ -1769,6 +1775,46 @@
},
"x-go-package": "github.com/cockroachdb/cockroach/pkg/server"
},
"hotRangeInfo": {
"description": "Hot range details",
"type": "object",
"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"
},
"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 @@ -1778,15 +1824,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
59 changes: 33 additions & 26 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,27 @@ 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
//
// 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"`
ReplicaNodeIDs []roachpb.NodeID `json:"replica_node_ids"`
}

// swagger:operation GET /ranges/hot/ listHotRanges
//
// List hot ranges
Expand Down Expand Up @@ -464,9 +477,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 +495,28 @@ 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 []hotRangeInfo
for _, r := range resp.Ranges {
hotRangeInfos = append(hotRangeInfos, 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,
})
}
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

0 comments on commit 76d43e6

Please sign in to comment.