Skip to content

Commit

Permalink
ui: reduce frequent Metrics page re-rendering
Browse files Browse the repository at this point in the history
Before, many components on Metrics page relied on
`nodesSummarySelector` selector that in turn relies on `NodeStatus`
that change constantly with every request (and we request it periodically
every 10 seconds). `NodeStatus` include lots of unnecessary data for
Metrics page (ie. node's and stores last metrics that aren't used on charts)
but these changes forces react components to be re-rendered.

This patch refactors selectors that are used by metrics page in a way to
provide only relevant subset of NodeStatus's info.

Release note: None
  • Loading branch information
koorosh committed Sep 26, 2022
1 parent bbcb10f commit 65f2895
Show file tree
Hide file tree
Showing 18 changed files with 459 additions and 303 deletions.
45 changes: 32 additions & 13 deletions pkg/ui/workspaces/db-console/src/redux/nodes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,25 @@ type NodeStatusState = Pick<AdminUIState, "cachedData", "nodes">;
export const nodeStatusesSelector = (state: NodeStatusState) =>
state.cachedData.nodes.data;

export const selectNodesLastError = (state: AdminUIState) =>
state.cachedData.nodes.lastError;
// partialNodeStatusesSelector returns NodeStatus items without fields that constantly change
// and causes selectors to recompute.
const partialNodeStatusesSelector = createSelector(
nodeStatusesSelector,
(nodeStatuses: INodeStatus[]) => {
return nodeStatuses?.map((ns: INodeStatus) => {
const { metrics, store_statuses, updated_at, activity, ...rest } = ns;
return {
...rest,
store_statuses: store_statuses?.map(ss => ({ desc: ss.desc })),
};
});
},
);

export const selectNodesLastError = createSelector(
(state: AdminUIState) => state.cachedData.nodes,
nodes => nodes.lastError,
);

/*
* clusterSelector returns information about cluster.
Expand Down Expand Up @@ -139,16 +156,21 @@ export const selectCommissionedNodeStatuses = createSelector(
* nodeIDsSelector returns the NodeID of all nodes currently on the cluster.
*/
export const nodeIDsSelector = createSelector(
nodeStatusesSelector,
nodeStatuses => {
return _.map(nodeStatuses, ns => ns.desc.node_id.toString());
},
partialNodeStatusesSelector,
nodeStatuses => _.map(nodeStatuses, ns => ns.desc.node_id),
);

/**
* nodeIDsStringifiedSelector returns available node IDs on cluster as list of strings.
*/
export const nodeIDsStringifiedSelector = createSelector(nodeIDsSelector, ids =>
ids.map(id => id.toString()),
);

