mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2025-01-10 15:14:09 +00:00
vmui: update render logic for nested component (#2795)
* vmui: update render logic for nested component, avoid rerender, remove local storage usage for tracing flag * docs/url-examples.md: fix various documentation issues there * docs: add Troubleshooting doc This doc contains troubleshooting guides for typical problems with VictoriaMetrics. * docs/Troubleshooting.md: add troubleshooting guide for cluster instability * wip * wip Co-authored-by: Aliaksandr Valialkin <valyala@victoriametrics.com>
This commit is contained in:
parent
2a70a9296e
commit
67753cc6f0
17 changed files with 74 additions and 107 deletions
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"files": {
|
||||
"main.css": "./static/css/main.7e6d0c89.css",
|
||||
"main.js": "./static/js/main.645fe611.js",
|
||||
"main.js": "./static/js/main.84a0d8f8.js",
|
||||
"static/js/27.939f971b.chunk.js": "./static/js/27.939f971b.chunk.js",
|
||||
"index.html": "./index.html"
|
||||
},
|
||||
"entrypoints": [
|
||||
"static/css/main.7e6d0c89.css",
|
||||
"static/js/main.645fe611.js"
|
||||
"static/js/main.84a0d8f8.js"
|
||||
]
|
||||
}
|
|
@ -1 +1 @@
|
|||
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="./favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="VM-UI is a metric explorer for Victoria Metrics"/><link rel="apple-touch-icon" href="./apple-touch-icon.png"/><link rel="icon" type="image/png" sizes="32x32" href="./favicon-32x32.png"><link rel="manifest" href="./manifest.json"/><title>VM UI</title><link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"/><script src="./dashboards/index.js" type="module"></script><script defer="defer" src="./static/js/main.645fe611.js"></script><link href="./static/css/main.7e6d0c89.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="./favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="VM-UI is a metric explorer for Victoria Metrics"/><link rel="apple-touch-icon" href="./apple-touch-icon.png"/><link rel="icon" type="image/png" sizes="32x32" href="./favicon-32x32.png"><link rel="manifest" href="./manifest.json"/><title>VM UI</title><link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"/><script src="./dashboards/index.js" type="module"></script><script defer="defer" src="./static/js/main.84a0d8f8.js"></script><link href="./static/css/main.7e6d0c89.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
|
File diff suppressed because one or more lines are too long
2
app/vmselect/vmui/static/js/main.84a0d8f8.js
Normal file
2
app/vmselect/vmui/static/js/main.84a0d8f8.js
Normal file
File diff suppressed because one or more lines are too long
|
@ -81,7 +81,7 @@ const CardinalityConfigurator: FC<CardinalityConfiguratorProps> = ({
|
|||
onChange={onFocusLabelChange} />
|
||||
</Box>
|
||||
<Box>
|
||||
<FormControlLabel label="Enable autocomplete"
|
||||
<FormControlLabel label="Autocomplete"
|
||||
control={<BasicSwitch checked={autocomplete} onChange={onChangeAutocomplete}/>}
|
||||
/>
|
||||
</Box>
|
||||
|
|
|
@ -32,17 +32,17 @@ const AdditionalSettings: FC = () => {
|
|||
|
||||
return <Box display="flex" alignItems="center">
|
||||
<Box>
|
||||
<FormControlLabel label="Enable autocomplete"
|
||||
<FormControlLabel label="Autocomplete"
|
||||
control={<BasicSwitch checked={autocomplete} onChange={onChangeAutocomplete}/>}
|
||||
/>
|
||||
</Box>
|
||||
<Box ml={2}>
|
||||
<FormControlLabel label="Enable cache"
|
||||
control={<BasicSwitch checked={!nocache} onChange={onChangeCache}/>}
|
||||
<FormControlLabel label="Disable cache"
|
||||
control={<BasicSwitch checked={nocache} onChange={onChangeCache}/>}
|
||||
/>
|
||||
</Box>
|
||||
<Box ml={2}>
|
||||
<FormControlLabel label="Enable query tracing"
|
||||
<FormControlLabel label="Trace query"
|
||||
control={<BasicSwitch checked={isTracingEnabled} onChange={onChangeQueryTracing} />}
|
||||
/>
|
||||
</Box>
|
||||
|
|
|
@ -18,7 +18,7 @@ import Trace from "./Trace/Trace";
|
|||
|
||||
const CustomPanel: FC = () => {
|
||||
|
||||
const [tracingsData, setTracingData] = useState<Trace[]>([]);
|
||||
const [tracesState, setTracesState] = useState<Trace[]>([]);
|
||||
const {displayType, time: {period}, query, queryControls: {isTracingEnabled}} = useAppState();
|
||||
const { customStep, yaxis } = useGraphState();
|
||||
|
||||
|
@ -38,24 +38,24 @@ const CustomPanel: FC = () => {
|
|||
};
|
||||
|
||||
const {queryOptions} = useFetchQueryOptions();
|
||||
const {isLoading, liveData, graphData, error, tracingData} = useFetchQuery({
|
||||
const {isLoading, liveData, graphData, error, traces} = useFetchQuery({
|
||||
visible: true,
|
||||
customStep
|
||||
});
|
||||
|
||||
const handleTraceDelete = (tracingData: Trace) => {
|
||||
const updatedTracings = tracingsData.filter((data) => data.idValue !== tracingData.idValue);
|
||||
setTracingData([...updatedTracings]);
|
||||
const handleTraceDelete = (trace: Trace) => {
|
||||
const updatedTraces = tracesState.filter((data) => data.idValue !== trace.idValue);
|
||||
setTracesState([...updatedTraces]);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (tracingData) {
|
||||
setTracingData([...tracingsData, tracingData]);
|
||||
if (traces) {
|
||||
setTracesState([...tracesState, ...traces]);
|
||||
}
|
||||
}, [tracingData]);
|
||||
}, [traces]);
|
||||
|
||||
useEffect(() => {
|
||||
setTracingData([]);
|
||||
setTracesState([]);
|
||||
}, [displayType]);
|
||||
|
||||
return (
|
||||
|
@ -78,7 +78,7 @@ const CustomPanel: FC = () => {
|
|||
{error && <Alert color="error" severity="error" sx={{whiteSpace: "pre-wrap", mt: 2}}>{error}</Alert>}
|
||||
{graphData && period && (displayType === "chart") && <>
|
||||
{isTracingEnabled && <TracingsView
|
||||
tracingsData={tracingsData}
|
||||
traces={tracesState}
|
||||
onDeleteClick={handleTraceDelete}
|
||||
/>}
|
||||
<GraphView data={graphData} period={period} customStep={customStep} query={query} yaxis={yaxis}
|
||||
|
@ -87,7 +87,7 @@ const CustomPanel: FC = () => {
|
|||
{liveData && (displayType === "code") && <JsonView data={liveData}/>}
|
||||
{liveData && (displayType === "table") && <>
|
||||
{isTracingEnabled && <TracingsView
|
||||
tracingsData={tracingsData}
|
||||
traces={tracesState}
|
||||
onDeleteClick={handleTraceDelete}
|
||||
/>}
|
||||
<TableView data={liveData}/>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, {FC} from "preact/compat";
|
||||
import React, {FC, useState} from "preact/compat";
|
||||
import Box from "@mui/material/Box";
|
||||
import ListItem from "@mui/material/ListItem";
|
||||
import ListItemText from "@mui/material/ListItemText";
|
||||
|
@ -14,20 +14,28 @@ import Trace from "../Trace/Trace";
|
|||
interface RecursiveProps {
|
||||
trace: Trace;
|
||||
totalMsec: number;
|
||||
openLevels: Record<number, boolean>;
|
||||
onChange: (level: number) => void;
|
||||
}
|
||||
|
||||
const NestedNav: FC<RecursiveProps> = ({ trace, openLevels, totalMsec, onChange}) => {
|
||||
const handleListClick = (traceID: number) => () => onChange(traceID);
|
||||
interface OpenLevels {
|
||||
[x: number]: boolean
|
||||
}
|
||||
|
||||
const NestedNav: FC<RecursiveProps> = ({ trace, totalMsec}) => {
|
||||
const [openLevels, setOpenLevels] = useState({} as OpenLevels);
|
||||
|
||||
const handleListClick = (level: number) => () => {
|
||||
setOpenLevels((prevState:OpenLevels) => {
|
||||
return {...prevState, [level]: !prevState[level]};
|
||||
});
|
||||
};
|
||||
const hasChildren = trace.children && trace.children.length;
|
||||
const progress = trace.duration / totalMsec * 100;
|
||||
return (
|
||||
<Box sx={{ bgcolor: "rgba(201, 227, 246, 0.4)" }}>
|
||||
<ListItem onClick={handleListClick(trace.duration)} sx={!hasChildren ? {p:0, pl: 7} : {p:0}}>
|
||||
<ListItem onClick={handleListClick(trace.idValue)} sx={!hasChildren ? {p:0, pl: 7} : {p:0}}>
|
||||
<ListItemButton alignItems={"flex-start"} sx={{ pt: 0, pb: 0}}>
|
||||
{hasChildren ? <ListItemIcon>
|
||||
{openLevels[trace.duration] ?
|
||||
{openLevels[trace.idValue] ?
|
||||
<ExpandLess fontSize={"large"} color={"info"} /> :
|
||||
<AddCircleRoundedIcon fontSize={"large"} color={"info"} />}
|
||||
</ListItemIcon>: null}
|
||||
|
@ -43,15 +51,13 @@ const NestedNav: FC<RecursiveProps> = ({ trace, openLevels, totalMsec, onChange}
|
|||
</ListItemButton>
|
||||
</ListItem>
|
||||
<>
|
||||
<Collapse in={openLevels[trace.duration]} timeout="auto" unmountOnExit>
|
||||
<Collapse in={openLevels[trace.idValue]} timeout="auto" unmountOnExit>
|
||||
<List component="div" disablePadding sx={{ pl: 4 }}>
|
||||
{hasChildren ?
|
||||
trace.children.map((trace) => <NestedNav
|
||||
key={trace.duration}
|
||||
trace={trace}
|
||||
openLevels={openLevels}
|
||||
totalMsec={totalMsec}
|
||||
onChange={onChange}
|
||||
/>) : null}
|
||||
</List>
|
||||
</Collapse>
|
||||
|
|
|
@ -1,33 +1,18 @@
|
|||
import {TracingData} from "../../../api/types";
|
||||
|
||||
let traceId = 0;
|
||||
|
||||
export default class Trace {
|
||||
private readonly tracing: TracingData;
|
||||
private readonly tracingChildren: Trace[];
|
||||
private readonly query: string;
|
||||
private readonly id: number;
|
||||
constructor(tracingData: TracingData, query: string) {
|
||||
this.tracing = tracingData;
|
||||
this.query = query;
|
||||
this.id = new Date().getTime();
|
||||
}
|
||||
|
||||
recursiveMap(oldArray: TracingData[], callback: (tr: TracingData) => Trace, newArray: Trace[]): Trace[] {
|
||||
if (!oldArray) return [];
|
||||
//base case: check if there are any items left in the original array to process
|
||||
if (oldArray && oldArray.length <= 0){
|
||||
//if all items have been processed return the new array
|
||||
return newArray;
|
||||
} else {
|
||||
//destructure the first item from old array and put remaining in a separate array
|
||||
const [item, ...theRest] = oldArray;
|
||||
// create an array of the current new array and the result of the current item and the callback function
|
||||
const interimArray = [...newArray, callback(item)];
|
||||
// return a recursive call to to map to process the next item.
|
||||
return this.recursiveMap(theRest, callback, interimArray);
|
||||
}
|
||||
}
|
||||
|
||||
createTrace(traceData: TracingData) {
|
||||
return new Trace(traceData, "");
|
||||
this.id = traceId++;
|
||||
const children = tracingData.children || [];
|
||||
this.tracingChildren = children.map((x: TracingData) => new Trace(x, query));
|
||||
}
|
||||
|
||||
get queryValue(): string {
|
||||
|
@ -37,8 +22,7 @@ export default class Trace {
|
|||
return this.id;
|
||||
}
|
||||
get children(): Trace[] {
|
||||
const arr: Trace[] = [];
|
||||
return this.recursiveMap(this.tracing.children, this.createTrace, arr);
|
||||
return this.tracingChildren;
|
||||
}
|
||||
get message(): string {
|
||||
return this.tracing.message;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, {FC, useState} from "preact/compat";
|
||||
import React, {FC} from "preact/compat";
|
||||
import List from "@mui/material/List";
|
||||
import NestedNav from "../NestedNav/NestedNav";
|
||||
import Trace from "../Trace/Trace";
|
||||
|
@ -7,23 +7,10 @@ interface TraceViewProps {
|
|||
trace: Trace;
|
||||
}
|
||||
|
||||
interface OpenLevels {
|
||||
[x: number]: boolean
|
||||
}
|
||||
|
||||
const TraceView: FC<TraceViewProps> = ({trace}) => {
|
||||
|
||||
const [openLevels, setOpenLevels] = useState({} as OpenLevels);
|
||||
const handleClick = (level: number) => {
|
||||
setOpenLevels((prevState:OpenLevels) => ({...prevState, [level]: !prevState[level]}));
|
||||
};
|
||||
return (<List sx={{ width: "100%" }} component="nav">
|
||||
<NestedNav
|
||||
trace={trace}
|
||||
openLevels={openLevels}
|
||||
totalMsec={trace.duration}
|
||||
onChange={handleClick}
|
||||
/>
|
||||
<NestedNav trace={trace} totalMsec={trace.duration} />
|
||||
</List>);
|
||||
};
|
||||
|
||||
|
|
|
@ -7,17 +7,15 @@ import Button from "@mui/material/Button";
|
|||
import Trace from "../Trace/Trace";
|
||||
|
||||
interface TraceViewProps {
|
||||
tracingsData: Trace[];
|
||||
onDeleteClick: (tracingData: Trace) => void;
|
||||
traces: Trace[];
|
||||
onDeleteClick: (trace: Trace) => void;
|
||||
}
|
||||
|
||||
const EMPTY_MESSAGE = "Please re-run the query to see results of the tracing";
|
||||
|
||||
const TracingsView: FC<TraceViewProps> = ({tracingsData, onDeleteClick}) => {
|
||||
if (!tracingsData.length) {
|
||||
const TracingsView: FC<TraceViewProps> = ({traces, onDeleteClick}) => {
|
||||
if (!traces.length) {
|
||||
return (
|
||||
<Alert color={"info"} severity="info" sx={{whiteSpace: "pre-wrap", mt: 2}}>
|
||||
{EMPTY_MESSAGE}
|
||||
Please re-run the query to see results of the tracing
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
|
@ -26,14 +24,14 @@ const TracingsView: FC<TraceViewProps> = ({tracingsData, onDeleteClick}) => {
|
|||
onDeleteClick(tracingData);
|
||||
};
|
||||
|
||||
return <>{tracingsData.map((tracingData) => <>
|
||||
<Typography variant="h4" gutterBottom component="div">
|
||||
{"Tracing for"} {tracingData.queryValue}
|
||||
<Button onClick={handleDeleteClick(tracingData)}>
|
||||
<RemoveCircleIcon fontSize={"large"} color={"error"} />
|
||||
return <>{traces.map((trace: Trace) => <>
|
||||
<Typography variant="h5" component="div">
|
||||
Trace for <b>{trace.queryValue}</b>
|
||||
<Button onClick={handleDeleteClick(trace)}>
|
||||
<RemoveCircleIcon fontSize={"medium"} color={"error"} />
|
||||
</Button>
|
||||
</Typography>
|
||||
<TraceView trace={tracingData} />
|
||||
<TraceView trace={trace} />
|
||||
</>)}</>;
|
||||
};
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import {useCallback, useEffect, useMemo, useState} from "preact/compat";
|
||||
import {getQueryRangeUrl, getQueryUrl} from "../api/query-range";
|
||||
import {useAppState} from "../state/common/StateContext";
|
||||
import {InstantMetricResult, MetricBase, MetricResult, TracingData} from "../api/types";
|
||||
import {InstantMetricResult, MetricBase, MetricResult} from "../api/types";
|
||||
import {isValidHttpUrl} from "../utils/url";
|
||||
import {ErrorTypes} from "../types";
|
||||
import {getAppModeEnable, getAppModeParams} from "../utils/app-mode";
|
||||
|
@ -28,14 +28,14 @@ export const useFetchQuery = ({predefinedQuery, visible, display, customStep}: F
|
|||
graphData?: MetricResult[],
|
||||
liveData?: InstantMetricResult[],
|
||||
error?: ErrorTypes | string,
|
||||
tracingData?: Trace,
|
||||
traces?: Trace[],
|
||||
} => {
|
||||
const {query, displayType, serverUrl, time: {period}, queryControls: {nocache, isTracingEnabled}} = useAppState();
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [graphData, setGraphData] = useState<MetricResult[]>();
|
||||
const [liveData, setLiveData] = useState<InstantMetricResult[]>();
|
||||
const [tracingData, setTracingData] = useState<Trace>();
|
||||
const [traces, setTraces] = useState<Trace[]>();
|
||||
const [error, setError] = useState<ErrorTypes | string>();
|
||||
const [fetchQueue, setFetchQueue] = useState<AbortController[]>([]);
|
||||
|
||||
|
@ -43,33 +43,26 @@ export const useFetchQuery = ({predefinedQuery, visible, display, customStep}: F
|
|||
if (error) {
|
||||
setGraphData(undefined);
|
||||
setLiveData(undefined);
|
||||
setTracingData(undefined);
|
||||
setTraces(undefined);
|
||||
}
|
||||
}, [error]);
|
||||
|
||||
const updateTracingData = (tracing: TracingData, queries: string[]) => {
|
||||
if (tracing) {
|
||||
queries.forEach((query) => {
|
||||
const {message} = tracing;
|
||||
if (message.includes(query)) {
|
||||
setTracingData(new Trace(tracing, query));
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const fetchData = async (fetchUrl: string[], fetchQueue: AbortController[], displayType: DisplayType, query: string[]) => {
|
||||
const controller = new AbortController();
|
||||
setFetchQueue([...fetchQueue, controller]);
|
||||
try {
|
||||
const responses = await Promise.all(fetchUrl.map(url => fetch(url, {signal: controller.signal})));
|
||||
const tempData = [];
|
||||
const tempTraces: Trace[] = [];
|
||||
let counter = 1;
|
||||
for await (const response of responses) {
|
||||
const resp = await response.json();
|
||||
if (response.ok) {
|
||||
setError(undefined);
|
||||
updateTracingData(resp.trace, query);
|
||||
if (resp.trace) {
|
||||
const trace = new Trace(resp.trace, query[counter-1]);
|
||||
tempTraces.push(trace);
|
||||
}
|
||||
tempData.push(...resp.data.result.map((d: MetricBase) => {
|
||||
d.group = counter;
|
||||
return d;
|
||||
|
@ -80,6 +73,7 @@ export const useFetchQuery = ({predefinedQuery, visible, display, customStep}: F
|
|||
}
|
||||
}
|
||||
displayType === "chart" ? setGraphData(tempData) : setLiveData(tempData);
|
||||
setTraces(tempTraces);
|
||||
} catch (e) {
|
||||
if (e instanceof Error && e.name !== "AbortError") {
|
||||
setError(`${e.name}: ${e.message}`);
|
||||
|
@ -127,5 +121,5 @@ export const useFetchQuery = ({predefinedQuery, visible, display, customStep}: F
|
|||
setFetchQueue(fetchQueue.filter(f => !f.signal.aborted));
|
||||
}, [fetchQueue]);
|
||||
|
||||
return {fetchUrl, isLoading, graphData, liveData, error, tracingData};
|
||||
return {fetchUrl, isLoading, graphData, liveData, error, traces};
|
||||
};
|
||||
|
|
|
@ -80,8 +80,8 @@ export const initialState: AppState = {
|
|||
queryControls: {
|
||||
autoRefresh: false,
|
||||
autocomplete: getFromStorage("AUTOCOMPLETE") as boolean || false,
|
||||
nocache: getFromStorage("NO_CACHE") as boolean || false,
|
||||
isTracingEnabled: getFromStorage("QUERY_TRACING") as boolean || false,
|
||||
nocache: false,
|
||||
isTracingEnabled: false,
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -244,7 +244,7 @@ Prometheus doesn't drop data during VictoriaMetrics restart. See [this article](
|
|||
|
||||
VictoriaMetrics provides UI for query troubleshooting and exploration. The UI is available at `http://victoriametrics:8428/vmui`.
|
||||
The UI allows exploring query results via graphs and tables.
|
||||
It also provides the ability to [explore cardinality](#cardinality-explorer) and to [investigate query tracec](#query-tracing).
|
||||
It also provides the ability to [explore cardinality](#cardinality-explorer) and to [investigate query traces](#query-tracing).
|
||||
|
||||
Graphs in vmui support scrolling and zooming:
|
||||
|
||||
|
@ -255,7 +255,7 @@ Query history can be navigated by holding `Ctrl` (or `Cmd` on MacOS) and pressin
|
|||
|
||||
Multi-line queries can be entered by pressing `Shift-Enter` in query input field.
|
||||
|
||||
When querying the [backfilled data](https://docs.victoriametrics.com/#backfilling), it may be useful disabling response cache by clicking `Enable cache` checkbox.
|
||||
When querying the [backfilled data](https://docs.victoriametrics.com/#backfilling), it may be useful disabling response cache by clicking `Disable cache` checkbox.
|
||||
|
||||
VMUI automatically adjusts the interval between datapoints on the graph depending on the horizontal resolution and on the selected time range. The step value can be customized by clickhing `Override step value` checkbox.
|
||||
|
||||
|
|
|
@ -248,7 +248,7 @@ Prometheus doesn't drop data during VictoriaMetrics restart. See [this article](
|
|||
|
||||
VictoriaMetrics provides UI for query troubleshooting and exploration. The UI is available at `http://victoriametrics:8428/vmui`.
|
||||
The UI allows exploring query results via graphs and tables.
|
||||
It also provides the ability to [explore cardinality](#cardinality-explorer) and to [investigate query tracec](#query-tracing).
|
||||
It also provides the ability to [explore cardinality](#cardinality-explorer) and to [investigate query traces](#query-tracing).
|
||||
|
||||
Graphs in vmui support scrolling and zooming:
|
||||
|
||||
|
@ -259,7 +259,7 @@ Query history can be navigated by holding `Ctrl` (or `Cmd` on MacOS) and pressin
|
|||
|
||||
Multi-line queries can be entered by pressing `Shift-Enter` in query input field.
|
||||
|
||||
When querying the [backfilled data](https://docs.victoriametrics.com/#backfilling), it may be useful disabling response cache by clicking `Enable cache` checkbox.
|
||||
When querying the [backfilled data](https://docs.victoriametrics.com/#backfilling), it may be useful disabling response cache by clicking `Disable cache` checkbox.
|
||||
|
||||
VMUI automatically adjusts the interval between datapoints on the graph depending on the horizontal resolution and on the selected time range. The step value can be customized by clickhing `Override step value` checkbox.
|
||||
|
||||
|
|
|
@ -273,5 +273,3 @@ have enough free resources for graceful processing the increased workload.
|
|||
See [capacity planning docs](https://docs.victoriametrics.com/Cluster-VictoriaMetrics.html#capacity-planning)
|
||||
and [cluster resizing and scalability docs](https://docs.victoriametrics.com/Cluster-VictoriaMetrics.html#cluster-resizing-and-scalability)
|
||||
for details.
|
||||
|
||||
|
||||
|
|
Loading…
Reference in a new issue