diff --git a/server/api/region.go b/server/api/region.go index 7d2b632dcbbe..1db9bac2d501 100644 --- a/server/api/region.go +++ b/server/api/region.go @@ -24,6 +24,7 @@ import ( "github.com/pingcap/kvproto/pkg/metapb" "github.com/pingcap/kvproto/pkg/pdpb" "github.com/pingcap/kvproto/pkg/replication_modepb" + "github.com/pingcap/pd/v4/pkg/apiutil" "github.com/pingcap/pd/v4/server" "github.com/pingcap/pd/v4/server/core" "github.com/unrolled/render" @@ -557,6 +558,59 @@ func (h *regionsHandler) GetTopSize(w http.ResponseWriter, r *http.Request) { }) } +// @Tags region +// @Summary Accelerate regions schedule in given range +// @Accept json +// @Param body body object true "json params" +// @Produce json +// @Param limit query integer false "Limit count" default(16) +// @Produce json +// @Success 200 {object} string "The regions in given range have been improve schedule priority" +// @Failure 400 {string} string "The input is invalid." +// @Router /regions/keys/accelerate-schedule [post] +func (h *regionsHandler) AccelerateRegionsScheduleInRange(w http.ResponseWriter, r *http.Request) { + rc := getCluster(r.Context()) + var input map[string]interface{} + if err := apiutil.ReadJSONRespondError(h.rd, w, r.Body, &input); err != nil { + return + } + startKey, err := parseHexRequestBodyAttribute("start_key", input) + if err != nil { + h.rd.JSON(w, http.StatusBadRequest, err.Error()) + return + } + + endKey, err := parseHexRequestBodyAttribute("end_key", input) + if err != nil { + h.rd.JSON(w, http.StatusBadRequest, err.Error()) + return + } + + limit := defaultRegionLimit + if limitStr := r.URL.Query().Get("limit"); limitStr != "" { + var err error + limit, err = strconv.Atoi(limitStr) + if err != nil { + h.rd.JSON(w, http.StatusBadRequest, err.Error()) + return + } + } + if limit > maxRegionLimit { + limit = maxRegionLimit + } + + regions := rc.ScanRegions([]byte(startKey), []byte(endKey), limit) + if len(regions) > 0 { + regionsIDList := make([]uint64, 0, len(regions)) + for _, region := range regions { + regionsIDList = append(regionsIDList, region.GetID()) + } + rc.AddSuspectRegions(regionsIDList...) + } + + h.rd.JSON(w, http.StatusOK, nil) +} + func (h *regionsHandler) GetTopNRegions(w http.ResponseWriter, r *http.Request, less func(a, b *core.RegionInfo) bool) { rc := getCluster(r.Context()) limit := defaultRegionLimit diff --git a/server/api/region_test.go b/server/api/region_test.go index 633d9a7d2def..e5a3d7634660 100644 --- a/server/api/region_test.go +++ b/server/api/region_test.go @@ -258,6 +258,18 @@ func (s *testRegionSuite) TestTopSize(c *C) { s.checkTopRegions(c, fmt.Sprintf("%s/regions/size?limit=%d", s.urlPrefix, 2), []uint64{7, 8}) } +func (s *testRegionSuite) TestImproveRegionsSchedulePriorityInRange(c *C) { + r1 := newTestRegionInfo(1, 1, []byte("a"), []byte("b"), core.SetWrittenBytes(1000), core.SetReadBytes(1000), core.SetRegionConfVer(1), core.SetRegionVersion(1)) + mustRegionHeartbeat(c, s.svr, r1) + r2 := newTestRegionInfo(2, 1, []byte("b"), []byte("c"), core.SetWrittenBytes(2000), core.SetReadBytes(0), core.SetRegionConfVer(2), core.SetRegionVersion(3)) + mustRegionHeartbeat(c, s.svr, r2) + r3 := newTestRegionInfo(3, 1, []byte("c"), []byte("d"), core.SetWrittenBytes(500), core.SetReadBytes(800), core.SetRegionConfVer(3), core.SetRegionVersion(2)) + mustRegionHeartbeat(c, s.svr, r3) + + err := postJSON(testDialClient, fmt.Sprintf("%s/regions/keys/accelerate-schedule", s.urlPrefix), []byte(`{"start_key":"b", "end_key": "d"}`)) + c.Assert(err, IsNil) +} + func (s *testRegionSuite) checkTopRegions(c *C, url string, regionIDs []uint64) { regions := &RegionsInfo{} err := readJSON(testDialClient, url, regions) diff --git a/server/api/router.go b/server/api/router.go index b09e7bd629e1..893747b7f1a7 100644 --- a/server/api/router.go +++ b/server/api/router.go @@ -148,6 +148,7 @@ func createRouter(ctx context.Context, prefix string, svr *server.Server) *mux.R clusterRouter.HandleFunc("/regions/check/hist-size", regionsHandler.GetSizeHistogram).Methods("GET") clusterRouter.HandleFunc("/regions/check/hist-keys", regionsHandler.GetKeysHistogram).Methods("GET") clusterRouter.HandleFunc("/regions/sibling/{id}", regionsHandler.GetRegionSiblings).Methods("GET") + clusterRouter.HandleFunc("/regions/keys/accelerate-schedule", regionsHandler.AccelerateRegionsScheduleInRange).Methods("POST") apiRouter.Handle("/version", newVersionHandler(rd)).Methods("GET") apiRouter.Handle("/status", newStatusHandler(svr, rd)).Methods("GET") diff --git a/server/api/util.go b/server/api/util.go index d5d6be8e47d3..579e72eafbd3 100644 --- a/server/api/util.go +++ b/server/api/util.go @@ -16,6 +16,7 @@ package api import ( "bytes" "encoding/json" + "fmt" "io/ioutil" "net/http" "net/url" @@ -108,3 +109,15 @@ func doDelete(client *http.Client, url string) (*http.Response, error) { res.Body.Close() return res, nil } + +func parseHexRequestBodyAttribute(name string, input map[string]interface{}) (string, error) { + k, ok := input[name] + if !ok { + return "", fmt.Errorf("missing %s", name) + } + key, ok := k.(string) + if !ok { + return "", fmt.Errorf("bad format %s", name) + } + return key, nil +}