Skip to content

Commit

Permalink
This commit provides a new implementation of systemStatusServer.SpanS…
Browse files Browse the repository at this point in the history
…tats,

proposed in cockroachdb#84105. The goal is to provide accurate table/index stats since
the assumption that 1 range can contain at most 1 table/index no longer holds.

Part of: https://cockroachlabs.atlassian.net/browse/CRDB-22711
Release note: None
  • Loading branch information
Zach Lite committed Jan 30, 2023
1 parent 10ef5d9 commit 13ca09a
Show file tree
Hide file tree
Showing 7 changed files with 136 additions and 36 deletions.
26 changes: 14 additions & 12 deletions pkg/kv/kvserver/client_status_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,17 +76,19 @@ func TestComputeStatsForKeySpan(t *testing.T) {
{"b", "e", 2, 5},
{"e", "i", 2, 1},
} {
start, end := tcase.startKey, tcase.endKey
result, err := store.ComputeStatsForKeySpan(
roachpb.RKey(start), roachpb.RKey(end))
if err != nil {
t.Fatal(err)
}
if a, e := result.ReplicaCount, tcase.expectedRanges; a != e {
t.Errorf("Expected %d ranges in span [%s - %s], found %d", e, start, end, a)
}
if a, e := result.MVCC.LiveCount, tcase.expectedKeys; a != e {
t.Errorf("Expected %d keys in span [%s - %s], found %d", e, start, end, a)
}
_ = tcase
t.Errorf("implement me")
//start, end := tcase.startKey, tcase.endKey
//result, err := store.ComputeStatsForKeySpan(
// roachpb.RKey(start), roachpb.RKey(end))
//if err != nil {
// t.Fatal(err)
//}
//if a, e := result.ReplicaCount, tcase.expectedRanges; a != e {
// t.Errorf("Expected %d ranges in span [%s - %s], found %d", e, start, end, a)
//}
//if a, e := result.MVCC.LiveCount, tcase.expectedKeys; a != e {
// t.Errorf("Expected %d keys in span [%s - %s], found %d", e, start, end, a)
//}
}
}
20 changes: 0 additions & 20 deletions pkg/kv/kvserver/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -3076,26 +3076,6 @@ type StoreKeySpanStats struct {
ApproximateDiskBytes uint64
}

// ComputeStatsForKeySpan computes the aggregated MVCCStats for all replicas on
// this store which contain any keys in the supplied range.
func (s *Store) ComputeStatsForKeySpan(startKey, endKey roachpb.RKey) (StoreKeySpanStats, error) {
var result StoreKeySpanStats

newStoreReplicaVisitor(s).UndefinedOrder().Visit(func(repl *Replica) bool {
desc := repl.Desc()
if bytes.Compare(startKey, desc.EndKey) >= 0 || bytes.Compare(desc.StartKey, endKey) >= 0 {
return true // continue
}
result.MVCC.Add(repl.GetMVCCStats())
result.ReplicaCount++
return true
})

var err error
result.ApproximateDiskBytes, err = s.engine.ApproximateDiskBytes(startKey.AsRawKey(), endKey.AsRawKey())
return result, err
}

// ReplicateQueueDryRun runs the given replica through the replicate queue
// (using the allocator) without actually carrying out any changes, returning
// all trace messages collected along the way.
Expand Down
1 change: 1 addition & 0 deletions pkg/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -855,6 +855,7 @@ func NewServer(cfg Config, stopper *stop.Stopper) (*Server, error) {
serverIterator,
spanConfig.reporter,
clock,
distSender,
)

