Repository: VictoriaMetrics/VictoriaMetrics
Pull request #5721 (2024-01-30 08:17:51)

make encoding.MarshalVarInt64s faster
Use `Switch/case` to replace `for`.
go compiler might be use jump table to replace compare.

Before:
=== RUN   BenchmarkMarshalVarInt64s/up-to-(1<<6)-1
BenchmarkMarshalVarInt64s/up-to-(1<<6)-1
BenchmarkMarshalVarInt64s/up-to-(1<<6)-1-10              1551774               686.0 ns/op      11661.55 MB/s          0 B/op          0 allocs/op
=== RUN   BenchmarkMarshalVarInt64s/up-to-(1<<13)-1
BenchmarkMarshalVarInt64s/up-to-(1<<13)-1
BenchmarkMarshalVarInt64s/up-to-(1<<13)-1-10              703304              1759 ns/op        4547.44 MB/s           0 B/op          0 allocs/op
=== RUN   BenchmarkMarshalVarInt64s/up-to-(1<<27)-1
BenchmarkMarshalVarInt64s/up-to-(1<<27)-1
BenchmarkMarshalVarInt64s/up-to-(1<<27)-1-10              362886              3343 ns/op        2393.31 MB/s           4 B/op          0 allocs/op
=== RUN   BenchmarkMarshalVarInt64s/up-to-(1<<63)-1
BenchmarkMarshalVarInt64s/up-to-(1<<63)-1
BenchmarkMarshalVarInt64s/up-to-(1<<63)-1-10              212334              6472 ns/op        1236.18 MB/s          18 B/op          0 allocs/op
PASS
ok      [github.com/VictoriaMetrics/VictoriaMetrics/lib/encoding](http://github.com/VictoriaMetrics/VictoriaMetrics/lib/encoding) 7.274s

After:
BenchmarkMarshalVarInt64s/up-to-(1<<6)-1
BenchmarkMarshalVarInt64s/up-to-(1<<6)-1-10               712990              1443 ns/op        5545.80 MB/s           0 B/op          0 allocs/op
=== RUN   BenchmarkMarshalVarInt64s/up-to-(1<<13)-1
BenchmarkMarshalVarInt64s/up-to-(1<<13)-1
BenchmarkMarshalVarInt64s/up-to-(1<<13)-1-10              796993              1475 ns/op        5425.56 MB/s           0 B/op          0 allocs/op
=== RUN   BenchmarkMarshalVarInt64s/up-to-(1<<27)-1
BenchmarkMarshalVarInt64s/up-to-(1<<27)-1
BenchmarkMarshalVarInt64s/up-to-(1<<27)-1-10              673441              1757 ns/op        4552.40 MB/s           2 B/op          0 allocs/op
=== RUN   BenchmarkMarshalVarInt64s/up-to-(1<<63)-1
BenchmarkMarshalVarInt64s/up-to-(1<<63)-1
BenchmarkMarshalVarInt64s/up-to-(1<<63)-1-10              439021              2738 ns/op        2921.60 MB/s           8 B/op          0 allocs/op

====

BTW, c version is faster 40 times than golang version.
So go assemly version is on the way.
I will commit faster version next time.



codecov[bot]: ## [Codecov](https://app.codecov.io/gh/VictoriaMetrics/VictoriaMetrics/pull/5721?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Comparison is base [(`00db646`)](https://app.codecov.io/gh/VictoriaMetrics/VictoriaMetrics/commit/00db646851d704720cce4a42e4e51f02a427561b?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics) 58.67% compared to head [(`151fbf9`)](https://app.codecov.io/gh/VictoriaMetrics/VictoriaMetrics/pull/5721?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics) 58.68%.
> Report is 3 commits behind head on cluster.


<details><summary>Additional details and impacted files</summary>


```diff
@@             Coverage Diff             @@
##           cluster    #5721      +/-   ##
===========================================
+ Coverage    58.67%   58.68%   +0.01%     
===========================================
  Files          416      416              
  Lines        78660    78684      +24     
===========================================
+ Hits         46154    46179      +25     
- Misses       29960    29961       +1     
+ Partials      2546     2544       -2     
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/VictoriaMetrics/VictoriaMetrics/pull/5721?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics).

ahfuzhang: > @ahfuzhang , thanks for the optimization! It would be great optimizing the case for small integers with up to `1<<6-1` values, since this case is very common in real world. According to the provided benchmark results, the performance for this case has been dropped from 11.7Gbytes/sec to 5.5Gbytes/sec.

because I don't add fast path for 1 byte marshal.
Wait a moment pls, I add it and test again.
ahfuzhang: New bechmark:

=== RUN   BenchmarkMarshalVarInt64s/up-to-(1<<6)-1
BenchmarkMarshalVarInt64s/up-to-(1<<6)-1
BenchmarkMarshalVarInt64s/up-to-(1<<6)-1-10              1795152               659.3 ns/op      12133.53 MB/s          0 B/op          0 allocs/op
=== RUN   BenchmarkMarshalVarInt64s/up-to-(1<<13)-1
BenchmarkMarshalVarInt64s/up-to-(1<<13)-1
BenchmarkMarshalVarInt64s/up-to-(1<<13)-1-10              673744              1614 ns/op        4958.10 MB/s           0 B/op          0 allocs/op
=== RUN   BenchmarkMarshalVarInt64s/up-to-(1<<27)-1
BenchmarkMarshalVarInt64s/up-to-(1<<27)-1
BenchmarkMarshalVarInt64s/up-to-(1<<27)-1-10              645064              1742 ns/op        4593.73 MB/s           2 B/op          0 allocs/op
=== RUN   BenchmarkMarshalVarInt64s/up-to-(1<<63)-1
BenchmarkMarshalVarInt64s/up-to-(1<<63)-1
BenchmarkMarshalVarInt64s/up-to-(1<<63)-1-10              435181              2515 ns/op        3180.43 MB/s           8 B/op          0 allocs/op
PASS
ok      [github.com/VictoriaMetrics/VictoriaMetrics/lib/encoding](http://github.com/VictoriaMetrics/VictoriaMetrics/lib/encoding) 5.385s

And new code is:
```go
func MarshalVarInt64s(dst []byte, vs []int64) []byte {
	for _, v := range vs {
		n := uint64((v << 1) ^ (v >> 63))
		if n < (1 << 7) {
			dst = append(dst, byte(n))
			continue
		}
		switch (64 - bits.LeadingZeros64(n>>1)) / 7 {
		case 0:
			dst = append(dst, byte(n))
		case 1:
			dst = append(dst, byte(n|0x80), byte(n>>7))
		case 2:
			dst = append(dst, byte(n|0x80), byte((n>>7)|0x80), byte(n>>14))
		case 3:
			dst = append(dst, byte(n|0x80), byte((n>>7)|0x80), byte((n>>14)|0x80), byte(n>>21))
		case 4:
			dst = append(dst, byte(n|0x80), byte((n>>7)|0x80), byte((n>>14)|0x80), byte((n>>21)|0x80), byte(n>>28))
		case 5:
			dst = append(dst, byte(n|0x80), byte((n>>7)|0x80), byte((n>>14)|0x80), byte((n>>21)|0x80), byte(n>>28|0x80), byte(n>>35))
		case 6:
			dst = append(dst, byte(n|0x80), byte((n>>7)|0x80), byte((n>>14)|0x80), byte((n>>21)|0x80), byte(n>>28|0x80), byte(n>>35|0x80), byte(n>>42))
		case 7:
			dst = append(dst, byte(n|0x80), byte((n>>7)|0x80), byte((n>>14)|0x80), byte((n>>21)|0x80), byte(n>>28|0x80), byte(n>>35|0x80), byte(n>>42|0x80), byte(n>>49))
		case 8:
			dst = append(dst, byte(n|0x80), byte((n>>7)|0x80), byte((n>>14)|0x80), byte((n>>21)|0x80), byte(n>>28|0x80), byte(n>>35|0x80), byte(n>>42|0x80), byte(n>>49|0x80), byte(n>>56))
		case 9:
			fallthrough
		default:
			dst = append(dst, byte(n|0x80), byte((n>>7)|0x80), byte((n>>14)|0x80), byte((n>>21)|0x80), byte(n>>28|0x80), byte(n>>35|0x80), byte(n>>42|0x80), byte(n>>49|0x80), byte(n>>56|0x80), byte(n>>63))
		}
	}
	return dst
}
```
ahfuzhang: And I make `UnmarshalVarInt64s` 28% faster.

Before:
=== RUN   BenchmarkUnmarshalVarInt64s/up-to-(1<<6)-1
BenchmarkUnmarshalVarInt64s/up-to-(1<<6)-1
BenchmarkUnmarshalVarInt64s/up-to-(1<<6)-1-10            1579142               793.7 ns/op      10079.69 MB/s          0 B/op       0 allocs/op
=== RUN   BenchmarkUnmarshalVarInt64s/up-to-(1<<13)-1
BenchmarkUnmarshalVarInt64s/up-to-(1<<13)-1
BenchmarkUnmarshalVarInt64s/up-to-(1<<13)-1-10            568491              1816 ns/op        4404.86 MB/s           0 B/op       0 allocs/op
=== RUN   BenchmarkUnmarshalVarInt64s/up-to-(1<<27)-1
BenchmarkUnmarshalVarInt64s/up-to-(1<<27)-1
BenchmarkUnmarshalVarInt64s/up-to-(1<<27)-1-10            231183              4840 ns/op        1652.94 MB/s           0 B/op       0 allocs/op
=== RUN   BenchmarkUnmarshalVarInt64s/up-to-(1<<63)-1
BenchmarkUnmarshalVarInt64s/up-to-(1<<63)-1
BenchmarkUnmarshalVarInt64s/up-to-(1<<63)-1-10            124256              8935 ns/op         895.37 MB/s           0 B/op       0 allocs/op

After:
BenchmarkUnmarshalVarInt64s/up-to-(1<<6)-1-10            1543092               796.8 ns/op      10039.85 MB/s          0 B/op          0 allocs/op
=== RUN   BenchmarkUnmarshalVarInt64s/up-to-(1<<13)-1
BenchmarkUnmarshalVarInt64s/up-to-(1<<13)-1
BenchmarkUnmarshalVarInt64s/up-to-(1<<13)-1-10           1000000              1123 ns/op        7125.49 MB/s           0 B/op          0 allocs/op
=== RUN   BenchmarkUnmarshalVarInt64s/up-to-(1<<27)-1
BenchmarkUnmarshalVarInt64s/up-to-(1<<27)-1
BenchmarkUnmarshalVarInt64s/up-to-(1<<27)-1-10            282601              3802 ns/op        2104.30 MB/s           0 B/op          0 allocs/op
=== RUN   BenchmarkUnmarshalVarInt64s/up-to-(1<<63)-1
BenchmarkUnmarshalVarInt64s/up-to-(1<<63)-1
BenchmarkUnmarshalVarInt64s/up-to-(1<<63)-1-10            179688              6460 ns/op        1238.30 MB/s           0 B/op          0 allocs/op

Code is:
```go
// UnmarshalVarInt64s unmarshals len(dst) int64 values from src to dst
// and returns the remaining tail from src.
func UnmarshalVarInt64s(dst []int64, src []byte) ([]byte, error) {
	idx := uint(0)
	for i := range dst {
		if idx >= uint(len(src)) {
			return nil, fmt.Errorf("cannot unmarshal varint from empty data")
		}
		c := src[idx]
		idx++
		if c < 0x80 {
			// Fast path
			v := int8(c>>1) ^ (int8(c<<7) >> 7) // zig-zag decoding without branching.
			dst[i] = int64(v)
			continue
		}
		if idx < uint(len(src)) && src[idx] < 0x80 {
			// Fast path, for 2 bytes
			n := uint64(c&0x7f) | (uint64(src[idx]&0x7f) << 7)
			dst[i] = int64(n>>1) ^ (int64(n<<63) >> 63)
			idx++
			continue
		}
		j := idx + 1
		for ; j < uint(len(src)); j++ { // find end loc
			if src[j] < 0x80 {
				break
			}
		}
		if j-idx > 10 {
			return nil, fmt.Errorf("cannot unmarshal varint, buffer too long, len=%d", j-idx)
		}
		dst[i] = unmarshalVarInt64ForOne(src[idx-1 : j+1])
		idx = j + 1
	}
	return src[idx:], nil
}

func unmarshalVarInt64ForOne(buf []byte) int64 {
	var n uint64
	switch len(buf) {
	case 1:
		n = uint64(buf[0] & 0x7F)
	case 2:
		n = uint64(buf[0]&0x7F) | (uint64(buf[1]&0x7F) << 7)
	case 3:
		n = uint64(buf[0]&0x7F) | (uint64(buf[1]&0x7F) << 7) | (uint64(buf[2]&0x7F) << 14)
	case 4:
		n = uint64(buf[0]&0x7F) | (uint64(buf[1]&0x7F) << 7) | (uint64(buf[2]&0x7F) << 14) | (uint64(buf[3]&0x7F) << 21)
	case 5:
		n = uint64(buf[0]&0x7F) | (uint64(buf[1]&0x7F) << 7) | (uint64(buf[2]&0x7F) << 14) | (uint64(buf[3]&0x7F) << 21) | (uint64(buf[4]&0x7F) << 28)
	case 6:
		n = uint64(buf[0]&0x7F) | (uint64(buf[1]&0x7F) << 7) | (uint64(buf[2]&0x7F) << 14) | (uint64(buf[3]&0x7F) << 21) | (uint64(buf[4]&0x7F) << 28) | (uint64(buf[5]&0x7F) << 35)
	case 7:
		n = uint64(buf[0]&0x7F) | (uint64(buf[1]&0x7F) << 7) | (uint64(buf[2]&0x7F) << 14) | (uint64(buf[3]&0x7F) << 21) | (uint64(buf[4]&0x7F) << 28) | (uint64(buf[5]&0x7F) << 35) | (uint64(buf[6]&0x7F) << 42)
	case 8:
		n = uint64(buf[0]&0x7F) | (uint64(buf[1]&0x7F) << 7) | (uint64(buf[2]&0x7F) << 14) | (uint64(buf[3]&0x7F) << 21) | (uint64(buf[4]&0x7F) << 28) | (uint64(buf[5]&0x7F) << 35) | (uint64(buf[6]&0x7F) << 42) | (uint64(buf[7]&0x7F) << 49)
	case 9:
		n = uint64(buf[0]&0x7F) | (uint64(buf[1]&0x7F) << 7) | (uint64(buf[2]&0x7F) << 14) | (uint64(buf[3]&0x7F) << 21) | (uint64(buf[4]&0x7F) << 28) | (uint64(buf[5]&0x7F) << 35) | (uint64(buf[6]&0x7F) << 42) | (uint64(buf[7]&0x7F) << 49) | (uint64(buf[8]&0x7F) << 56)
	case 10:
		n = uint64(buf[0]&0x7F) | (uint64(buf[1]&0x7F) << 7) | (uint64(buf[2]&0x7F) << 14) | (uint64(buf[3]&0x7F) << 21) | (uint64(buf[4]&0x7F) << 28) | (uint64(buf[5]&0x7F) << 35) | (uint64(buf[6]&0x7F) << 42) | (uint64(buf[7]&0x7F) << 49) | (uint64(buf[8]&0x7F) << 56) | (uint64(buf[9]&0x7F) << 63)
	default:
		panic("impossible error: buf length must be 1 to 10")
	}
	return int64(n>>1) ^ (int64(n<<63) >> 63)
}

```

Patch:
diff --git a/app/vmselect/netstorage/netstorage.go b/app/vmselect/netstorage/netstorage.go
index 0faa59c81..5e855892e 100644
--- a/app/vmselect/netstorage/netstorage.go
+++ b/app/vmselect/netstorage/netstorage.go
@@ -1382,10 +1382,23 @@ type blockAddrs struct {
 	addrs []tmpBlockAddr
 }
 
-func haveSameBlockAddrTails(a, b []tmpBlockAddr) bool {
-	sha := (*reflect.SliceHeader)(unsafe.Pointer(&a))
-	shb := (*reflect.SliceHeader)(unsafe.Pointer(&b))
-	return sha.Data+uintptr(sha.Len)*unsafe.Sizeof(tmpBlockAddr{}) == shb.Data+uintptr(shb.Len)*unsafe.Sizeof(tmpBlockAddr{})
+// canAppendToBlockAddrPool returns true if a points to the pool and the last item in a is the last item in the pool.
+//
+// In this case it is safe appending an item to the pool and then updating the a, so it refers to the extended slice.
+//
+// True is also returned if a is nil, since in this case it is safe appending an item to the pool and pointing a
+// to the last item in the pool.
+func canAppendToBlockAddrPool(pool, a []tmpBlockAddr) bool {
+	if a == nil {
+		return true
+	}
+	if len(a) > len(pool) {
+		// a doesn't belong to pool
+		return false
+	}
+	shPool := (*reflect.SliceHeader)(unsafe.Pointer(&pool))
+	shA := (*reflect.SliceHeader)(unsafe.Pointer(&a))
+	return shPool.Data+uintptr(shPool.Len)*unsafe.Sizeof(tmpBlockAddr{}) == shA.Data+uintptr(shA.Len)*unsafe.Sizeof(tmpBlockAddr{})
 }
 
 func (tbfwLocal *tmpBlocksFileWrapperShard) newBlockAddrs() int {
@@ -1442,7 +1455,7 @@ func (tbfw *tmpBlocksFileWrapper) RegisterAndWriteBlock(mb *storage.MetricBlock,
 		addrsPool = make([]tmpBlockAddr, 0, maxFastAllocBlockSize/unsafe.Sizeof(tmpBlockAddr{}))
 		tbfwLocal.addrsPool = addrsPool
 	}
-	if addrs.addrs == nil || haveSameBlockAddrTails(addrs.addrs, addrsPool) {
+	if canAppendToBlockAddrPool(addrsPool, addrs.addrs) {
 		// It is safe appending addr to addrsPool, since there are no other items added there yet.
 		addrsPool = append(addrsPool, addr)
 		tbfwLocal.addrsPool = addrsPool
diff --git a/dashboards/victoriametrics.json b/dashboards/victoriametrics.json
index dcb95cdbc..3d8a6a1bc 100644
--- a/dashboards/victoriametrics.json
+++ b/dashboards/victoriametrics.json
@@ -76,7 +76,7 @@
           "uid": "$ds"
         },
         "enable": true,
-        "expr": "sum(vm_app_version{job=~\"$job\", instance=~\"$instance\"}) by(version) unless (sum(vm_app_version{job=~\"$job\", instance=~\"$instance\"} offset 20m) by(short_versionversion))",
+        "expr": "sum(vm_app_version{job=~\"$job\", instance=~\"$instance\"}) by(version) unless (sum(vm_app_version{job=~\"$job\", instance=~\"$instance\"} offset 20m) by(version))",
         "hide": true,
         "iconColor": "dark-blue",
         "name": "version",
diff --git a/dashboards/vm/victoriametrics.json b/dashboards/vm/victoriametrics.json
index a77231d78..1162eb3a7 100644
--- a/dashboards/vm/victoriametrics.json
+++ b/dashboards/vm/victoriametrics.json
@@ -77,7 +77,7 @@
           "uid": "$ds"
         },
         "enable": true,
-        "expr": "sum(vm_app_version{job=~\"$job\", instance=~\"$instance\"}) by(version) unless (sum(vm_app_version{job=~\"$job\", instance=~\"$instance\"} offset 20m) by(short_versionversion))",
+        "expr": "sum(vm_app_version{job=~\"$job\", instance=~\"$instance\"}) by(version) unless (sum(vm_app_version{job=~\"$job\", instance=~\"$instance\"} offset 20m) by(version))",
         "hide": true,
         "iconColor": "dark-blue",
         "name": "version",
diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md
index 1d8a4c7a5..c851e0061 100644
--- a/docs/CHANGELOG.md
+++ b/docs/CHANGELOG.md
@@ -28,7 +28,11 @@ The sandbox cluster installation is running under the constant load generated by
 
 ## tip
 
+* FEATURE: [vmagent](https://docs.victoriametrics.com/vmagent.html): add support for `username_file` option at `basic_auth` section in [`scrape_configs`](https://docs.victoriametrics.com/sd_configs/#http-api-client-options). See [this feature request](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/5511). Thanks to @wasim-nihal for [the initial implementation](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/5720).
+
+* BUGFIX: fix `runtime error: slice bounds out of range` panic, which can occur during query execution. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/5733). The bug has been introduced in `v1.97.0`.
 * BUGFIX: [MetricsQL](https://docs.victoriametrics.com/MetricsQL.html): properly handle `avg_over_time({some_filter}[d]) keep_metric_names` queries, where [`some_filter`](https://docs.victoriametrics.com/keyconcepts/#filtering) matches multiple time series with multiple names, while `d` is bigger or equal to `3h`. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/5556).
+* BUGFIX: dashboards/single: fix typo in query for `version` annotation which falsely produced many version change events.
 
 ## [v1.97.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.97.0)
 
diff --git a/docs/sd_configs.md b/docs/sd_configs.md
index cd6001c7f..ba879a776 100644
--- a/docs/sd_configs.md
+++ b/docs/sd_configs.md
@@ -821,6 +821,7 @@ scrape_configs:
     #
     # basic_auth:
     #  username: "..."
+    #  username_file: "..."  # is mutually-exclusive with username
     #  password: "..."
     #  password_file: "..."  # is mutually-exclusive with password
 
@@ -1625,8 +1626,9 @@ and in the majority of [supported service discovery configs](#supported-service-
     # basic_auth is an optional HTTP basic authentication configuration.
     # basic_auth:
     #   username: "..."
+    #   username_file: "..."  # is mutually-exclusive with username
     #   password: "..."
-    #   password_file: "..."
+    #   password_file: "..."  # is mutually-exclusive with password
 
     # bearer_token is an optional Bearer token to send in every HTTP API request during service discovery.
     # bearer_token: "..."
@@ -1663,8 +1665,9 @@ and in the majority of [supported service discovery configs](#supported-service-
     # proxy_basic_auth is an optional HTTP basic authentication configuration for the proxy_url.
     # proxy_basic_auth:
     #   username: "..."
+    #   username_file: "..."  # is mutually-exclusive with username
     #   password: "..."
-    #   password_file: "..."
+    #   password_file: "..."  # is mutually-exclusive with password
 
     # proxy_bearer_token is an optional Bearer token to send to proxy_url.
     # proxy_bearer_token: "..."
diff --git a/lib/encoding/int.go b/lib/encoding/int.go
index 7814d6ac7..55c7554b3 100644
--- a/lib/encoding/int.go
+++ b/lib/encoding/int.go
@@ -3,6 +3,7 @@ package encoding
 import (
 	"encoding/binary"
 	"fmt"
+	"math/bits"
 	"sync"
 )
 
@@ -91,21 +92,35 @@ func MarshalVarInt64(dst []byte, v int64) []byte {
 // MarshalVarInt64s appends marshaled vs to dst and returns the result.
 func MarshalVarInt64s(dst []byte, vs []int64) []byte {
 	for _, v := range vs {
-		if v < 0x40 && v > -0x40 {
-			// Fast path
-			c := int8(v)
-			v := (c << 1) ^ (c >> 7) // zig-zag encoding without branching.
-			dst = append(dst, byte(v))
+		n := uint64((v << 1) ^ (v >> 63))
+		if n < (1 << 7) {
+			dst = append(dst, byte(n))
 			continue
 		}
-
-		v = (v << 1) ^ (v >> 63) // zig-zag encoding without branching.
-		u := uint64(v)
-		for u > 0x7f {
-			dst = append(dst, 0x80|byte(u))
-			u >>= 7
+		switch (64 - bits.LeadingZeros64(n>>1)) / 7 {
+		case 0:
+			dst = append(dst, byte(n))
+		case 1:
+			dst = append(dst, byte(n|0x80), byte(n>>7))
+		case 2:
+			dst = append(dst, byte(n|0x80), byte((n>>7)|0x80), byte(n>>14))
+		case 3:
+			dst = append(dst, byte(n|0x80), byte((n>>7)|0x80), byte((n>>14)|0x80), byte(n>>21))
+		case 4:
+			dst = append(dst, byte(n|0x80), byte((n>>7)|0x80), byte((n>>14)|0x80), byte((n>>21)|0x80), byte(n>>28))
+		case 5:
+			dst = append(dst, byte(n|0x80), byte((n>>7)|0x80), byte((n>>14)|0x80), byte((n>>21)|0x80), byte(n>>28|0x80), byte(n>>35))
+		case 6:
+			dst = append(dst, byte(n|0x80), byte((n>>7)|0x80), byte((n>>14)|0x80), byte((n>>21)|0x80), byte(n>>28|0x80), byte(n>>35|0x80), byte(n>>42))
+		case 7:
+			dst = append(dst, byte(n|0x80), byte((n>>7)|0x80), byte((n>>14)|0x80), byte((n>>21)|0x80), byte(n>>28|0x80), byte(n>>35|0x80), byte(n>>42|0x80), byte(n>>49))
+		case 8:
+			dst = append(dst, byte(n|0x80), byte((n>>7)|0x80), byte((n>>14)|0x80), byte((n>>21)|0x80), byte(n>>28|0x80), byte(n>>35|0x80), byte(n>>42|0x80), byte(n>>49|0x80), byte(n>>56))
+		case 9:
+			fallthrough
+		default:
+			dst = append(dst, byte(n|0x80), byte((n>>7)|0x80), byte((n>>14)|0x80), byte((n>>21)|0x80), byte(n>>28|0x80), byte(n>>35|0x80), byte(n>>42|0x80), byte(n>>49|0x80), byte(n>>56|0x80), byte(n>>63))
 		}
-		dst = append(dst, byte(u))
 	}
 	return dst
 }
@@ -134,30 +149,57 @@ func UnmarshalVarInt64s(dst []int64, src []byte) ([]byte, error) {
 			dst[i] = int64(v)
 			continue
 		}
-
-		// Slow path
-		u := uint64(c & 0x7f)
-		startIdx := idx - 1
-		shift := uint8(0)
-		for c >= 0x80 {
-			if idx >= uint(len(src)) {
-				return nil, fmt.Errorf("unexpected end of encoded varint at byte %d; src=%x", idx-startIdx, src[startIdx:])
-			}
-			if idx-startIdx > 9 {
-				return src[idx:], fmt.Errorf("too long encoded varint; the maximum allowed length is 10 bytes; got %d bytes; src=%x",
-					(idx-startIdx)+1, src[startIdx:])
-			}
-			c = src[idx]
+		if idx < uint(len(src)) && src[idx] < 0x80 {
+			// Fast path, for 2 bytes
+			n := uint64(c&0x7f) | (uint64(src[idx]&0x7f) << 7)
+			dst[i] = int64(n>>1) ^ (int64(n<<63) >> 63)
 			idx++
-			shift += 7
-			u |= uint64(c&0x7f) << shift
+			continue
+		}
+		j := idx + 1
+		for ; j < uint(len(src)); j++ { // find end loc
+			if src[j] < 0x80 {
+				break
+			}
+		}
+		if j-idx > 10 {
+			return nil, fmt.Errorf("cannot unmarshal varint, buffer too long, len=%d", j-idx)
 		}
-		v := int64(u>>1) ^ (int64(u<<63) >> 63) // zig-zag decoding without branching.
-		dst[i] = v
+		dst[i] = unmarshalVarInt64ForOne(src[idx-1 : j+1])
+		idx = j + 1
 	}
 	return src[idx:], nil
 }
 
+func unmarshalVarInt64ForOne(buf []byte) int64 {
+	var n uint64
+	switch len(buf) {
+	case 1:
+		n = uint64(buf[0] & 0x7F)
+	case 2:
+		n = uint64(buf[0]&0x7F) | (uint64(buf[1]&0x7F) << 7)
+	case 3:
+		n = uint64(buf[0]&0x7F) | (uint64(buf[1]&0x7F) << 7) | (uint64(buf[2]&0x7F) << 14)
+	case 4:
+		n = uint64(buf[0]&0x7F) | (uint64(buf[1]&0x7F) << 7) | (uint64(buf[2]&0x7F) << 14) | (uint64(buf[3]&0x7F) << 21)
+	case 5:
+		n = uint64(buf[0]&0x7F) | (uint64(buf[1]&0x7F) << 7) | (uint64(buf[2]&0x7F) << 14) | (uint64(buf[3]&0x7F) << 21) | (uint64(buf[4]&0x7F) << 28)
+	case 6:
+		n = uint64(buf[0]&0x7F) | (uint64(buf[1]&0x7F) << 7) | (uint64(buf[2]&0x7F) << 14) | (uint64(buf[3]&0x7F) << 21) | (uint64(buf[4]&0x7F) << 28) | (uint64(buf[5]&0x7F) << 35)
+	case 7:
+		n = uint64(buf[0]&0x7F) | (uint64(buf[1]&0x7F) << 7) | (uint64(buf[2]&0x7F) << 14) | (uint64(buf[3]&0x7F) << 21) | (uint64(buf[4]&0x7F) << 28) | (uint64(buf[5]&0x7F) << 35) | (uint64(buf[6]&0x7F) << 42)
+	case 8:
+		n = uint64(buf[0]&0x7F) | (uint64(buf[1]&0x7F) << 7) | (uint64(buf[2]&0x7F) << 14) | (uint64(buf[3]&0x7F) << 21) | (uint64(buf[4]&0x7F) << 28) | (uint64(buf[5]&0x7F) << 35) | (uint64(buf[6]&0x7F) << 42) | (uint64(buf[7]&0x7F) << 49)
+	case 9:
+		n = uint64(buf[0]&0x7F) | (uint64(buf[1]&0x7F) << 7) | (uint64(buf[2]&0x7F) << 14) | (uint64(buf[3]&0x7F) << 21) | (uint64(buf[4]&0x7F) << 28) | (uint64(buf[5]&0x7F) << 35) | (uint64(buf[6]&0x7F) << 42) | (uint64(buf[7]&0x7F) << 49) | (uint64(buf[8]&0x7F) << 56)
+	case 10:
+		n = uint64(buf[0]&0x7F) | (uint64(buf[1]&0x7F) << 7) | (uint64(buf[2]&0x7F) << 14) | (uint64(buf[3]&0x7F) << 21) | (uint64(buf[4]&0x7F) << 28) | (uint64(buf[5]&0x7F) << 35) | (uint64(buf[6]&0x7F) << 42) | (uint64(buf[7]&0x7F) << 49) | (uint64(buf[8]&0x7F) << 56) | (uint64(buf[9]&0x7F) << 63)
+	default:
+		panic("impossible error: buf length must be 1 to 10")
+	}
+	return int64(n>>1) ^ (int64(n<<63) >> 63)
+}
+
 // MarshalVarUint64 appends marshaled u to dst and returns the result.
 func MarshalVarUint64(dst []byte, u uint64) []byte {
 	var tmp [1]uint64
diff --git a/lib/promauth/config.go b/lib/promauth/config.go
index 6b95a8017..4fafb3d7a 100644
--- a/lib/promauth/config.go
+++ b/lib/promauth/config.go
@@ -92,7 +92,8 @@ type Authorization struct {
 
 // BasicAuthConfig represents basic auth config.
 type BasicAuthConfig struct {
-	Username     string  `yaml:"username"`
+	Username     string  `yaml:"username,omitempty"`
+	UsernameFile string  `yaml:"username_file,omitempty"`
 	Password     *Secret `yaml:"password,omitempty"`
 	PasswordFile string  `yaml:"password_file,omitempty"`
 }
@@ -643,35 +644,54 @@ func (actx *authContext) initFromAuthorization(baseDir string, az *Authorization
 }
 
 func (actx *authContext) initFromBasicAuthConfig(baseDir string, ba *BasicAuthConfig) error {
-	if ba.Username == "" {
-		return fmt.Errorf("missing `username` in `basic_auth` section")
+	username := ba.Username
+	usernameFile := ba.UsernameFile
+	password := ""
+	if ba.Password != nil {
+		password = ba.Password.S
 	}
-	if ba.PasswordFile == "" {
-		// See https://en.wikipedia.org/wiki/Basic_access_authentication
-		token := ba.Username + ":" + ba.Password.String()
-		token64 := base64.StdEncoding.EncodeToString([]byte(token))
-		ah := "Basic " + token64
-		actx.getAuthHeader = func() (string, error) {
-			return ah, nil
-		}
-		actx.authHeaderDigest = fmt.Sprintf("basic(username=%q, password=%q)", ba.Username, ba.Password)
-		return nil
+	passwordFile := ba.PasswordFile
+	if username == "" && usernameFile == "" {
+		return fmt.Errorf("missing `username` and `username_file` in `basic_auth` section; please specify one; " +
+			"see https://docs.victoriametrics.com/sd_configs.html#http-api-client-options")
 	}
-	if ba.Password != nil {
-		return fmt.Errorf("both `password`=%q and `password_file`=%q are set in `basic_auth` section", ba.Password, ba.PasswordFile)
+	if username != "" && usernameFile != "" {
+		return fmt.Errorf("both `username` and `username_file` are set in `basic_auth` section; please specify only one; " +
+			"see https://docs.victoriametrics.com/sd_configs.html#http-api-client-options")
+	}
+	if password != "" && passwordFile != "" {
+		return fmt.Errorf("both `password` and `password_file` are set in `basic_auth` section; please specify only one; " +
+			"see https://docs.victoriametrics.com/sd_configs.html#http-api-client-options")
+	}
+	if usernameFile != "" {
+		usernameFile = fscore.GetFilepath(baseDir, usernameFile)
+	}
+	if passwordFile != "" {
+		passwordFile = fscore.GetFilepath(baseDir, passwordFile)
 	}
-	filePath := fscore.GetFilepath(baseDir, ba.PasswordFile)
 	actx.getAuthHeader = func() (string, error) {
-		password, err := fscore.ReadPasswordFromFileOrHTTP(filePath)
-		if err != nil {
-			return "", fmt.Errorf("cannot read password from `password_file`=%q set in `basic_auth` section: %w", ba.PasswordFile, err)
+		usernameLocal := username
+		if usernameFile != "" {
+			s, err := fscore.ReadPasswordFromFileOrHTTP(usernameFile)
+			if err != nil {
+				return "", fmt.Errorf("cannot read username from `username_file`=%q: %w", usernameFile, err)
+			}
+			usernameLocal = s
+		}
+		passwordLocal := password
+		if passwordFile != "" {
+			s, err := fscore.ReadPasswordFromFileOrHTTP(passwordFile)
+			if err != nil {
+				return "", fmt.Errorf("cannot read password from `password_file`=%q: %w", passwordFile, err)
+			}
+			passwordLocal = s
 		}
 		// See https://en.wikipedia.org/wiki/Basic_access_authentication
-		token := ba.Username + ":" + password
+		token := usernameLocal + ":" + passwordLocal
 		token64 := base64.StdEncoding.EncodeToString([]byte(token))
 		return "Basic " + token64, nil
 	}
-	actx.authHeaderDigest = fmt.Sprintf("basic(username=%q, passwordFile=%q)", ba.Username, filePath)
+	actx.authHeaderDigest = fmt.Sprintf("basic(username=%q, usernameFile=%q, password=%q, passwordFile=%q)", username, usernameFile, password, passwordFile)
 	return nil
 }
 
diff --git a/lib/promscrape/scraper.go b/lib/promscrape/scraper.go
index ad6319203..183dcd0e1 100644
--- a/lib/promscrape/scraper.go
+++ b/lib/promscrape/scraper.go
@@ -410,7 +410,7 @@ func (sg *scraperGroup) update(sws []*ScrapeWork) {
 		sg.scrapersStarted.Inc()
 		sg.wg.Add(1)
 		tsmGlobal.Register(&sc.sw)
-		go func(sw *ScrapeWork) {
+		go func() {
 			defer func() {
 				sg.wg.Done()
 				close(sc.stoppedCh)
@@ -419,7 +419,7 @@ func (sg *scraperGroup) update(sws []*ScrapeWork) {
 			tsmGlobal.Unregister(&sc.sw)
 			sg.activeScrapers.Dec()
 			sg.scrapersStopped.Inc()
-		}(sw)
+		}()
 		key := sw.key()
 		sg.m[key] = sc
 		additionsCount++


Test patch:
diff --git a/lib/encoding/int_timing_test.go b/lib/encoding/int_timing_test.go
index 6adc40ca0..0beb2ba49 100644
--- a/lib/encoding/int_timing_test.go
+++ b/lib/encoding/int_timing_test.go
@@ -60,6 +60,46 @@ func BenchmarkUnmarshalInt64(b *testing.B) {
 	})
 }
 
+func BenchmarkMarshalVarUint64s(b *testing.B) {
+	b.Run("up-to-(1<<7)-1", func(b *testing.B) {
+		benchmarkMarshalVarUint64s(b, (1<<7)-1)
+	})
+	b.Run("up-to-(1<<14)-1", func(b *testing.B) {
+		benchmarkMarshalVarUint64s(b, (1<<14)-1)
+	})
+	b.Run("up-to-(1<<28)-1", func(b *testing.B) {
+		benchmarkMarshalVarUint64s(b, (1<<28)-1)
+	})
+	b.Run("up-to-(1<<64)-1", func(b *testing.B) {
+		benchmarkMarshalVarUint64s(b, (1<<64)-1)
+	})
+}
+
+func benchmarkMarshalVarUint64s(b *testing.B, maxValue uint64) {
+	const numsCount = 8000
+	var data []uint64
+	n := maxValue
+	for i := 0; i < numsCount; i++ {
+		if n <= 0 {
+			n = maxValue
+		}
+		data = append(data, n)
+		n--
+	}
+	b.ResetTimer()
+	b.ReportAllocs()
+	b.SetBytes(numsCount)
+	b.RunParallel(func(pb *testing.PB) {
+		var sink uint64
+		var dst []byte
+		for pb.Next() {
+			dst = MarshalVarUint64s(dst[:0], data)
+			sink += uint64(len(dst))
+		}
+		atomic.AddUint64(&Sink, sink)
+	})
+}
+
 func BenchmarkMarshalVarInt64s(b *testing.B) {
 	b.Run("up-to-(1<<6)-1", func(b *testing.B) {
 		benchmarkMarshalVarInt64s(b, (1<<6)-1)
@@ -100,6 +140,52 @@ func benchmarkMarshalVarInt64s(b *testing.B, maxValue int64) {
 	})
 }
 
+func BenchmarkUnmarshalVarUint64s(b *testing.B) {
+	b.Run("up-to-(1<<7)-1", func(b *testing.B) {
+		benchmarkUnmarshalVarUint64s(b, (1<<7)-1)
+	})
+	b.Run("up-to-(1<<14)-1", func(b *testing.B) {
+		benchmarkUnmarshalVarUint64s(b, (1<<14)-1)
+	})
+	b.Run("up-to-(1<<28)-1", func(b *testing.B) {
+		benchmarkUnmarshalVarUint64s(b, (1<<28)-1)
+	})
+	b.Run("up-to-(1<<64)-1", func(b *testing.B) {
+		benchmarkUnmarshalVarUint64s(b, (1<<64)-1)
+	})
+}
+
+func benchmarkUnmarshalVarUint64s(b *testing.B, maxValue uint64) {
+	const numsCount = 8000
+	var data []byte
+	n := maxValue
+	for i := 0; i < numsCount; i++ {
+		if n <= 0 {
+			n = maxValue
+		}
+		data = MarshalVarUint64(data, n)
+		n--
+	}
+	b.ResetTimer()
+	b.ReportAllocs()
+	b.SetBytes(numsCount)
+	b.RunParallel(func(pb *testing.PB) {
+		var sink uint64
+		dst := make([]uint64, numsCount)
+		for pb.Next() {
+			tail, err := UnmarshalVarUint64s(dst, data)
+			if err != nil {
+				panic(fmt.Errorf("unexpected error: %w", err))
+			}
+			if len(tail) > 0 {
+				panic(fmt.Errorf("unexpected non-empty tail with len=%d: %X", len(tail), tail))
+			}
+			sink += uint64(len(dst))
+		}
+		atomic.AddUint64(&Sink, sink)
+	})
+}
+
 func BenchmarkUnmarshalVarInt64s(b *testing.B) {
 	b.Run("up-to-(1<<6)-1", func(b *testing.B) {
 		benchmarkUnmarshalVarInt64s(b, (1<<6)-1)
diff --git a/lib/promauth/config_test.go b/lib/promauth/config_test.go
index 861ab3265..0f51a223c 100644
--- a/lib/promauth/config_test.go
+++ b/lib/promauth/config_test.go
@@ -47,7 +47,14 @@ basic_auth:
   password: pass
 `)
 