/**
* nodeStatusByIDSelector returns a map from NodeID to a current INodeStatus.
*/
const nodeStatusByIDSelector = createSelector(
export const nodeStatusByIDSelector = createSelector(
nodeStatusesSelector,
nodeStatuses => {
const statuses: { [s: string]: INodeStatus } = {};
Expand Down Expand Up @@ -310,7 +332,7 @@ function isNoConnection(
// This function will never be passed decommissioned nodes because
// #56529 removed a node's status entry once it's decommissioned.
export const nodeDisplayNameByIDSelector = createSelector(
nodeStatusesSelector,
partialNodeStatusesSelector,
livenessStatusByNodeIDSelector,
(nodeStatuses, livenessStatusByNodeID) => {
const result: { [key: string]: string } = {};
Expand Down Expand Up @@ -359,7 +381,7 @@ export const selectIsMoreThanOneNode = createSelector(
// selectStoreIDsByNodeID returns a map from node ID to a list of store IDs for
// that node. Like nodeIDsSelector, the store ids are converted to strings.
export const selectStoreIDsByNodeID = createSelector(
nodeStatusesSelector,
partialNodeStatusesSelector,
nodeStatuses => {
const result: { [key: string]: string[] } = {};
_.each(
Expand All @@ -380,9 +402,8 @@ export const selectStoreIDsByNodeID = createSelector(
*/
export const nodesSummarySelector = createSelector(
nodeStatusesSelector,
nodeIDsSelector,
nodeIDsStringifiedSelector,
nodeStatusByIDSelector,
nodeSumsSelector,
nodeDisplayNameByIDSelector,
livenessStatusByNodeIDSelector,
livenessByNodeIDSelector,
Expand All @@ -392,7 +413,6 @@ export const nodesSummarySelector = createSelector(
nodeStatuses,
nodeIDs,
nodeStatusByID,
nodeSums,
nodeDisplayNameByID,
livenessStatusByNodeID,
livenessByNodeID,
Expand All @@ -403,7 +423,6 @@ export const nodesSummarySelector = createSelector(
nodeStatuses,
nodeIDs,
nodeStatusByID,
nodeSums,
nodeDisplayNameByID,
livenessStatusByNodeID,
livenessByNodeID,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React from "react";
import { configureStore } from "@reduxjs/toolkit";
import type { PreloadedState } from "@reduxjs/toolkit";
import { Provider } from "react-redux";

import { AdminUIState } from "src/redux/state";
import { createMemoryHistory } from "history";
import { apiReducersReducer } from "src/redux/apiReducers";
import { hoverReducer } from "src/redux/hover";
import { localSettingsReducer } from "src/redux/localsettings";
import { metricsReducer } from "src/redux/metrics";
import { sqlActivityReducer } from "src/redux/sqlActivity";
import { queryManagerReducer } from "src/redux/queryManager/reducer";
import { timeScaleReducer } from "src/redux/timeScale";
import { uiDataReducer } from "src/redux/uiData";
import { loginReducer } from "src/redux/login";
import { connectRouter } from "connected-react-router";

export function renderWithProviders(
element: React.ReactElement,
preloadedState?: PreloadedState<AdminUIState>,
): React.ReactElement {
const history = createMemoryHistory({
initialEntries: ["/"],
});
const routerReducer = connectRouter(history);
const store = configureStore<AdminUIState>({
reducer: {
cachedData: apiReducersReducer,
hover: hoverReducer,
localSettings: localSettingsReducer,
metrics: metricsReducer,
sqlActivity: sqlActivityReducer,
queryManager: queryManagerReducer,
router: routerReducer,
timeScale: timeScaleReducer,
uiData: uiDataReducer,
login: loginReducer,
},
preloadedState,
});
return <Provider store={store}>{element}</Provider>;
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { connect } from "react-redux";
import { createSelector } from "reselect";

import { AdminUIState } from "src/redux/state";
import { nodesSummarySelector, NodesSummary } from "src/redux/nodes";
import { nodeSumsSelector } from "src/redux/nodes";
import { Bytes as formatBytes } from "src/util/format";
import createChartComponent from "src/views/shared/util/d3-react";
import capacityChart from "./capacity";
Expand Down Expand Up @@ -81,9 +81,9 @@ function renderCapacityUsage(props: CapacityUsageProps) {
}

const mapStateToCapacityUsageProps = createSelector(
nodesSummarySelector,
function (nodesSummary: NodesSummary) {
const { capacityUsed, capacityUsable } = nodesSummary.nodeSums;
nodeSumsSelector,
nodeSums => {
const { capacityUsed, capacityUsable } = nodeSums;
return {
usedCapacity: capacityUsed,
usableCapacity: capacityUsable,
Expand Down Expand Up @@ -149,9 +149,9 @@ function renderNodeLiveness(props: NodeLivenessProps) {
}

const mapStateToNodeLivenessProps = createSelector(
nodesSummarySelector,
function (nodesSummary: NodesSummary) {
const { nodeCounts } = nodesSummary.nodeSums;
nodeSumsSelector,
nodeSums => {
const { nodeCounts } = nodeSums;
return {
liveNodes: nodeCounts.healthy,
suspectNodes: nodeCounts.suspect,
Expand Down Expand Up @@ -220,10 +220,9 @@ function renderReplicationStatus(props: ReplicationStatusProps) {
}

const mapStateToReplicationStatusProps = createSelector(
nodesSummarySelector,
function (nodesSummary: NodesSummary) {
const { totalRanges, underReplicatedRanges, unavailableRanges } =
nodesSummary.nodeSums;
nodeSumsSelector,
nodeSums => {
const { totalRanges, underReplicatedRanges, unavailableRanges } = nodeSums;
return {
totalRanges: totalRanges,
underReplicatedRanges: underReplicatedRanges,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,11 @@
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.

import { NodesSummary } from "src/redux/nodes";

/**
* GraphDashboardProps are the properties accepted by the renderable component
* of each graph dashboard.
*/
export interface GraphDashboardProps {
/**
* Summary of nodes data.
*/
nodesSummary: NodesSummary;
/**
* List of node IDs which should be used in graphs which display a series per
* node.
Expand All @@ -40,21 +34,26 @@ export interface GraphDashboardProps {
* all nodes" or "on node X".
*/
tooltipSelection: string;

nodeDisplayNameByID: {
[key: string]: string;
};

storeIDsByNodeID: {
[key: string]: string[];
};
}

export function nodeDisplayName(nodesSummary: NodesSummary, nid: string) {
const ns = nodesSummary.nodeStatusByID[nid];
if (!ns) {
// This should only happen immediately after loading a page, and
// associated graphs should display no data.
return "unknown node";
}
return nodesSummary.nodeDisplayNameByID[ns.desc.node_id];
export function nodeDisplayName(
nodeDisplayNameByID: { [nodeId: string]: string },
nid: string,
): string {
return nodeDisplayNameByID[nid] || "unknown node";
}

export function storeIDsForNode(
nodesSummary: NodesSummary,
storeIDsByNodeID: { [key: string]: string[] },
nid: string,
): string[] {
return nodesSummary.storeIDsByNodeID[nid] || [];
return storeIDsByNodeID[nid] || [];
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { AxisUnits } from "@cockroachlabs/cluster-ui";
import { GraphDashboardProps, nodeDisplayName } from "./dashboardUtils";

export default function (props: GraphDashboardProps) {
const { nodeIDs, nodesSummary, nodeSources } = props;
const { nodeIDs, nodeSources, nodeDisplayNameByID } = props;

return [
<LineGraph title="Batches" sources={nodeSources}>
Expand Down Expand Up @@ -93,7 +93,7 @@ export default function (props: GraphDashboardProps) {
<Metric
key={node}
name="cr.node.txn.durations-p99"
title={nodeDisplayName(nodesSummary, node)}
title={nodeDisplayName(nodeDisplayNameByID, node)}
sources={[node]}
downsampleMax
/>
Expand All @@ -111,7 +111,7 @@ export default function (props: GraphDashboardProps) {
<Metric
key={node}
name="cr.node.txn.durations-p90"
title={nodeDisplayName(nodesSummary, node)}
title={nodeDisplayName(nodeDisplayNameByID, node)}
sources={[node]}
downsampleMax
/>
Expand All @@ -129,7 +129,7 @@ export default function (props: GraphDashboardProps) {
<Metric
key={node}
name="cr.node.liveness.heartbeatlatency-p99"
title={nodeDisplayName(nodesSummary, node)}
title={nodeDisplayName(nodeDisplayNameByID, node)}
sources={[node]}
downsampleMax
/>
Expand All @@ -147,7 +147,7 @@ export default function (props: GraphDashboardProps) {
<Metric
key={node}
name="cr.node.liveness.heartbeatlatency-p90"
title={nodeDisplayName(nodesSummary, node)}
title={nodeDisplayName(nodeDisplayNameByID, node)}
sources={[node]}
downsampleMax
/>
Expand Down
Loading

0 comments on commit 65f2895

Please sign in to comment.