spanStatsServer := &SpanStatsServer{
Expand Down
76 changes: 72 additions & 4 deletions pkg/server/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ import (
"crypto/x509/pkix"
"encoding/json"
"fmt"
"github.com/cockroachdb/cockroach/pkg/kv/kvclient/kvcoord"
"github.com/cockroachdb/cockroach/pkg/kv/kvclient/rangestats"
"github.com/cockroachdb/cockroach/pkg/storage"
"io"
"net/http"
"os"
Expand Down Expand Up @@ -496,6 +499,7 @@ type systemStatusServer struct {
stores *kvserver.Stores
nodeLiveness *liveness.NodeLiveness
spanConfigReporter spanconfig.Reporter
distSender *kvcoord.DistSender
}

// StmtDiagnosticsRequester is the interface into *stmtdiagnostics.Registry
Expand Down Expand Up @@ -603,6 +607,7 @@ func newSystemStatusServer(
serverIterator ServerIterator,
spanConfigReporter spanconfig.Reporter,
clock *hlc.Clock,
distSender *kvcoord.DistSender,
) *systemStatusServer {
server := newStatusServer(
ambient,
Expand All @@ -628,6 +633,7 @@ func newSystemStatusServer(
stores: stores,
nodeLiveness: nodeLiveness,
spanConfigReporter: spanConfigReporter,
distSender: distSender,
}
}

Expand Down Expand Up @@ -3406,18 +3412,80 @@ func (s *systemStatusServer) SpanStats(
}
return status.SpanStats(ctx, req)
}
fetcher := rangestats.NewFetcher(s.db)

sp := roachpb.RSpan{
Key: req.StartKey,
EndKey: req.EndKey,
}
ri := kvcoord.MakeRangeIterator(s.distSender)
ri.Seek(ctx, sp.Key, kvcoord.Ascending)
var descriptors []*roachpb.RangeDescriptor

for {
if !ri.Valid() {
return nil, serverError(ctx, ri.Error())
}
descriptors = append(descriptors, ri.Desc())
if !ri.NeedAnother(sp) {
break
}
ri.Next(ctx)
}

output := &serverpb.SpanStatsResponse{}

for _, desc := range descriptors {
// Is the descriptor fully contained by the request span?
startContained := sp.Key.Less(desc.StartKey) || sp.Key.Equal(desc.StartKey)
endContained := !sp.EndKey.Less(desc.EndKey)
contained := startContained && endContained

if contained {
// If so, obtain stats for this range via RangeStats.
rangeStats, err := fetcher.RangeStats(ctx, desc.StartKey.AsRawKey())
if err != nil {
return nil, serverError(ctx, err)
}
for _, resp := range rangeStats {
output.TotalStats.Add(resp.MVCCStats)
}

} else {
// Otherwise, do an MVCC scan.
err := s.stores.VisitStores(func(s *kvserver.Store) error {
stats, err := storage.ComputeStats(
s.Engine(),
req.StartKey.AsRawKey(),
req.EndKey.AsRawKey(),
timeutil.Now().UnixNano(),
)

if err != nil {
return err
}

output.TotalStats.Add(stats)
return nil
})

if err != nil {
return nil, serverError(ctx, err)
}
}
}

// Finally, get the approximate disk bytes from each store.
err = s.stores.VisitStores(func(store *kvserver.Store) error {
result, err := store.ComputeStatsForKeySpan(req.StartKey.Next(), req.EndKey)
approxDiskBytes, err := store.Engine().ApproximateDiskBytes(
roachpb.Key(req.StartKey), roachpb.Key(req.EndKey))
if err != nil {
return err
}
output.TotalStats.Add(result.MVCC)
output.RangeCount += int32(result.ReplicaCount)
output.ApproximateDiskBytes += result.ApproximateDiskBytes
output.ApproximateDiskBytes += approxDiskBytes
return nil
})

if err != nil {
return nil, serverError(ctx, err)
}
Expand Down
5 changes: 5 additions & 0 deletions pkg/server/status_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1377,6 +1377,11 @@ func TestSpanStatsResponse(t *testing.T) {
if err != nil {
t.Fatal(err)
}

log.Infof(context.Background(), "mvcc stats: %v", response.TotalStats)
log.Infof(context.Background(), "approx bytes: %v",
response.ApproximateDiskBytes)

if a, e := int(response.RangeCount), initialRanges; a != e {
t.Errorf("expected %d ranges, found %d", e, a)
}
Expand Down
29 changes: 29 additions & 0 deletions pkg/ui/workspaces/db-console/src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,10 @@ import StatementInsightDetailsPage from "./views/insights/statementInsightDetail
import { CockroachCloudContext } from "@cockroachlabs/cluster-ui";
import { SnapshotRouter } from "src/views/tracez_v2/snapshotRoutes";
import KeyVisualizerPage from "src/views/keyVisualizer";
import {getSpanStatsSamples} from "src/util/api";
import { cockroach } from "src/js/protos";
import SpanStatsRequest = cockroach.server.serverpb.SpanStatsRequest;
import SpanStatsResponse = cockroach.server.serverpb.SpanStatsResponse;

// NOTE: If you are adding a new path to the router, and that path contains any
// components that are personally identifying information, you MUST update the
Expand All @@ -97,6 +101,30 @@ import KeyVisualizerPage from "src/views/keyVisualizer";
// Serial numeric values, such as NodeIDs or Descriptor IDs, are not PII and do
// not need to be redacted.

const fromHexString = (hexString) =>
Uint8Array.from(hexString.match(/.{1,2}/g).map((byte) => parseInt(byte, 16)));


class SpanStatsPage extends React.Component<any, any> {

componentDidMount() {
getSpanStatsSamples(SpanStatsRequest.create({
node_id:"1",
start_key:fromHexString("f289127061726973000112cccccccccccc4000ff8000ff00ff00ff00ff00ff00ff280001"),
end_key:fromHexString("f2891273616e206672616e636973636f0001128000ff00ff00ff00ff00ff4000ff8000ff00ff00ff00ff00ff00ff190001")
})).then(res => {

console.log(res.toJSON())
console.log(res.approximate_disk_bytes)
})
}

render () {
return <div>span stats</div>
}
}


export interface AppProps {
history: History;
store: Store<AdminUIState, Action>;
Expand All @@ -115,6 +143,7 @@ export const App: React.FC<AppProps> = (props: AppProps) => {
{createLogoutRoute(store)}
<Route path="/">
<Layout>
<SpanStatsPage/>
<Switch>
<Redirect exact from="/" to="/overview" />
{/* overview page */}
Expand Down
15 changes: 15 additions & 0 deletions pkg/ui/workspaces/db-console/src/util/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,10 @@ export type ListTracingSnapshotsRequestMessage =
export type ListTracingSnapshotsResponseMessage =
protos.cockroach.server.serverpb.ListTracingSnapshotsResponse;


export type SpanStatsRequestMessage = protos.cockroach.server.serverpb.SpanStatsRequest;
export type SpanStatsResponseMessage = protos.cockroach.server.serverpb.SpanStatsResponse;

// API constants

export const API_PREFIX = "_admin/v1";
Expand Down Expand Up @@ -877,3 +881,14 @@ export function getKeyVisualizerSamples(
timeout,
);
}
export function getSpanStatsSamples(
req: SpanStatsRequestMessage,
timeout?: moment.Duration,
): Promise<SpanStatsResponseMessage> {
return timeoutFetch(
serverpb.SpanStatsResponse,
`${STATUS_PREFIX}/span`,
req as any,
timeout,
);
}

0 comments on commit 13ca09a

Please sign in to comment.