-	// basic_auth: password and password_file are set
+	// basic_auth: both username and username_file are set
+	f(`
+basic_auth:
+  username: foo
+  username_file: testdata/test_secretfile.txt
+`)
+
+	// basic_auth: both password and password_file are set
 	f(`
 basic_auth:
   username: user
@@ -345,7 +352,14 @@ oauth2:
     ca_file: non-existing-file
 `)
 
-	// basic auth via non-existing file
+	// basic auth via non-existing username file
+	f(`
+basic_auth:
+  username_file: non-existing-file
+  password: foobar
+`)
+
+	// basic auth via non-existing password file
 	f(`
 basic_auth:
   username: user
@@ -464,13 +478,26 @@ basic_auth:
   password: password
 `, "Basic dXNlcjpwYXNzd29yZA==")
 
-	// basic auth via file
+	// basic auth via username file
+	f(`
+basic_auth:
+  username_file: testdata/test_secretfile.txt
+`, "Basic c2VjcmV0LWNvbnRlbnQ6")
+
+	// basic auth via password file
 	f(`
 basic_auth:
   username: user
   password_file: testdata/test_secretfile.txt
 `, "Basic dXNlcjpzZWNyZXQtY29udGVudA==")
 
+	// basic auth via username file and password file
+	f(`
+basic_auth:
+  username_file: testdata/test_secretfile.txt
+  password_file: testdata/test_secretfile.txt
+`, "Basic c2VjcmV0LWNvbnRlbnQ6c2VjcmV0LWNvbnRlbnQ=")
+
 	// inline authorization config
 	f(`
 authorization: