From 76d43e646d32a9ff458620589a4801129dcae9e6 Mon Sep 17 00:00:00 2001 From: Andrii Vorobiov Date: Mon, 10 Jan 2022 12:14:13 +0200 Subject: [PATCH] server: update hot ranges api/v2 version 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 --- docs/generated/swagger/spec.json | 59 +++++++++++++++++++++++++++----- pkg/server/api_v2_ranges.go | 59 ++++++++++++++++++-------------- pkg/server/api_v2_ranges_test.go | 17 +++------ 3 files changed, 88 insertions(+), 47 deletions(-) diff --git a/docs/generated/swagger/spec.json b/docs/generated/swagger/spec.json index 7213cf361fb0..6c1f6f3c8d3a 100644 --- a/docs/generated/swagger/spec.json +++ b/docs/generated/swagger/spec.json @@ -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.", @@ -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.", @@ -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", diff --git a/pkg/server/api_v2_ranges.go b/pkg/server/api_v2_ranges.go index 9794efe0f846..f0db6dbbaac8 100644 --- a/pkg/server/api_v2_ranges.go +++ b/pkg/server/api_v2_ranges.go @@ -14,7 +14,6 @@ import ( "context" "fmt" "net/http" - "sort" "strconv" "strings" @@ -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 @@ -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) @@ -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{ diff --git a/pkg/server/api_v2_ranges_test.go b/pkg/server/api_v2_ranges_test.go index bfffae1c4d4f..74af7708c466 100644 --- a/pkg/server/api_v2_ranges_test.go +++ b/pkg/server/api_v2_ranges_test.go @@ -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) } } }