Repository: VictoriaMetrics/VictoriaMetrics
Pull request #2101 (2022-01-25 13:32:31)

vmui: add sortable columns
Added sortable columns in table ([task card](https://github.com/VictoriaMetrics/VictoriaMetrics/projects/2#card-64915536))


Patch:
diff --git a/.github/workflows/wiki.yml b/.github/workflows/wiki.yml
deleted file mode 100644
index 05f2fd02b..000000000
--- a/.github/workflows/wiki.yml
+++ /dev/null
@@ -1,28 +0,0 @@
-name: wiki
-on:
-  push:
-    paths:
-      - 'docs/*'
-    branches:
-      - master
-jobs:
-  build:
-    runs-on: ubuntu-latest
-    steps:
-      - uses: actions/checkout@master
-      - name: publish
-        shell: bash
-        env:
-          TOKEN: ${{secrets.CI_TOKEN}}
-        run: |
-          git clone https://vika:${TOKEN}@github.com/VictoriaMetrics/VictoriaMetrics.wiki.git wiki
-          cp -r docs/* wiki
-          cd wiki
-          git config --local user.email "info@victoriametrics.com"
-          git config --local user.name "Vika"
-          git add .
-          git commit -m "update wiki pages"
-          remote_repo="https://vika:${TOKEN}@github.com/VictoriaMetrics/VictoriaMetrics.wiki.git"
-          git push "${remote_repo}"
-          cd ..
-          rm -rf wiki
diff --git a/app/vmselect/netstorage/tmp_blocks_file.go b/app/vmselect/netstorage/tmp_blocks_file.go
index 7a16131a3..8c8c44acd 100644
--- a/app/vmselect/netstorage/tmp_blocks_file.go
+++ b/app/vmselect/netstorage/tmp_blocks_file.go
@@ -149,7 +149,7 @@ func (tbf *tmpBlocksFile) MustReadBlockRefAt(partRef storage.PartRef, addr tmpBl
 	} else {
 		bb := tmpBufPool.Get()
 		defer tmpBufPool.Put(bb)
-		bb.B = bytesutil.Resize(bb.B, addr.size)
+		bb.B = bytesutil.ResizeNoCopy(bb.B, addr.size)
 		tbf.r.MustReadAt(bb.B, int64(addr.offset))
 		buf = bb.B
 	}
diff --git a/app/vmselect/promql/timeseries.go b/app/vmselect/promql/timeseries.go
index b9c35ff40..718162b03 100644
--- a/app/vmselect/promql/timeseries.go
+++ b/app/vmselect/promql/timeseries.go
@@ -98,7 +98,7 @@ func marshalTimeseriesFast(dst []byte, tss []*timeseries, maxSize int, step int6
 
 	// Allocate the buffer for the marshaled tss before its' marshaling.
 	// This should reduce memory fragmentation and memory usage.
-	dst = bytesutil.Resize(dst, size)
+	dst = bytesutil.ResizeNoCopy(dst, size)
 	dst = marshalFastTimestamps(dst[:0], tss[0].Timestamps)
 	for _, ts := range tss {
 		dst = ts.marshalFastNoTimestamps(dst)
diff --git a/app/vmui/packages/vmui/src/components/Home/Views/TableView.tsx b/app/vmui/packages/vmui/src/components/Home/Views/TableView.tsx
index 8e6ca9b0c..bf005081b 100644
--- a/app/vmui/packages/vmui/src/components/Home/Views/TableView.tsx
+++ b/app/vmui/packages/vmui/src/components/Home/Views/TableView.tsx
@@ -1,4 +1,4 @@
-import React, {FC, useMemo} from "preact/compat";
+import React, {FC, useMemo, useState} from "preact/compat";
 import {InstantMetricResult} from "../../../api/types";
 import {InstantDataSeries} from "../../../types";
 import Table from "@mui/material/Table";
@@ -7,6 +7,7 @@ import TableCell from "@mui/material/TableCell";
 import TableContainer from "@mui/material/TableContainer";
 import TableHead from "@mui/material/TableHead";
 import TableRow from "@mui/material/TableRow";
+import TableSortLabel from "@mui/material/TableSortLabel";
 import makeStyles from "@mui/styles/makeStyles";
 import {useSortedCategories} from "../../../hooks/useSortedCategories";
 
@@ -26,12 +27,30 @@ const TableView: FC<GraphViewProps> = ({data}) => {
 
   const sortedColumns = useSortedCategories(data);
 
+  const [orderBy, setOrderBy] = useState("");
+  const [orderDir, setOrderDir] = useState<"asc" | "desc">("asc");
+
   const rows: InstantDataSeries[] = useMemo(() => {
-    return data?.map(d => ({
+    const rows = data?.map(d => ({
       metadata: sortedColumns.map(c => d.metric[c.key] || "-"),
       value: d.value ? d.value[1] : "-"
     }));
-  }, [sortedColumns, data]);
+    const orderByValue = orderBy === "Value";
+    const rowIndex = sortedColumns.findIndex(c => c.key === orderBy);
+    if (!orderByValue && rowIndex === -1) return rows;
+    return rows.sort((a,b) => {
+      const n1 = orderByValue ? Number(a.value) : a.metadata[rowIndex];
+      const n2 = orderByValue ? Number(b.value) : b.metadata[rowIndex];
+      const asc = orderDir === "asc" ? n1 < n2 : n1 > n2;
+      return asc ? -1 : 1;
+    });
+  }, [sortedColumns, data, orderBy, orderDir]);
+
+  const sortHandler = (key: string) => {
+    setOrderDir((prev) => prev === "asc" && orderBy === key ? "desc" : "asc");
+    setOrderBy(key);
+  };
+
 
   return (
     <>
@@ -41,8 +60,25 @@ const TableView: FC<GraphViewProps> = ({data}) => {
             <TableHead>
               <TableRow>
                 {sortedColumns.map((col, index) => (
-                  <TableCell style={{textTransform: "capitalize"}} key={index}>{col.key}</TableCell>))}
-                <TableCell align="right">Value</TableCell>
+                  <TableCell key={index} style={{textTransform: "capitalize"}}>
+                    <TableSortLabel
+                      active={orderBy === col.key}
+                      direction={orderDir}
+                      onClick={() => sortHandler(col.key)}
+                    >
+                      {col.key}
+                    </TableSortLabel>
+                  </TableCell>
+                ))}
+                <TableCell align="right">
+                  <TableSortLabel
+                    active={orderBy === "Value"}
+                    direction={orderDir}
+                    onClick={() => sortHandler("Value")}
+                  >
+                    Value
+                  </TableSortLabel>
+                </TableCell>
               </TableRow>
             </TableHead>
             <TableBody>
diff --git a/docs/operator/quick-start.MD b/docs/operator/quick-start.MD
index a284a546b..c841404de 100644
--- a/docs/operator/quick-start.MD
+++ b/docs/operator/quick-start.MD
@@ -118,7 +118,7 @@ The VictoriaMetrics Operator introduces additional resources in Kubernetes to de
 
 ## VMSingle
 
-[VMSingle](https://github.com/VictoriaMetrics/VictoriaMetrics/) represents database for storing metrics, for all possible config options check api [doc](/Operator/api.MD#VMSingle):
+[VMSingle](https://github.com/VictoriaMetrics/VictoriaMetrics/) represents database for storing metrics, for all possible config options check api [doc](https://docs.victoriametrics.com/operator/api.html#vmsingle):
  
 ```yaml
 cat <<EOF | kubectl apply -f -
@@ -273,7 +273,7 @@ Or you can use default rbac account, that will be created for `VMAgent` by opera
  kubectl apply -f release/examples/vmagent_rbac.yaml
 ```
 
-Modify `VMAgent` config parameters at `release/examples/vmagent.yaml` and apply it, config options [doc](/docs/api.MD#VMAgent)
+Modify `VMAgent` config parameters at `release/examples/vmagent.yaml` and apply it, config options [doc](https://docs.victoriametrics.com/operator/api.html#vmagent)
 
 Example:
 
@@ -354,7 +354,7 @@ stringData:
 EOF
 ```
 
-Then add `Alertmanager` object, other config options at [doc](/docs/api.MD#Alertmanager)
+Then add `Alertmanager` object, other config options at [doc](https://docs.victoriametrics.com/operator/api.html#alertmanager)
 you have to set configSecret with name of secret, that we created before - `alertmanager-config`.
 ```yaml
 cat << EOF | kubectl apply -f -
@@ -452,7 +452,7 @@ EOF
 ## VMAlert
 
 [VMAlert](https://github.com/VictoriaMetrics/VictoriaMetrics/tree/master/app/vmalert) - executes a list of given [alerting](https://prometheus.io/docs/prometheus/latest/configuration/alerting_rules/) or [recording](https://prometheus.io/docs/prometheus/latest/configuration/recording_rules/) rules against configured address. It
-has few required config options - `datasource` and `notifier` are required, for other config parameters check [doc](/Operator/api.MD#VMAlert).
+has few required config options - `datasource` and `notifier` are required, for other config parameters check [doc](https://docs.victoriametrics.com/operator/api.html#vmalert).
 
 ```yaml
 cat << EOF | kubectl apply -f -
@@ -478,7 +478,7 @@ EOF
  by corresponding `Service` and it's `Endpoint`s.
  It has various options for scraping configuration of target (with basic auth,tls access, by specific port name etc).
 
-Let's make some demo, you have to deploy [VMAgent](#VMAgent) and [VMSingle](#VMSingle) from previous step with match any selectors:
+Let's make some demo, you have to deploy [VMAgent](#vmagent) and [VMSingle](#vmsingle) from previous step with match any selectors:
 
 ```yaml
 cat <<EOF | kubectl apply -f -
@@ -714,7 +714,7 @@ kubectl logs vmagent-example-vmagent-5777fdf7bf-tctcv vmagent --tail 100
 It generates `VMAlert` config with ruleset defined at `VMRule` spec.
 
 Lets create `VMAlert` with selector for `VMRule` with label project=devops.
-  You also need datasource from previous step [VMSingle](#VMSingle) and [VMAgent](#VMAgent) connected to it.
+  You also need datasource from previous step [VMSingle](#vmsingle) and [VMAgent](#vmagent) connected to it.
 
 ```yaml
 cat << EOF | kubectl apply -f -
@@ -1100,7 +1100,7 @@ static_configs: added targets: 2, removed targets: 0; total targets: 2
 
 [VMAuth](https://docs.victoriametrics.com/vmauth.html) allows protecting application with authentication and route traffic by rules.
 
-api docs [link](/Operator/api.MD#VMAuthSpec)
+api docs [link](https://docs.victoriametrics.com/operator/api.html#vmauthspec)
 
  First create `VMAuth` configuration:
 ```yaml
@@ -1141,7 +1141,7 @@ users:
 
 ## VMUser 
 
-  `VMUser` configures `VMAuth`. api doc [link](/Operator/api.MD#VMUserSpec)
+  `VMUser` configures `VMAuth`. api doc [link](https://docs.victoriametrics.com/operator/api.html#vmuserspec)
 
   There are two authentication mechanisms: `bearerToken` and `basicAuth` with `username` and `password`. Only one of them can be used with `VMUser` at one time.
 If you need to provide access with different mechanisms for single endpoint, create multiple `VMUsers`.
diff --git a/lib/bytesutil/bytebuffer.go b/lib/bytesutil/bytebuffer.go
index 6bdd9d921..3aec86498 100644
--- a/lib/bytesutil/bytebuffer.go
+++ b/lib/bytesutil/bytebuffer.go
@@ -53,7 +53,7 @@ func (bb *ByteBuffer) MustReadAt(p []byte, offset int64) {
 func (bb *ByteBuffer) ReadFrom(r io.Reader) (int64, error) {
 	b := bb.B
 	bLen := len(b)
-	b = Resize(b, 4*1024)
+	b = ResizeWithCopy(b, 4*1024)
 	b = b[:cap(b)]
 	offset := bLen
 	for {
diff --git a/lib/bytesutil/bytesutil.go b/lib/bytesutil/bytesutil.go
index 4dd26840b..1ead40f5c 100644
--- a/lib/bytesutil/bytesutil.go
+++ b/lib/bytesutil/bytesutil.go
@@ -5,12 +5,29 @@ import (
 	"unsafe"
 )
 
-// Resize resizes b to n bytes and returns b (which may be newly allocated).
-func Resize(b []byte, n int) []byte {
-	if nn := n - cap(b); nn > 0 {
-		b = append(b[:cap(b)], make([]byte, nn)...)
+// ResizeWithCopy resizes b to n bytes and returns the resized buffer (which may be newly allocated).
+//
+// If newly allocated buffer is returned then b contents is copied to it.
+func ResizeWithCopy(b []byte, n int) []byte {
+	if n <= cap(b) {
+		return b[:n]
+	}
+	// Allocate the exact number of bytes instead of using `b = append(b[:cap(b)], make([]byte, nn)...)`,
+	// since `append()` may allocate more than the requested bytes for additional capacity.
+	// Using make() instead of append() should save RAM when the resized slice is cached somewhere.
+	bNew := make([]byte, n)
+	copy(bNew, b)
+	return bNew
+}
+
+// ResizeNoCopy resizes b to n bytes and returns the resized buffer (which may be newly allocated).
+//
+// If newly allocated buffer is returned then b contents isn't copied to it.
+func ResizeNoCopy(b []byte, n int) []byte {
+	if n <= cap(b) {
+		return b[:n]
 	}
-	return b[:n]
+	return make([]byte, n)
 }
 
 // ToUnsafeString converts b to string without memory allocations.
diff --git a/lib/ingestserver/graphite/server.go b/lib/ingestserver/graphite/server.go
index bdc949f0c..6069f2def 100644
--- a/lib/ingestserver/graphite/server.go
+++ b/lib/ingestserver/graphite/server.go
@@ -135,7 +135,7 @@ func (s *Server) serveUDP(insertHandler func(r io.Reader) error) {
 		go func() {
 			defer wg.Done()
 			var bb bytesutil.ByteBuffer
-			bb.B = bytesutil.Resize(bb.B, 64*1024)
+			bb.B = bytesutil.ResizeNoCopy(bb.B, 64*1024)
 			for {
 				bb.Reset()
 				bb.B = bb.B[:cap(bb.B)]
diff --git a/lib/ingestserver/influx/server.go b/lib/ingestserver/influx/server.go
index 8a412e239..f6fa873cd 100644
--- a/lib/ingestserver/influx/server.go
+++ b/lib/ingestserver/influx/server.go
@@ -135,7 +135,7 @@ func (s *Server) serveUDP(insertHandler func(r io.Reader) error) {
 		go func() {
 			defer wg.Done()
 			var bb bytesutil.ByteBuffer
-			bb.B = bytesutil.Resize(bb.B, 64*1024)
+			bb.B = bytesutil.ResizeNoCopy(bb.B, 64*1024)
 			for {
 				bb.Reset()
 				bb.B = bb.B[:cap(bb.B)]
diff --git a/lib/ingestserver/opentsdb/server.go b/lib/ingestserver/opentsdb/server.go
index a16267a66..ee5056f8c 100644
--- a/lib/ingestserver/opentsdb/server.go
+++ b/lib/ingestserver/opentsdb/server.go
@@ -153,7 +153,7 @@ func (s *Server) serveUDP(insertHandler func(r io.Reader) error) {
 		go func() {
 			defer wg.Done()
 			var bb bytesutil.ByteBuffer
-			bb.B = bytesutil.Resize(bb.B, 64*1024)
+			bb.B = bytesutil.ResizeNoCopy(bb.B, 64*1024)
 			for {
 				bb.Reset()
 				bb.B = bb.B[:cap(bb.B)]
diff --git a/lib/mergeset/block_stream_reader.go b/lib/mergeset/block_stream_reader.go
index 06a28b238..ea2f1b8ee 100644
--- a/lib/mergeset/block_stream_reader.go
+++ b/lib/mergeset/block_stream_reader.go
@@ -211,13 +211,13 @@ func (bsr *blockStreamReader) Next() bool {
 	bsr.bh = &bsr.bhs[bsr.bhIdx]
 	bsr.bhIdx++
 
-	bsr.sb.itemsData = bytesutil.Resize(bsr.sb.itemsData, int(bsr.bh.itemsBlockSize))
+	bsr.sb.itemsData = bytesutil.ResizeNoCopy(bsr.sb.itemsData, int(bsr.bh.itemsBlockSize))
 	if err := fs.ReadFullData(bsr.itemsReader, bsr.sb.itemsData); err != nil {
 		bsr.err = fmt.Errorf("cannot read compressed items block with size %d: %w", bsr.bh.itemsBlockSize, err)
 		return false
 	}
 
-	bsr.sb.lensData = bytesutil.Resize(bsr.sb.lensData, int(bsr.bh.lensBlockSize))
+	bsr.sb.lensData = bytesutil.ResizeNoCopy(bsr.sb.lensData, int(bsr.bh.lensBlockSize))
 	if err := fs.ReadFullData(bsr.lensReader, bsr.sb.lensData); err != nil {
 		bsr.err = fmt.Errorf("cannot read compressed lens block with size %d: %w", bsr.bh.lensBlockSize, err)
 		return false
@@ -260,7 +260,7 @@ func (bsr *blockStreamReader) readNextBHS() error {
 	bsr.mrIdx++
 
 	// Read compressed index block.
-	bsr.packedBuf = bytesutil.Resize(bsr.packedBuf, int(mr.indexBlockSize))
+	bsr.packedBuf = bytesutil.ResizeNoCopy(bsr.packedBuf, int(mr.indexBlockSize))
 	if err := fs.ReadFullData(bsr.indexReader, bsr.packedBuf); err != nil {
 		return fmt.Errorf("cannot read compressed index block with size %d: %w", mr.indexBlockSize, err)
 	}
diff --git a/lib/mergeset/encoding.go b/lib/mergeset/encoding.go
index 516d20eac..2e9643a60 100644
--- a/lib/mergeset/encoding.go
+++ b/lib/mergeset/encoding.go
@@ -119,7 +119,7 @@ func (ib *inmemoryBlock) Add(x []byte) bool {
 	}
 	if cap(data) < maxInmemoryBlockSize {
 		dataLen := len(data)
-		data = bytesutil.Resize(data, maxInmemoryBlockSize)[:dataLen]
+		data = bytesutil.ResizeWithCopy(data, maxInmemoryBlockSize)[:dataLen]
 	}
 	dataLen := len(data)
 	data = append(data, x...)
@@ -141,7 +141,7 @@ func (ib *inmemoryBlock) sort() {
 	data := ib.data
 	items := ib.items
 	bb := bbPool.Get()
-	b := bytesutil.Resize(bb.B, len(data))
+	b := bytesutil.ResizeNoCopy(bb.B, len(data))
 	b = b[:0]
 	for i, it := range items {
 		bLen := len(b)
@@ -378,12 +378,12 @@ func (ib *inmemoryBlock) UnmarshalData(sb *storageBlock, firstItem, commonPrefix
 		return fmt.Errorf("unexpected tail left unmarshaling %d lens; tail size=%d; contents=%X", itemsCount, len(tail), tail)
 	}
 	lens[0] = uint64(len(firstItem) - len(commonPrefix))
-	dataLen := uint64(len(commonPrefix) * int(itemsCount))
-	dataLen += lens[0]
+	dataLen := len(commonPrefix) * int(itemsCount)
+	dataLen += int(lens[0])
 	for i, xLen := range is.A {
 		itemLen := xLen ^ lens[i]
 		lens[i+1] = itemLen
-		dataLen += itemLen
+		dataLen += int(itemLen)
 	}
 
 	// Unmarshal items data.
@@ -391,7 +391,10 @@ func (ib *inmemoryBlock) UnmarshalData(sb *storageBlock, firstItem, commonPrefix
 	if err != nil {
 		return fmt.Errorf("cannot decompress lensData: %w", err)
 	}
-	data := bytesutil.Resize(ib.data, maxInmemoryBlockSize)
+	// Resize ib.data to dataLen instead of maxInmemoryBlockSize,
+	// since the data isn't going to be resized after unmarshaling.
+	// This may save memory for caching the unmarshaled block.
+	data := bytesutil.ResizeNoCopy(ib.data, dataLen)
 	if n := int(itemsCount) - cap(ib.items); n > 0 {
 		ib.items = append(ib.items[:cap(ib.items)], make([]Item, n)...)
 	}
@@ -417,12 +420,12 @@ func (ib *inmemoryBlock) UnmarshalData(sb *storageBlock, firstItem, commonPrefix
 		if prefixLen > uint64(len(prevItem)) {
 			return fmt.Errorf("prefixLen cannot exceed %d; got %d", len(prevItem), prefixLen)
 		}
-		dataLen := len(data)
+		dataStart := len(data)
 		data = append(data, commonPrefix...)
 		data = append(data, prevItem[:prefixLen]...)
 		data = append(data, b[:suffixLen]...)
 		items[i] = Item{
-			Start: uint32(dataLen),
+			Start: uint32(dataStart),
 			End:   uint32(len(data)),
 		}
 		b = b[suffixLen:]
@@ -431,7 +434,7 @@ func (ib *inmemoryBlock) UnmarshalData(sb *storageBlock, firstItem, commonPrefix
 	if len(b) > 0 {
 		return fmt.Errorf("unexpected tail left after itemsData with len %d: %q", len(b), b)
 	}
-	if uint64(len(data)) != dataLen {
+	if len(data) != dataLen {
 		return fmt.Errorf("unexpected data len; got %d; want %d", len(data), dataLen)
 	}
 	ib.data = data
@@ -489,7 +492,7 @@ func (ib *inmemoryBlock) unmarshalDataPlain(sb *storageBlock, firstItem []byte,
 	// Unmarshal items data.
 	data := ib.data
 	items := ib.items
-	data = bytesutil.Resize(data, len(firstItem)+len(sb.itemsData)+len(commonPrefix)*int(itemsCount))
+	data = bytesutil.ResizeNoCopy(data, len(firstItem)+len(sb.itemsData)+len(commonPrefix)*int(itemsCount))
 	data = append(data[:0], firstItem...)
 	items = append(items[:0], Item{
 		Start: 0,
diff --git a/lib/mergeset/part_search.go b/lib/mergeset/part_search.go
index 479070a00..2e7979bf2 100644
--- a/lib/mergeset/part_search.go
+++ b/lib/mergeset/part_search.go
@@ -274,7 +274,7 @@ func (ps *partSearch) nextBHS() error {
 }
 
 func (ps *partSearch) readIndexBlock(mr *metaindexRow) (*indexBlock, error) {
-	ps.compressedIndexBuf = bytesutil.Resize(ps.compressedIndexBuf, int(mr.indexBlockSize))
+	ps.compressedIndexBuf = bytesutil.ResizeNoCopy(ps.compressedIndexBuf, int(mr.indexBlockSize))
 	ps.p.indexFile.MustReadAt(ps.compressedIndexBuf, int64(mr.indexBlockOffset))
 
 	var err error
@@ -311,10 +311,10 @@ func (ps *partSearch) getInmemoryBlock(bh *blockHeader) (*inmemoryBlock, error)
 func (ps *partSearch) readInmemoryBlock(bh *blockHeader) (*inmemoryBlock, error) {
 	ps.sb.Reset()
 
-	ps.sb.itemsData = bytesutil.Resize(ps.sb.itemsData, int(bh.itemsBlockSize))
+	ps.sb.itemsData = bytesutil.ResizeNoCopy(ps.sb.itemsData, int(bh.itemsBlockSize))
 	ps.p.itemsFile.MustReadAt(ps.sb.itemsData, int64(bh.itemsBlockOffset))
 
-	ps.sb.lensData = bytesutil.Resize(ps.sb.lensData, int(bh.lensBlockSize))
+	ps.sb.lensData = bytesutil.ResizeNoCopy(ps.sb.lensData, int(bh.lensBlockSize))
 	ps.p.lensFile.MustReadAt(ps.sb.lensData, int64(bh.lensBlockOffset))
 
 	ib := getInmemoryBlock()
diff --git a/lib/persistentqueue/persistentqueue.go b/lib/persistentqueue/persistentqueue.go
index 3bbb9c519..66ad247f9 100644
--- a/lib/persistentqueue/persistentqueue.go
+++ b/lib/persistentqueue/persistentqueue.go
@@ -499,7 +499,7 @@ func (q *queue) readBlock(dst []byte) ([]byte, error) {
 again:
 	// Read block len.
 	header := headerBufPool.Get()
-	header.B = bytesutil.Resize(header.B, 8)
+	header.B = bytesutil.ResizeNoCopy(header.B, 8)
 	err := q.readFull(header.B)
 	blockLen := encoding.UnmarshalUint64(header.B)
 	headerBufPool.Put(header)
@@ -520,7 +520,7 @@ again:
 
 	// Read block contents.
 	dstLen := len(dst)
-	dst = bytesutil.Resize(dst, dstLen+int(blockLen))
+	dst = bytesutil.ResizeWithCopy(dst, dstLen+int(blockLen))
 	if err := q.readFull(dst[dstLen:]); err != nil {
 		logger.Errorf("skipping corrupted %q, since contents with size %d bytes cannot be read from it: %s", q.readerPath, blockLen, err)
 		if err := q.skipBrokenChunkFile(); err != nil {
diff --git a/lib/protoparser/common/lines_reader.go b/lib/protoparser/common/lines_reader.go
index 6e9ed3fb2..fc8f52780 100644
--- a/lib/protoparser/common/lines_reader.go
+++ b/lib/protoparser/common/lines_reader.go
@@ -40,7 +40,7 @@ func ReadLinesBlock(r io.Reader, dstBuf, tailBuf []byte) ([]byte, []byte, error)
 func ReadLinesBlockExt(r io.Reader, dstBuf, tailBuf []byte, maxLineLen int) ([]byte, []byte, error) {
 	startTime := time.Now()
 	if cap(dstBuf) < defaultBlockSize {
-		dstBuf = bytesutil.Resize(dstBuf, defaultBlockSize)
+		dstBuf = bytesutil.ResizeNoCopy(dstBuf, defaultBlockSize)
 	}
 	dstBuf = append(dstBuf[:0], tailBuf...)
 	tailBuf = tailBuf[:0]
@@ -79,7 +79,7 @@ again:
 		if cap(dstBuf) < 2*len(dstBuf) {
 			// Increase dsbBuf capacity, so more data could be read into it.
 			dstBufLen := len(dstBuf)
-			dstBuf = bytesutil.Resize(dstBuf, 2*cap(dstBuf))
+			dstBuf = bytesutil.ResizeWithCopy(dstBuf, 2*cap(dstBuf))
 			dstBuf = dstBuf[:dstBufLen]
 		}
 		goto again
diff --git a/lib/protoparser/native/streamparser.go b/lib/protoparser/native/streamparser.go
index fa4c5e3ce..80f89581e 100644
--- a/lib/protoparser/native/streamparser.go
+++ b/lib/protoparser/native/streamparser.go
@@ -84,7 +84,7 @@ func ParseStream(req *http.Request, callback func(block *Block) error) error {
 			wg.Wait()
 			return fmt.Errorf("too big metricName size; got %d; shouldn't exceed %d", bufSize, 1024*1024)
 		}
-		uw.metricNameBuf = bytesutil.Resize(uw.metricNameBuf, int(bufSize))
+		uw.metricNameBuf = bytesutil.ResizeNoCopy(uw.metricNameBuf, int(bufSize))
 		if _, err := io.ReadFull(br, uw.metricNameBuf); err != nil {
 			readErrors.Inc()
 			wg.Wait()
@@ -105,7 +105,7 @@ func ParseStream(req *http.Request, callback func(block *Block) error) error {
 			wg.Wait()
 			return fmt.Errorf("too big native block size; got %d; shouldn't exceed %d", bufSize, 1024*1024)
 		}
-		uw.blockBuf = bytesutil.Resize(uw.blockBuf, int(bufSize))
+		uw.blockBuf = bytesutil.ResizeNoCopy(uw.blockBuf, int(bufSize))
 		if _, err := io.ReadFull(br, uw.blockBuf); err != nil {
 			readErrors.Inc()
 			wg.Wait()
diff --git a/lib/storage/block_stream_reader.go b/lib/storage/block_stream_reader.go
index 72b1966c7..6398adf22 100644
--- a/lib/storage/block_stream_reader.go
+++ b/lib/storage/block_stream_reader.go
@@ -303,7 +303,7 @@ func (bsr *blockStreamReader) readBlock() error {
 	if usePrevTimestamps {
 		bsr.Block.timestampsData = append(bsr.Block.timestampsData[:0], bsr.prevTimestampsData...)
 	} else {
-		bsr.Block.timestampsData = bytesutil.Resize(bsr.Block.timestampsData, int(bsr.Block.bh.TimestampsBlockSize))
+		bsr.Block.timestampsData = bytesutil.ResizeNoCopy(bsr.Block.timestampsData, int(bsr.Block.bh.TimestampsBlockSize))
 		if err := fs.ReadFullData(bsr.timestampsReader, bsr.Block.timestampsData); err != nil {
 			return fmt.Errorf("cannot read timestamps block at offset %d: %w", bsr.timestampsBlockOffset, err)
 		}
@@ -312,7 +312,7 @@ func (bsr *blockStreamReader) readBlock() error {
 	}
 
 	// Read values data.
-	bsr.Block.valuesData = bytesutil.Resize(bsr.Block.valuesData, int(bsr.Block.bh.ValuesBlockSize))
+	bsr.Block.valuesData = bytesutil.ResizeNoCopy(bsr.Block.valuesData, int(bsr.Block.bh.ValuesBlockSize))
 	if err := fs.ReadFullData(bsr.valuesReader, bsr.Block.valuesData); err != nil {
 		return fmt.Errorf("cannot read values block at offset %d: %w", bsr.valuesBlockOffset, err)
 	}
@@ -347,7 +347,7 @@ func (bsr *blockStreamReader) readIndexBlock() error {
 	}
 
 	// Read index block.
-	bsr.compressedIndexData = bytesutil.Resize(bsr.compressedIndexData, int(bsr.mr.IndexBlockSize))
+	bsr.compressedIndexData = bytesutil.ResizeNoCopy(bsr.compressedIndexData, int(bsr.mr.IndexBlockSize))
 	if err := fs.ReadFullData(bsr.indexReader, bsr.compressedIndexData); err != nil {
 		return fmt.Errorf("cannot read index block from index data at offset %d: %w", bsr.indexBlockOffset, err)
 	}
diff --git a/lib/storage/metric_name.go b/lib/storage/metric_name.go
index f16ddd929..6ec6fa18b 100644
--- a/lib/storage/metric_name.go
+++ b/lib/storage/metric_name.go
@@ -370,8 +370,7 @@ func (mn *MetricName) Marshal(dst []byte) []byte {
 		tag := &mn.Tags[i]
 		requiredSize += len(tag.Key) + len(tag.Value) + 2
 	}
-	dst = bytesutil.Resize(dst, requiredSize)
-	dst = dst[:dstLen]
+	dst = bytesutil.ResizeWithCopy(dst, requiredSize)[:dstLen]
 
 	// Marshal MetricGroup
 	dst = marshalTagValue(dst, mn.MetricGroup)
@@ -478,7 +477,7 @@ func MarshalMetricNameRaw(dst []byte, labels []prompb.Label) []byte {
 		dstSize += len(label.Value)
 		dstSize += 4
 	}
-	dst = bytesutil.Resize(dst, dstSize)[:dstLen]
+	dst = bytesutil.ResizeWithCopy(dst, dstSize)[:dstLen]
 
 	// Marshal labels to dst.
 	for i := range labels {
diff --git a/lib/storage/part_search.go b/lib/storage/part_search.go
index 6e7af1951..b2542f663 100644
--- a/lib/storage/part_search.go
+++ b/lib/storage/part_search.go
@@ -211,7 +211,7 @@ func skipSmallMetaindexRows(metaindex []metaindexRow, tsid *TSID) []metaindexRow
 }
 
 func (ps *partSearch) readIndexBlock(mr *metaindexRow) (*indexBlock, error) {
-	ps.compressedIndexBuf = bytesutil.Resize(ps.compressedIndexBuf[:0], int(mr.IndexBlockSize))
+	ps.compressedIndexBuf = bytesutil.ResizeNoCopy(ps.compressedIndexBuf, int(mr.IndexBlockSize))
 	ps.p.indexFile.MustReadAt(ps.compressedIndexBuf, int64(mr.IndexBlockOffset))
 
 	var err error
diff --git a/lib/storage/search.go b/lib/storage/search.go
index 5c6beddb7..9f399a79c 100644
--- a/lib/storage/search.go
+++ b/lib/storage/search.go
@@ -75,10 +75,10 @@ func (br *BlockRef) MustReadBlock(dst *Block, fetchData bool) {
 		return
 	}
 
-	dst.timestampsData = bytesutil.Resize(dst.timestampsData[:0], int(br.bh.TimestampsBlockSize))
+	dst.timestampsData = bytesutil.ResizeNoCopy(dst.timestampsData, int(br.bh.TimestampsBlockSize))
 	br.p.timestampsFile.MustReadAt(dst.timestampsData, int64(br.bh.TimestampsBlockOffset))
 
-	dst.valuesData = bytesutil.Resize(dst.valuesData[:0], int(br.bh.ValuesBlockSize))
+	dst.valuesData = bytesutil.ResizeNoCopy(dst.valuesData, int(br.bh.ValuesBlockSize))
 	br.p.valuesFile.MustReadAt(dst.valuesData, int64(br.bh.ValuesBlockOffset))
 }
 


Test patch:
diff --git a/lib/bytesutil/bytesutil_test.go b/lib/bytesutil/bytesutil_test.go
index 721b5c2ed..c59ff4e86 100644
--- a/lib/bytesutil/bytesutil_test.go
+++ b/lib/bytesutil/bytesutil_test.go
@@ -5,19 +5,62 @@ import (
 	"testing"
 )
 
-func TestResize(t *testing.T) {
+func TestResizeNoCopy(t *testing.T) {
 	for i := 0; i < 1000; i++ {
-		b := Resize(nil, i)
+		b := ResizeNoCopy(nil, i)
 		if len(b) != i {
 			t.Fatalf("invalid b size; got %d; expecting %d", len(b), i)
 		}
-		b1 := Resize(b, i)
+		b1 := ResizeNoCopy(b, i)
 		if len(b1) != len(b) || (len(b) > 0 && &b1[0] != &b[0]) {
-			t.Fatalf("invalid b1; got %x; expecting %x", b1, b)
+			t.Fatalf("invalid b1; got %x; expecting %x", &b1[0], &b[0])
 		}
-		b2 := Resize(b[:0], i)
+		b2 := ResizeNoCopy(b[:0], i)
 		if len(b2) != len(b) || (len(b) > 0 && &b2[0] != &b[0]) {
-			t.Fatalf("invalid b2; got %x; expecting %x", b2, b)
+			t.Fatalf("invalid b2; got %x; expecting %x", &b2[0], &b[0])
+		}
+		if i > 0 {
+			b[0] = 123
+			b3 := ResizeNoCopy(b, i+1)
+			if len(b3) != i+1 {
+				t.Fatalf("invalid b3 len; got %d; want %d", len(b3), i+1)
+			}
+			if &b3[0] == &b[0] {
+				t.Fatalf("b3 must be newly allocated")
+			}
+			if b3[0] != 0 {
+				t.Fatalf("b3[0] must be zeroed; got %d", b3[0])
+			}
+		}
+	}
+}
+
+func TestResizeWithCopy(t *testing.T) {
+	for i := 0; i < 1000; i++ {
+		b := ResizeWithCopy(nil, i)
+		if len(b) != i {
+			t.Fatalf("invalid b size; got %d; expecting %d", len(b), i)
+		}
+		b1 := ResizeWithCopy(b, i)
+		if len(b1) != len(b) || (len(b) > 0 && &b1[0] != &b[0]) {
+			t.Fatalf("invalid b1; got %x; expecting %x", &b1[0], &b[0])
+		}
+		b2 := ResizeWithCopy(b[:0], i)
+		if len(b2) != len(b) || (len(b) > 0 && &b2[0] != &b[0]) {
+			t.Fatalf("invalid b2; got %x; expecting %x", &b2[0], &b[0])
+		}
+		if i > 0 {
+			b[0] = 123
+			b3 := ResizeWithCopy(b, i+1)
+			if len(b3) != i+1 {
+				t.Fatalf("invalid b3 len; got %d; want %d", len(b3), i+1)
+			}
+			if &b3[0] == &b[0] {
+				t.Fatalf("b3 must be newly allocated for i=%d", i)
+			}
+			if b3[0] != b[0] || b3[0] != 123 {
+				t.Fatalf("b3[0] must equal b[0]; got %d; want %d", b3[0], b[0])
+			}
 		}
 	}
 }