From c6faa8bd8f1b4f8b9a8fa67e1786d92f0032852c Mon Sep 17 00:00:00 2001 From: Eugene Kalinin Date: Mon, 2 Dec 2019 11:00:00 +0300 Subject: [PATCH] PCP: /api/v1/regions optimization (#1970) --- server/api/region.go | 45 +++++++++++++++---------- server/api/region_test.go | 69 +++++++++++++++++++++++++++++++++++++++ server/api/router.go | 21 +++++++++--- server/core/region.go | 50 +++++++++++++++++++++++++++- 4 files changed, 162 insertions(+), 23 deletions(-) diff --git a/server/api/region.go b/server/api/region.go index c5de8c091d12..4fdb52f229f3 100644 --- a/server/api/region.go +++ b/server/api/region.go @@ -45,23 +45,29 @@ type RegionInfo struct { // NewRegionInfo create a new api RegionInfo. func NewRegionInfo(r *core.RegionInfo) *RegionInfo { + return InitRegion(r, &RegionInfo{}) +} + +// InitRegion init a new api RegionInfo from the core.RegionInfo. +func InitRegion(r *core.RegionInfo, s *RegionInfo) *RegionInfo { if r == nil { return nil } - return &RegionInfo{ - ID: r.GetID(), - StartKey: string(core.HexRegionKey(r.GetStartKey())), - EndKey: string(core.HexRegionKey(r.GetEndKey())), - RegionEpoch: r.GetRegionEpoch(), - Peers: r.GetPeers(), - Leader: r.GetLeader(), - DownPeers: r.GetDownPeers(), - PendingPeers: r.GetPendingPeers(), - WrittenBytes: r.GetBytesWritten(), - ReadBytes: r.GetBytesRead(), - ApproximateSize: r.GetApproximateSize(), - ApproximateKeys: r.GetApproximateKeys(), - } + + s.ID = r.GetID() + s.StartKey = core.HexRegionKeyStr(r.GetStartKey()) + s.EndKey = core.HexRegionKeyStr(r.GetEndKey()) + s.RegionEpoch = r.GetRegionEpoch() + s.Peers = r.GetPeers() + s.Leader = r.GetLeader() + s.DownPeers = r.GetDownPeers() + s.PendingPeers = r.GetPendingPeers() + s.WrittenBytes = r.GetBytesWritten() + s.ReadBytes = r.GetBytesRead() + s.ApproximateSize = r.GetApproximateSize() + s.ApproximateKeys = r.GetApproximateKeys() + + return s } // RegionsInfo contains some regions with the detailed region info. @@ -126,13 +132,18 @@ func newRegionsHandler(svr *server.Server, rd *render.Render) *regionsHandler { } func convertToAPIRegions(regions []*core.RegionInfo) *RegionsInfo { - regionInfos := make([]*RegionInfo, len(regions)) + regionInfos := make([]RegionInfo, len(regions)) + regionInfosRefs := make([]*RegionInfo, len(regions)) + + for i := 0; i < len(regions); i++ { + regionInfosRefs[i] = ®ionInfos[i] + } for i, r := range regions { - regionInfos[i] = NewRegionInfo(r) + regionInfosRefs[i] = InitRegion(r, regionInfosRefs[i]) } return &RegionsInfo{ Count: len(regions), - Regions: regionInfos, + Regions: regionInfosRefs, } } diff --git a/server/api/region_test.go b/server/api/region_test.go index c1c59d08ca77..a2c3874895e5 100644 --- a/server/api/region_test.go +++ b/server/api/region_test.go @@ -14,10 +14,12 @@ package api import ( + "bytes" "fmt" "math/rand" "net/url" "sort" + "testing" . "github.com/pingcap/check" "github.com/pingcap/kvproto/pkg/metapb" @@ -320,3 +322,70 @@ func (s *testGetRegionSuite) TestScanRegionByKey(c *C) { c.Assert(v, Equals, regions.Regions[i].ID) } } + +// Create n regions (0..n) of n stores (0..n). +// Each region contains np peers, the first peer is the leader. +// (copied from server/cluster_test.go) +func newTestRegions() []*core.RegionInfo { + n := uint64(10000) + np := uint64(3) + + regions := make([]*core.RegionInfo, 0, n) + for i := uint64(0); i < n; i++ { + peers := make([]*metapb.Peer, 0, np) + for j := uint64(0); j < np; j++ { + peer := &metapb.Peer{ + Id: i*np + j, + } + peer.StoreId = (i + j) % n + peers = append(peers, peer) + } + region := &metapb.Region{ + Id: i, + Peers: peers, + StartKey: []byte(fmt.Sprintf("%d", i)), + EndKey: []byte(fmt.Sprintf("%d", i+1)), + RegionEpoch: &metapb.RegionEpoch{ConfVer: 2, Version: 2}, + } + regions = append(regions, core.NewRegionInfo(region, peers[0])) + } + return regions +} + +func BenchmarkRenderJSON(b *testing.B) { + regionInfos := newTestRegions() + rd := createStreamingRender() + regions := convertToAPIRegions(regionInfos) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + var buffer bytes.Buffer + rd.JSON(&buffer, 200, regions) + } +} + +func BenchmarkConvertToAPIRegions(b *testing.B) { + regionInfos := newTestRegions() + + b.ResetTimer() + for i := 0; i < b.N; i++ { + regions := convertToAPIRegions(regionInfos) + _ = regions.Count + } +} + +func BenchmarkHexRegionKey(b *testing.B) { + key := []byte("region_number_infinity") + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = core.HexRegionKey(key) + } +} + +func BenchmarkHexRegionKeyStr(b *testing.B) { + key := []byte("region_number_infinity") + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = core.HexRegionKeyStr(key) + } +} diff --git a/server/api/router.go b/server/api/router.go index da3bd0bb037c..edf2c3fda606 100644 --- a/server/api/router.go +++ b/server/api/router.go @@ -22,12 +22,20 @@ import ( "github.com/unrolled/render" ) -const pingAPI = "/ping" +func createStreamingRender() *render.Render { + return render.New(render.Options{ + StreamingJSON: true, + }) +} -func createRouter(prefix string, svr *server.Server) *mux.Router { - rd := render.New(render.Options{ +func createIndentRender() *render.Render { + return render.New(render.Options{ IndentJSON: true, }) +} + +func createRouter(prefix string, svr *server.Server) *mux.Router { + rd := createIndentRender() router := mux.NewRouter().PathPrefix(prefix).Subrouter() handler := svr.GetHandler() @@ -88,8 +96,11 @@ func createRouter(prefix string, svr *server.Server) *mux.Router { router.HandleFunc("/api/v1/region/id/{id}", regionHandler.GetRegionByID).Methods("GET") router.HandleFunc("/api/v1/region/key/{key}", regionHandler.GetRegionByKey).Methods("GET") + srd := createStreamingRender() + regionsAllHandler := newRegionsHandler(svr, srd) + router.HandleFunc("/api/v1/regions", regionsAllHandler.GetAll).Methods("GET") + regionsHandler := newRegionsHandler(svr, rd) - router.HandleFunc("/api/v1/regions", regionsHandler.GetAll).Methods("GET") router.HandleFunc("/api/v1/regions/key", regionsHandler.ScanRegionsByKey).Methods("GET") router.HandleFunc("/api/v1/regions/store/{id}", regionsHandler.GetStoreRegions).Methods("GET") router.HandleFunc("/api/v1/regions/writeflow", regionsHandler.GetTopWriteFlow).Methods("GET") @@ -136,7 +147,7 @@ func createRouter(prefix string, svr *server.Server) *mux.Router { logHanler := newlogHandler(svr, rd) router.HandleFunc("/api/v1/admin/log", logHanler.Handle).Methods("POST") - router.HandleFunc(pingAPI, func(w http.ResponseWriter, r *http.Request) {}).Methods("GET") + router.HandleFunc("/ping", func(w http.ResponseWriter, r *http.Request) {}).Methods("GET") router.Handle("/health", newHealthHandler(svr, rd)).Methods("GET") router.Handle("/diagnose", newDiagnoseHandler(svr, rd)).Methods("GET") return router diff --git a/server/core/region.go b/server/core/region.go index e04719411d55..980781becd14 100644 --- a/server/core/region.go +++ b/server/core/region.go @@ -20,6 +20,7 @@ import ( "math/rand" "reflect" "strings" + "unsafe" "github.com/gogo/protobuf/proto" "github.com/pingcap/kvproto/pkg/metapb" @@ -816,10 +817,57 @@ func DiffRegionKeyInfo(origin *RegionInfo, other *RegionInfo) string { return strings.Join(ret, ", ") } +// String converts slice of bytes to string without copy. +func String(b []byte) (s string) { + if len(b) == 0 { + return "" + } + pbytes := (*reflect.SliceHeader)(unsafe.Pointer(&b)) + pstring := (*reflect.StringHeader)(unsafe.Pointer(&s)) + pstring.Data = pbytes.Data + pstring.Len = pbytes.Len + return +} + +// ToUpperASCIIInplace bytes.ToUpper but zero-cost +func ToUpperASCIIInplace(s []byte) []byte { + hasLower := false + for i := 0; i < len(s); i++ { + c := s[i] + hasLower = hasLower || ('a' <= c && c <= 'z') + } + + if !hasLower { + return s + } + var c byte + for i := 0; i < len(s); i++ { + c = s[i] + if 'a' <= c && c <= 'z' { + c -= 'a' - 'A' + } + s[i] = c + } + return s +} + +// EncodeToString overrides hex.EncodeToString implementation. Difference: returns []byte, not string +func EncodeToString(src []byte) []byte { + dst := make([]byte, hex.EncodedLen(len(src))) + hex.Encode(dst, src) + return dst +} + // HexRegionKey converts region key to hex format. Used for formating region in // logs. func HexRegionKey(key []byte) []byte { - return []byte(strings.ToUpper(hex.EncodeToString(key))) + return ToUpperASCIIInplace(EncodeToString(key)) +} + +// HexRegionKeyStr converts region key to hex format. Used for formating region in +// logs. +func HexRegionKeyStr(key []byte) string { + return String(HexRegionKey(key)) } // RegionToHexMeta converts a region meta's keys to hex format. Used for formating