Repository: VictoriaMetrics/VictoriaMetrics
Pull request #4929 (2023-08-31 11:11:53)

deployment/docker: add VictoriaLogs
- added VictoriaLogs to main docker-compose file
- removed outdated comments
- added `path` to fluentbit stream fields
  Previously, stream only contained `stream` field which stands for `stdout` or `stderr`. This leads to performance in case this configuration will be used.

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/VictoriaMetrics/VictoriaMetrics/pull/4929?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics) Report
Patch coverage has no change and project coverage change: **`+0.01%`** :tada:
> Comparison is base [(`1c42154`)](https://app.codecov.io/gh/VictoriaMetrics/VictoriaMetrics/commit/1c42154785caa705e461d5cf9684af5963c83924?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics) 60.55% compared to head [(`0ef502e`)](https://app.codecov.io/gh/VictoriaMetrics/VictoriaMetrics/pull/4929?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics) 60.56%.
> Report is 2 commits behind head on master.

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


```diff
@@            Coverage Diff             @@
##           master    #4929      +/-   ##
==========================================
+ Coverage   60.55%   60.56%   +0.01%     
==========================================
  Files         384      384              
  Lines       72318    72318              
==========================================
+ Hits        43789    43803      +14     
+ Misses      26131    26121      -10     
+ Partials     2398     2394       -4     
```


[see 10 files with indirect coverage changes](https://app.codecov.io/gh/VictoriaMetrics/VictoriaMetrics/pull/4929/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics)


</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/VictoriaMetrics/VictoriaMetrics/pull/4929?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).


Patch:
diff --git a/Makefile b/Makefile
index b8160831c..9f256ae6a 100644
--- a/Makefile
+++ b/Makefile
@@ -463,7 +463,7 @@ golangci-lint: install-golangci-lint
 	golangci-lint run
 
 install-golangci-lint:
-	which golangci-lint || curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(shell go env GOPATH)/bin v1.51.2
+	which golangci-lint || curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(shell go env GOPATH)/bin v1.54.2
 
 govulncheck: install-govulncheck
 	govulncheck ./...
diff --git a/app/vlstorage/main.go b/app/vlstorage/main.go
index efd8dfc21..0a6c9b55a 100644
--- a/app/vlstorage/main.go
+++ b/app/vlstorage/main.go
@@ -39,13 +39,13 @@ func Init() {
 		logger.Panicf("BUG: Init() has been already called")
 	}
 
-	if retentionPeriod.Msecs < 24*3600*1000 {
+	if retentionPeriod.Duration() < 24*time.Hour {
 		logger.Fatalf("-retentionPeriod cannot be smaller than a day; got %s", retentionPeriod)
 	}
 	cfg := &logstorage.StorageConfig{
-		Retention:       time.Millisecond * time.Duration(retentionPeriod.Msecs),
+		Retention:       retentionPeriod.Duration(),
 		FlushInterval:   *inmemoryDataFlushInterval,
-		FutureRetention: time.Millisecond * time.Duration(futureRetention.Msecs),
+		FutureRetention: futureRetention.Duration(),
 		LogNewStreams:   *logNewStreams,
 		LogIngestedRows: *logIngestedRows,
 	}
diff --git a/app/vmagent/remotewrite/remotewrite.go b/app/vmagent/remotewrite/remotewrite.go
index 62dd1a908..39a7c7c02 100644
--- a/app/vmagent/remotewrite/remotewrite.go
+++ b/app/vmagent/remotewrite/remotewrite.go
@@ -258,7 +258,7 @@ func newRemoteWriteCtxs(at *auth.Token, urls []string) []*remoteWriteCtx {
 		if *showRemoteWriteURL {
 			sanitizedURL = fmt.Sprintf("%d:%s", i+1, remoteWriteURL)
 		}
-		rwctxs[i] = newRemoteWriteCtx(i, at, remoteWriteURL, maxInmemoryBlocks, sanitizedURL)
+		rwctxs[i] = newRemoteWriteCtx(i, remoteWriteURL, maxInmemoryBlocks, sanitizedURL)
 	}
 
 	if !*keepDanglingQueues {
@@ -559,7 +559,7 @@ type remoteWriteCtx struct {
 	rowsDroppedByRelabel   *metrics.Counter
 }
 
-func newRemoteWriteCtx(argIdx int, at *auth.Token, remoteWriteURL *url.URL, maxInmemoryBlocks int, sanitizedURL string) *remoteWriteCtx {
+func newRemoteWriteCtx(argIdx int, remoteWriteURL *url.URL, maxInmemoryBlocks int, sanitizedURL string) *remoteWriteCtx {
 	// strip query params, otherwise changing params resets pq
 	pqURL := *remoteWriteURL
 	pqURL.RawQuery = ""
diff --git a/app/vmagent/remotewrite/statconn.go b/app/vmagent/remotewrite/statconn.go
index 787aa19f5..cb4d756ec 100644
--- a/app/vmagent/remotewrite/statconn.go
+++ b/app/vmagent/remotewrite/statconn.go
@@ -27,7 +27,7 @@ var (
 	stdDialerOnce sync.Once
 )
 
-func statDial(ctx context.Context, networkUnused, addr string) (conn net.Conn, err error) {
+func statDial(ctx context.Context, _, addr string) (conn net.Conn, err error) {
 	network := netutil.GetTCPNetwork()
 	d := getStdDialer()
 	conn, err = d.DialContext(ctx, network, addr)
diff --git a/app/vmauth/README.md b/app/vmauth/README.md
index dea54e0e8..ea14aeff6 100644
--- a/app/vmauth/README.md
+++ b/app/vmauth/README.md
@@ -117,11 +117,16 @@ users:
 
   # Requests with the 'Authorization: Bearer YYY' header are proxied to http://localhost:8428 ,
   # The `X-Scope-OrgID: foobar` http header is added to every proxied request.
+  # The `X-Server-Hostname` http header is removed from the proxied response.
   # For example, http://vmauth:8427/api/v1/query is proxied to http://localhost:8428/api/v1/query
 - bearer_token: "YYY"
   url_prefix: "http://localhost:8428"
+  # extra headers to add to the request or remove from the request (if header value is empty)
   headers:
-  - "X-Scope-OrgID: foobar"
+    - "X-Scope-OrgID: foobar"
+  # extra headers to add to the response or remove from the response (if header value is empty)
+  response_headers:
+    - "X-Server-Hostname:" # empty value means the header will be removed from the response
 
   # All the requests to http://vmauth:8427 with the given Basic Auth (username:password)
   # are proxied to http://localhost:8428 .
@@ -175,6 +180,7 @@ users:
   #
   # - Requests to http://vmauth:8427/api/v1/write are proxied to http://vminsert:8480/insert/42/prometheus/api/v1/write .
   #   The "X-Scope-OrgID: abc" http header is added to these requests.
+  #   The "X-Server-Hostname" http header is removed from the proxied response.
   #
   # Request which do not match `src_paths` from the `url_map` are proxied to the urls from `default_url`
   # in a round-robin manner. The original request path is passed in `request_path` query arg.
@@ -194,6 +200,8 @@ users:
     url_prefix: "http://vminsert:8480/insert/42/prometheus"
     headers:
     - "X-Scope-OrgID: abc"
+    response_headers:
+    - "X-Server-Hostname:" # empty value means the header will be removed from the response
     ip_filters:
       deny_list: [127.0.0.1]
   default_url:
diff --git a/app/vmauth/auth_config.go b/app/vmauth/auth_config.go
index 6fab91dc2..af1b0573f 100644
--- a/app/vmauth/auth_config.go
+++ b/app/vmauth/auth_config.go
@@ -37,15 +37,15 @@ type AuthConfig struct {
 
 // UserInfo is user information read from authConfigPath
 type UserInfo struct {
-	Name                  string     `yaml:"name,omitempty"`
-	BearerToken           string     `yaml:"bearer_token,omitempty"`
-	Username              string     `yaml:"username,omitempty"`
-	Password              string     `yaml:"password,omitempty"`
-	URLPrefix             *URLPrefix `yaml:"url_prefix,omitempty"`
-	URLMaps               []URLMap   `yaml:"url_map,omitempty"`
-	Headers               []Header   `yaml:"headers,omitempty"`
-	MaxConcurrentRequests int        `yaml:"max_concurrent_requests,omitempty"`
-	DefaultURL            *URLPrefix `yaml:"default_url,omitempty"`
+	Name                  string      `yaml:"name,omitempty"`
+	BearerToken           string      `yaml:"bearer_token,omitempty"`
+	Username              string      `yaml:"username,omitempty"`
+	Password              string      `yaml:"password,omitempty"`
+	URLPrefix             *URLPrefix  `yaml:"url_prefix,omitempty"`
+	URLMaps               []URLMap    `yaml:"url_map,omitempty"`
+	HeadersConf           HeadersConf `yaml:",inline"`
+	MaxConcurrentRequests int         `yaml:"max_concurrent_requests,omitempty"`
+	DefaultURL            *URLPrefix  `yaml:"default_url,omitempty"`
 
 	concurrencyLimitCh      chan struct{}
 	concurrencyLimitReached *metrics.Counter
@@ -54,6 +54,12 @@ type UserInfo struct {
 	requestsDuration *metrics.Summary
 }
 
+// HeadersConf represents config for request and response headers.
+type HeadersConf struct {
+	RequestHeaders  []Header `yaml:"headers,omitempty"`
+	ResponseHeaders []Header `yaml:"response_headers,omitempty"`
+}
+
 func (ui *UserInfo) beginConcurrencyLimit() error {
 	select {
 	case ui.concurrencyLimitCh <- struct{}{}:
@@ -105,9 +111,9 @@ func (h *Header) MarshalYAML() (interface{}, error) {
 
 // URLMap is a mapping from source paths to target urls.
 type URLMap struct {
-	SrcPaths  []*SrcPath `yaml:"src_paths,omitempty"`
-	URLPrefix *URLPrefix `yaml:"url_prefix,omitempty"`
-	Headers   []Header   `yaml:"headers,omitempty"`
+	SrcPaths    []*SrcPath  `yaml:"src_paths,omitempty"`
+	URLPrefix   *URLPrefix  `yaml:"url_prefix,omitempty"`
+	HeadersConf HeadersConf `yaml:",inline"`
 }
 
 // SrcPath represents an src path
diff --git a/app/vmauth/example_config.yml b/app/vmauth/example_config.yml
index e7721be0e..f9e149626 100644
--- a/app/vmauth/example_config.yml
+++ b/app/vmauth/example_config.yml
@@ -12,11 +12,16 @@ users:
 
   # Requests with the 'Authorization: Bearer YYY' header are proxied to http://localhost:8428 ,
   # The `X-Scope-OrgID: foobar` http header is added to every proxied request.
+  # The `X-Server-Hostname:` http header is removed from the proxied response.
   # For example, http://vmauth:8427/api/v1/query is proxied to http://localhost:8428/api/v1/query
 - bearer_token: "YYY"
   url_prefix: "http://localhost:8428"
+  # extra headers to add to the request or remove from the request (if header value is empty)
   headers:
   - "X-Scope-OrgID: foobar"
+  # extra headers to add to the response or remove from the response (if header value is empty)
+  response_headers:
+  - "X-Server-Hostname:" # empty value means the header will be removed from the response
 
   # All the requests to http://vmauth:8427 with the given Basic Auth (username:password)
   # are proxied to http://localhost:8428 .
diff --git a/app/vmauth/main.go b/app/vmauth/main.go
index 925bfb7c6..1f7d685a0 100644
--- a/app/vmauth/main.go
+++ b/app/vmauth/main.go
@@ -151,7 +151,7 @@ func processUserRequest(w http.ResponseWriter, r *http.Request, ui *UserInfo) {
 
 func processRequest(w http.ResponseWriter, r *http.Request, ui *UserInfo) {
 	u := normalizeURL(r.URL)
-	up, headers := ui.getURLPrefixAndHeaders(u)
+	up, headersConf := ui.getURLPrefixAndHeaders(u)
 	isDefault := false
 	if up == nil {
 		missingRouteRequests.Inc()
@@ -159,7 +159,7 @@ func processRequest(w http.ResponseWriter, r *http.Request, ui *UserInfo) {
 			httpserver.Errorf(w, r, "missing route for %q", u.String())
 			return
 		}
-		up, headers = ui.DefaultURL, ui.Headers
+		up, headersConf = ui.DefaultURL, ui.HeadersConf
 		isDefault = true
 	}
 	r.Body = &readTrackingBody{
@@ -178,7 +178,7 @@ func processRequest(w http.ResponseWriter, r *http.Request, ui *UserInfo) {
 		} else { // Update path for regular routes.
 			targetURL = mergeURLs(targetURL, u)
 		}
-		ok := tryProcessingRequest(w, r, targetURL, headers)
+		ok := tryProcessingRequest(w, r, targetURL, headersConf)
 		bu.put()
 		if ok {
 			return
@@ -192,13 +192,11 @@ func processRequest(w http.ResponseWriter, r *http.Request, ui *UserInfo) {
 	httpserver.Errorf(w, r, "%s", err)
 }
 
-func tryProcessingRequest(w http.ResponseWriter, r *http.Request, targetURL *url.URL, headers []Header) bool {
+func tryProcessingRequest(w http.ResponseWriter, r *http.Request, targetURL *url.URL, headersConf HeadersConf) bool {
 	// This code has been copied from net/http/httputil/reverseproxy.go
 	req := sanitizeRequestHeaders(r)
 	req.URL = targetURL
-	for _, h := range headers {
-		req.Header.Set(h.Name, h.Value)
-	}
+	updateHeadersByConfig(req.Header, headersConf.RequestHeaders)
 	transportOnce.Do(transportInit)
 	res, err := transport.RoundTrip(req)
 	if err != nil {
@@ -222,6 +220,7 @@ func tryProcessingRequest(w http.ResponseWriter, r *http.Request, targetURL *url
 	}
 	removeHopHeaders(res.Header)
 	copyHeader(w.Header(), res.Header)
+	updateHeadersByConfig(w.Header(), headersConf.ResponseHeaders)
 	w.WriteHeader(res.StatusCode)
 
 	copyBuf := copyBufPool.Get()
@@ -247,6 +246,16 @@ func copyHeader(dst, src http.Header) {
 	}
 }
 
+func updateHeadersByConfig(headers http.Header, config []Header) {
+	for _, h := range config {
+		if h.Value == "" {
+			headers.Del(h.Name)
+		} else {
+			headers.Set(h.Name, h.Value)
+		}
+	}
+}
+
 func sanitizeRequestHeaders(r *http.Request) *http.Request {
 	// This code has been copied from net/http/httputil/reverseproxy.go
 	req := r.Clone(r.Context())
diff --git a/app/vmauth/target_url.go b/app/vmauth/target_url.go
index 7e4afd24a..365484d95 100644
--- a/app/vmauth/target_url.go
+++ b/app/vmauth/target_url.go
@@ -29,18 +29,18 @@ func mergeURLs(uiURL, requestURI *url.URL) *url.URL {
 	return &targetURL
 }
 
-func (ui *UserInfo) getURLPrefixAndHeaders(u *url.URL) (*URLPrefix, []Header) {
+func (ui *UserInfo) getURLPrefixAndHeaders(u *url.URL) (*URLPrefix, HeadersConf) {
 	for _, e := range ui.URLMaps {
 		for _, sp := range e.SrcPaths {
 			if sp.match(u.Path) {
-				return e.URLPrefix, e.Headers
+				return e.URLPrefix, e.HeadersConf
 			}
 		}
 	}
 	if ui.URLPrefix != nil {
-		return ui.URLPrefix, ui.Headers
+		return ui.URLPrefix, ui.HeadersConf
 	}
-	return nil, nil
+	return nil, HeadersConf{}
 }
 
 func normalizeURL(uOrig *url.URL) *url.URL {
diff --git a/app/vmctl/README.md b/app/vmctl/README.md
index 62f142394..beba31a67 100644
--- a/app/vmctl/README.md
+++ b/app/vmctl/README.md
@@ -583,7 +583,7 @@ then import it into VM using `vmctl` in `prometheus` mode.
     1. Run the `minio/mc` Docker container.
     1. `mc config host add minio http://minio:9000 accessKey secretKey`, substituting appropriate values for the last 3 items.
     1. `mc cp -r minio/prometheus thanos-data`
- 
+
 1. Import using `vmctl`.
     1. Follow the [instructions](#how-to-build) to compile `vmctl` on your machine.
     1. Use [prometheus](#migrating-data-from-prometheus) mode to import data:
diff --git a/app/vmctl/opentsdb.go b/app/vmctl/opentsdb.go
index a20ee007f..07fc3a6d5 100644
--- a/app/vmctl/opentsdb.go
+++ b/app/vmctl/opentsdb.go
@@ -170,8 +170,5 @@ func (op *otsdbProcessor) do(s queryObj) error {
 		Timestamps: data.Timestamps,
 		Values:     data.Values,
 	}
-	if err := op.im.Input(&ts); err != nil {
-		return err
-	}
-	return nil
+	return op.im.Input(&ts)
 }
diff --git a/app/vmselect/graphite/aggr_state.go b/app/vmselect/graphite/aggr_state.go
index 38fbe183f..768513c22 100644
--- a/app/vmselect/graphite/aggr_state.go
+++ b/app/vmselect/graphite/aggr_state.go
@@ -114,7 +114,7 @@ func (as *aggrStateAvgZero) Update(values []float64) {
 	as.seriesTotal++
 }
 
-func (as *aggrStateAvgZero) Finalize(xFilesFactor float64) []float64 {
+func (as *aggrStateAvgZero) Finalize(_ float64) []float64 {
 	sums := as.sums
 	values := make([]float64, as.pointsLen)
 	count := float64(as.seriesTotal)
diff --git a/app/vmselect/graphite/functions_api.go b/app/vmselect/graphite/functions_api.go
index 2a39933d6..97932bcef 100644
--- a/app/vmselect/graphite/functions_api.go
+++ b/app/vmselect/graphite/functions_api.go
@@ -6,7 +6,6 @@ import (
 	"encoding/json"
 	"fmt"
 	"net/http"
-	"time"
 
 	"github.com/VictoriaMetrics/VictoriaMetrics/lib/httputils"
 )
@@ -14,7 +13,7 @@ import (
 // FunctionsHandler implements /functions handler.
 //
 // See https://graphite.readthedocs.io/en/latest/functions.html#function-api
-func FunctionsHandler(startTime time.Time, w http.ResponseWriter, r *http.Request) error {
+func FunctionsHandler(w http.ResponseWriter, r *http.Request) error {
 	grouped := httputils.GetBool(r, "grouped")
 	group := r.FormValue("group")
 	result := make(map[string]interface{})
@@ -40,7 +39,7 @@ func FunctionsHandler(startTime time.Time, w http.ResponseWriter, r *http.Reques
 // FunctionDetailsHandler implements /functions/<func_name> handler.
 //
 // See https://graphite.readthedocs.io/en/latest/functions.html#function-api
-func FunctionDetailsHandler(startTime time.Time, funcName string, w http.ResponseWriter, r *http.Request) error {
+func FunctionDetailsHandler(funcName string, w http.ResponseWriter, r *http.Request) error {
 	result := funcs[funcName]
 	if result == nil {
 		return fmt.Errorf("cannot find function %q", funcName)
diff --git a/app/vmselect/graphite/metrics_api.go b/app/vmselect/graphite/metrics_api.go
index 6f50cd7b4..58fec5659 100644
--- a/app/vmselect/graphite/metrics_api.go
+++ b/app/vmselect/graphite/metrics_api.go
@@ -85,7 +85,7 @@ func MetricsFindHandler(startTime time.Time, w http.ResponseWriter, r *http.Requ
 	if leavesOnly {
 		paths = filterLeaves(paths, delimiter)
 	}
-	paths = deduplicatePaths(paths, delimiter)
+	paths = deduplicatePaths(paths)
 	sortPaths(paths, delimiter)
 	contentType := getContentType(jsonp)
 	w.Header().Set("Content-Type", contentType)
@@ -99,7 +99,7 @@ func MetricsFindHandler(startTime time.Time, w http.ResponseWriter, r *http.Requ
 	return nil
 }
 
-func deduplicatePaths(paths []string, delimiter string) []string {
+func deduplicatePaths(paths []string) []string {
 	if len(paths) == 0 {
 		return nil
 	}
diff --git a/app/vmselect/graphite/transform.go b/app/vmselect/graphite/transform.go
index 480abcc26..5cfeca59a 100644
--- a/app/vmselect/graphite/transform.go
+++ b/app/vmselect/graphite/transform.go
@@ -189,7 +189,7 @@ func init() {
 	}
 }
 
-func transformTODO(ec *evalConfig, fe *graphiteql.FuncExpr) (nextSeriesFunc, error) {
+func transformTODO(_ *evalConfig, _ *graphiteql.FuncExpr) (nextSeriesFunc, error) {
 	return nil, fmt.Errorf("TODO: implement this function")
 }
 
@@ -1062,7 +1062,7 @@ func transformCumulative(ec *evalConfig, fe *graphiteql.FuncExpr) (nextSeriesFun
 	if err != nil {
 		return nil, err
 	}
-	return consolidateBy(ec, fe, nextSeries, "sum")
+	return consolidateBy(fe, nextSeries, "sum")
 }
 
 // See https://graphite.readthedocs.io/en/stable/functions.html#graphite.render.functions.consolidateBy
@@ -1079,10 +1079,10 @@ func transformConsolidateBy(ec *evalConfig, fe *graphiteql.FuncExpr) (nextSeries
 	if err != nil {
 		return nil, err
 	}
-	return consolidateBy(ec, fe, nextSeries, funcName)
+	return consolidateBy(fe, nextSeries, funcName)
 }
 
-func consolidateBy(ec *evalConfig, expr graphiteql.Expr, nextSeries nextSeriesFunc, funcName string) (nextSeriesFunc, error) {
+func consolidateBy(expr graphiteql.Expr, nextSeries nextSeriesFunc, funcName string) (nextSeriesFunc, error) {
 	consolidateFunc, err := getAggrFunc(funcName)
 	if err != nil {
 		return nil, err
@@ -1843,10 +1843,10 @@ func transformHighest(ec *evalConfig, fe *graphiteql.FuncExpr) (nextSeriesFunc,
 	if err != nil {
 		return nil, err
 	}
-	return highestGeneric(ec, fe, nextSeries, n, funcName)
+	return highestGeneric(fe, nextSeries, n, funcName)
 }
 
-func highestGeneric(ec *evalConfig, expr graphiteql.Expr, nextSeries nextSeriesFunc, n float64, funcName string) (nextSeriesFunc, error) {
+func highestGeneric(expr graphiteql.Expr, nextSeries nextSeriesFunc, n float64, funcName string) (nextSeriesFunc, error) {
 	aggrFunc, err := getAggrFunc(funcName)
 	if err != nil {
 		_, _ = drainAllSeries(nextSeries)
@@ -1928,7 +1928,7 @@ func transformHighestAverage(ec *evalConfig, fe *graphiteql.FuncExpr) (nextSerie
 	if err != nil {
 		return nil, err
 	}
-	return highestGeneric(ec, fe, nextSeries, n, "average")
+	return highestGeneric(fe, nextSeries, n, "average")
 }
 
 // See https://graphite.readthedocs.io/en/stable/functions.html#graphite.render.functions.highestCurrent
@@ -1945,7 +1945,7 @@ func transformHighestCurrent(ec *evalConfig, fe *graphiteql.FuncExpr) (nextSerie
 	if err != nil {
 		return nil, err
 	}
-	return highestGeneric(ec, fe, nextSeries, n, "current")
+	return highestGeneric(fe, nextSeries, n, "current")
 }
 
 // See https://graphite.readthedocs.io/en/stable/functions.html#graphite.render.functions.highestMax
@@ -1962,7 +1962,7 @@ func transformHighestMax(ec *evalConfig, fe *graphiteql.FuncExpr) (nextSeriesFun
 	if err != nil {
 		return nil, err
 	}
-	return highestGeneric(ec, fe, nextSeries, n, "max")
+	return highestGeneric(fe, nextSeries, n, "max")
 }
 
 // See https://graphite.readthedocs.io/en/stable/functions.html#graphite.render.functions.hitcount
@@ -2379,10 +2379,10 @@ func transformLowest(ec *evalConfig, fe *graphiteql.FuncExpr) (nextSeriesFunc, e
 	if err != nil {
 		return nil, err
 	}
-	return lowestGeneric(ec, fe, nextSeries, n, funcName)
+	return lowestGeneric(fe, nextSeries, n, funcName)
 }
 
-func lowestGeneric(ec *evalConfig, expr graphiteql.Expr, nextSeries nextSeriesFunc, n float64, funcName string) (nextSeriesFunc, error) {
+func lowestGeneric(expr graphiteql.Expr, nextSeries nextSeriesFunc, n float64, funcName string) (nextSeriesFunc, error) {
 	aggrFunc, err := getAggrFunc(funcName)
 	if err != nil {
 		_, _ = drainAllSeries(nextSeries)
@@ -2459,7 +2459,7 @@ func transformLowestAverage(ec *evalConfig, fe *graphiteql.FuncExpr) (nextSeries
 	if err != nil {
 		return nil, err
 	}
-	return lowestGeneric(ec, fe, nextSeries, n, "average")
+	return lowestGeneric(fe, nextSeries, n, "average")
 }
 
 // See https://graphite.readthedocs.io/en/stable/functions.html#graphite.render.functions.lowestCurrent
@@ -2476,7 +2476,7 @@ func transformLowestCurrent(ec *evalConfig, fe *graphiteql.FuncExpr) (nextSeries
 	if err != nil {
 		return nil, err
 	}
-	return lowestGeneric(ec, fe, nextSeries, n, "current")
+	return lowestGeneric(fe, nextSeries, n, "current")
 }
 
 // See https://graphite.readthedocs.io/en/stable/functions.html#graphite.render.functions.maxSeries
@@ -2607,7 +2607,7 @@ func transformMostDeviant(ec *evalConfig, fe *graphiteql.FuncExpr) (nextSeriesFu
 	if err != nil {
 		return nil, err
 	}
-	return highestGeneric(ec, fe, nextSeries, n, "stddev")
+	return highestGeneric(fe, nextSeries, n, "stddev")
 }
 
 // See https://graphite.readthedocs.io/en/stable/functions.html#graphite.render.functions.movingAverage
@@ -3862,7 +3862,11 @@ func nextSeriesConcurrentWrapper(nextSeries nextSeriesFunc, f func(s *series) (*
 		}
 		if r.err != nil {
 			// Drain the rest of series before returning the error.
-			for range resultCh {
+			for {
+				_, ok := <-resultCh
+				if !ok {
+					break
+				}
 			}
 			<-errCh
 			return nil, r.err
@@ -4733,7 +4737,7 @@ func transformSortByTotal(ec *evalConfig, fe *graphiteql.FuncExpr) (nextSeriesFu
 	if err != nil {
 		return nil, err
 	}
-	return sortByGeneric(ec, fe, nextSeries, "sum", true)
+	return sortByGeneric(fe, nextSeries, "sum", true)
 }
 
 // https://graphite.readthedocs.io/en/stable/functions.html#graphite.render.functions.sortBy
@@ -4754,10 +4758,10 @@ func transformSortBy(ec *evalConfig, fe *graphiteql.FuncExpr) (nextSeriesFunc, e
 	if err != nil {
 		return nil, err
 	}
-	return sortByGeneric(ec, fe, nextSeries, funcName, reverse)
+	return sortByGeneric(fe, nextSeries, funcName, reverse)
 }
 
-func sortByGeneric(ec *evalConfig, fe *graphiteql.FuncExpr, nextSeries nextSeriesFunc, funcName string, reverse bool) (nextSeriesFunc, error) {
+func sortByGeneric(fe *graphiteql.FuncExpr, nextSeries nextSeriesFunc, funcName string, reverse bool) (nextSeriesFunc, error) {
 	aggrFunc, err := getAggrFunc(funcName)
 	if err != nil {
 		_, _ = drainAllSeries(nextSeries)
@@ -4868,7 +4872,7 @@ func transformSortByMinima(ec *evalConfig, fe *graphiteql.FuncExpr) (nextSeriesF
 		}
 		return s, nil
 	})
-	return sortByGeneric(ec, fe, f, "min", false)
+	return sortByGeneric(fe, f, "min", false)
 }
 
 // https://graphite.readthedocs.io/en/stable/functions.html#graphite.render.functions.sortByMaxima
@@ -4881,7 +4885,7 @@ func transformSortByMaxima(ec *evalConfig, fe *graphiteql.FuncExpr) (nextSeriesF
 	if err != nil {
 		return nil, err
 	}
-	return sortByGeneric(ec, fe, nextSeries, "max", true)
+	return sortByGeneric(fe, nextSeries, "max", true)
 }
 
 // https://graphite.readthedocs.io/en/stable/functions.html#graphite.render.functions.smartSummarize
@@ -5286,7 +5290,7 @@ func holtWinterConfidenceBands(ec *evalConfig, fe *graphiteql.FuncExpr, args []*
 	f := nextSeriesConcurrentWrapper(nextSeries, func(s *series) (*series, error) {
 		s.consolidate(&ecCopy, step)
 		timeStamps := s.Timestamps[trimWindowPoints:]
-		analysis := holtWintersAnalysis(&ecCopy, s, seasonalityMs)
+		analysis := holtWintersAnalysis(s, seasonalityMs)
 		forecastValues := analysis.predictions.Values[trimWindowPoints:]
 		deviationValues := analysis.deviations.Values[trimWindowPoints:]
 		valuesLen := len(forecastValues)
@@ -5450,7 +5454,7 @@ func transformHoltWintersForecast(ec *evalConfig, fe *graphiteql.FuncExpr) (next
 	trimWindowPoints := ecCopy.pointsLen(step) - ec.pointsLen(step)
 	f := nextSeriesConcurrentWrapper(nextSeries, func(s *series) (*series, error) {
 		s.consolidate(&ecCopy, step)
-		analysis := holtWintersAnalysis(&ecCopy, s, seasonalityMs)
+		analysis := holtWintersAnalysis(s, seasonalityMs)
 		predictions := analysis.predictions
 
 		s.Tags["holtWintersForecast"] = "1"
@@ -5468,7 +5472,7 @@ func transformHoltWintersForecast(ec *evalConfig, fe *graphiteql.FuncExpr) (next
 
 }
 
-func holtWintersAnalysis(ec *evalConfig, s *series, seasonality int64) holtWintersAnalysisResult {
+func holtWintersAnalysis(s *series, seasonality int64) holtWintersAnalysisResult {
 	alpha := 0.1
 	gamma := alpha
 	beta := 0.0035
diff --git a/app/vmselect/main.go b/app/vmselect/main.go
index 7ee0a6e90..8f15b83f3 100644
--- a/app/vmselect/main.go
+++ b/app/vmselect/main.go
@@ -236,7 +236,7 @@ func RequestHandler(w http.ResponseWriter, r *http.Request) bool {
 		funcName = strings.TrimPrefix(funcName, "/")
 		if funcName == "" {
 			graphiteFunctionsRequests.Inc()
-			if err := graphite.FunctionsHandler(startTime, w, r); err != nil {
+			if err := graphite.FunctionsHandler(w, r); err != nil {
 				graphiteFunctionsErrors.Inc()
 				httpserver.Errorf(w, r, "%s", err)
 				return true
@@ -244,7 +244,7 @@ func RequestHandler(w http.ResponseWriter, r *http.Request) bool {
 			return true
 		}
 		graphiteFunctionDetailsRequests.Inc()
-		if err := graphite.FunctionDetailsHandler(startTime, funcName, w, r); err != nil {
+		if err := graphite.FunctionDetailsHandler(funcName, w, r); err != nil {
 			graphiteFunctionDetailsErrors.Inc()
 			httpserver.Errorf(w, r, "%s", err)
 			return true
diff --git a/app/vmselect/prometheus/prometheus.go b/app/vmselect/prometheus/prometheus.go
index 131249570..f4ce705e7 100644
--- a/app/vmselect/prometheus/prometheus.go
+++ b/app/vmselect/prometheus/prometheus.go
@@ -667,10 +667,7 @@ func SeriesHandler(qt *querytracer.Tracer, startTime time.Time, w http.ResponseW
 		qt.Donef("start=%d, end=%d", cp.start, cp.end)
 	}
 	WriteSeriesResponse(bw, metricNames, qt, qtDone)
-	if err := bw.Flush(); err != nil {
-		return err
-	}
-	return nil
+	return bw.Flush()
 }
 
 var seriesDuration = metrics.NewSummary(`vm_request_duration_seconds{path="/api/v1/series"}`)
diff --git a/app/vmselect/promql/active_queries.go b/app/vmselect/promql/active_queries.go
index d66058e53..cf60de2c2 100644
--- a/app/vmselect/promql/active_queries.go
+++ b/app/vmselect/promql/active_queries.go
@@ -12,7 +12,7 @@ import (
 // ActiveQueriesHandler returns response to /api/v1/status/active_queries
 //
 // It writes a JSON with active queries to w.
-func ActiveQueriesHandler(w http.ResponseWriter, r *http.Request) {
+func ActiveQueriesHandler(w http.ResponseWriter, _ *http.Request) {
 	aqes := activeQueriesV.GetAll()
 
 	w.Header().Set("Content-Type", "application/json")
diff --git a/app/vmselect/promql/exec.go b/app/vmselect/promql/exec.go
index 7d70754d7..acbaa59d5 100644
--- a/app/vmselect/promql/exec.go
+++ b/app/vmselect/promql/exec.go
@@ -73,7 +73,7 @@ func Exec(qt *querytracer.Tracer, ec *EvalConfig, q string, isFirstPointOnly boo
 		}
 		qt.Printf("leave only the first point in every series")
 	}
-	maySort := maySortResults(e, rv)
+	maySort := maySortResults(e)
 	result, err := timeseriesToResult(rv, maySort)
 	if err != nil {
 		return nil, err
@@ -95,7 +95,7 @@ func Exec(qt *querytracer.Tracer, ec *EvalConfig, q string, isFirstPointOnly boo
 	return result, nil
 }
 
-func maySortResults(e metricsql.Expr, tss []*timeseries) bool {
+func maySortResults(e metricsql.Expr) bool {
 	switch v := e.(type) {
 	case *metricsql.FuncExpr:
 		switch strings.ToLower(v.Name) {
diff --git a/app/vmselect/promql/rollup.go b/app/vmselect/promql/rollup.go
index d7c742bec..2cf827344 100644
--- a/app/vmselect/promql/rollup.go
+++ b/app/vmselect/promql/rollup.go
@@ -2222,7 +2222,7 @@ func rollupIntegrate(rfa *rollupFuncArg) float64 {
 	return sum
 }
 
-func rollupFake(rfa *rollupFuncArg) float64 {
+func rollupFake(_ *rollupFuncArg) float64 {
 	logger.Panicf("BUG: rollupFake shouldn't be called")
 	return 0
 }
diff --git a/app/vmselect/promql/transform.go b/app/vmselect/promql/transform.go
index 28acb36bf..94da2a572 100644
--- a/app/vmselect/promql/transform.go
+++ b/app/vmselect/promql/transform.go
@@ -1090,18 +1090,18 @@ func transformHour(t time.Time) int {
 	return t.Hour()
 }
 
-func runningSum(a, b float64, idx int) float64 {
+func runningSum(a, b float64, _ int) float64 {
 	return a + b
 }
 
-func runningMax(a, b float64, idx int) float64 {
+func runningMax(a, b float64, _ int) float64 {
 	if a > b {
 		return a
 	}
 	return b
 }
 
-func runningMin(a, b float64, idx int) float64 {
+func runningMin(a, b float64, _ int) float64 {
 	if a < b {
 		return a
 	}
diff --git a/app/vmstorage/main.go b/app/vmstorage/main.go
index d7381a9f9..eef298616 100644
--- a/app/vmstorage/main.go
+++ b/app/vmstorage/main.go
@@ -102,13 +102,13 @@ func Init(resetCacheIfNeeded func(mrs []storage.MetricRow)) {
 	mergeset.SetIndexBlocksCacheSize(cacheSizeIndexDBIndexBlocks.IntN())
 	mergeset.SetDataBlocksCacheSize(cacheSizeIndexDBDataBlocks.IntN())
 
-	if retentionPeriod.Msecs < 24*3600*1000 {
+	if retentionPeriod.Duration() < 24*time.Hour {
 		logger.Fatalf("-retentionPeriod cannot be smaller than a day; got %s", retentionPeriod)
 	}
 	logger.Infof("opening storage at %q with -retentionPeriod=%s", *DataPath, retentionPeriod)
 	startTime := time.Now()
 	WG = syncwg.WaitGroup{}
-	strg := storage.MustOpenStorage(*DataPath, retentionPeriod.Msecs, *maxHourlySeries, *maxDailySeries)
+	strg := storage.MustOpenStorage(*DataPath, retentionPeriod.Duration(), *maxHourlySeries, *maxDailySeries)
 	Storage = strg
 	initStaleSnapshotsRemover(strg)
 
@@ -380,10 +380,10 @@ func RequestHandler(w http.ResponseWriter, r *http.Request) bool {
 
 func initStaleSnapshotsRemover(strg *storage.Storage) {
 	staleSnapshotsRemoverCh = make(chan struct{})
-	if snapshotsMaxAge.Msecs <= 0 {
+	if snapshotsMaxAge.Duration() <= 0 {
 		return
 	}
-	snapshotsMaxAgeDur := time.Duration(snapshotsMaxAge.Msecs) * time.Millisecond
+	snapshotsMaxAgeDur := snapshotsMaxAge.Duration()
 	staleSnapshotsRemoverWG.Add(1)
 	go func() {
 		defer staleSnapshotsRemoverWG.Done()
diff --git a/deployment/docker/README.md b/deployment/docker/README.md
index d4ab1a9cf..ed9364557 100644
--- a/deployment/docker/README.md
+++ b/deployment/docker/README.md
@@ -128,3 +128,16 @@ of alerting rules is the following:
 
 Please, also see [how to monitor](https://docs.victoriametrics.com/Single-server-VictoriaMetrics.html#monitoring) 
 VictoriaMetrics installations.
+
+## VictoriaLogs server
+
+VictoriaLogs will be accessible on the following port: `--httpListenAddr=:9428`
+
+[Fluent Bit](https://docs.fluentbit.io/manual) is used to send logs to VictoriaLogs instance.
+Fluent Bit is configured to send logs from running containers to VictoriaLogs instance.
+Additionally, it is configured to listen for syslog logs on port `5140` and send them to VictoriaLogs instance.
+
+To access VictoriaLogs UI use link [http://localhost:9428/select/vmui/](http://localhost:9428/select/vmui/).
+
+Please, also see [how to monitor](https://docs.victoriametrics.com/VictoriaLogs/#monitoring) 
+VictoriaLogs installations.
diff --git a/deployment/docker/docker-compose.yml b/deployment/docker/docker-compose.yml
index a1a59dd33..568b3b9d2 100644
--- a/deployment/docker/docker-compose.yml
+++ b/deployment/docker/docker-compose.yml
@@ -92,9 +92,27 @@ services:
     networks:
       - vm_net
     restart: always
+  fluentbit:
+    image: cr.fluentbit.io/fluent/fluent-bit:2.1.4
+    volumes:
+      - /var/lib/docker/containers:/var/lib/docker/containers:ro
+      - ./fluent-bit.conf:/fluent-bit/etc/fluent-bit.conf
+    depends_on: [victorialogs]
+    ports:
+      - "5140:5140"
+  victorialogs:
+    image: docker.io/victoriametrics/victoria-logs:v0.3.0-victorialogs
+    command:
+      - "--storageDataPath=/vlogs"
+      - "--httpListenAddr=:9428"
+    volumes:
+      - victorialogs-fluentbit:/vlogs
+    ports:
+      - "9428:9428"
 volumes:
   vmagentdata: {}
   vmdata: {}
   grafanadata: {}
+  victorialogs-fluentbit: {}
 networks:
   vm_net:
diff --git a/deployment/docker/fluent-bit.conf b/deployment/docker/fluent-bit.conf
new file mode 100644
index 000000000..42d349338
--- /dev/null
+++ b/deployment/docker/fluent-bit.conf
@@ -0,0 +1,30 @@
+[INPUT]
+    name              tail
+    path              /var/lib/docker/containers/**/*.log
+    path_key         path
+    multiline.parser  docker, cri
+    Parser docker
+    Docker_Mode  On
+
+[INPUT]
+    Name     syslog
+    Listen   0.0.0.0
+    Port     5140
+    Parser   syslog-rfc3164
+    Mode     tcp
+
+[SERVICE]
+    Flush        1
+    Parsers_File parsers.conf
+
+[Output]
+    Name http
+    Match *
+    host victorialogs
+    port 9428
+    compress gzip
+    uri /insert/jsonline?_stream_fields=stream,path&_msg_field=log&_time_field=date
+    format json_lines
+    json_date_format iso8601
+    header AccountID 0
+    header ProjectID 0
diff --git a/deployment/docker/victorialogs/filebeat-docker/docker-compose.yml b/deployment/docker/victorialogs/filebeat-docker/docker-compose.yml
index 734106467..58d482aea 100644
--- a/deployment/docker/victorialogs/filebeat-docker/docker-compose.yml
+++ b/deployment/docker/victorialogs/filebeat-docker/docker-compose.yml
@@ -21,7 +21,6 @@ services:
     command:
       - -beat.uri=http://filebeat-victorialogs:5066
 
-  # Run `make package-victoria-logs` to build victoria-logs image
   victorialogs:
     image: docker.io/victoriametrics/victoria-logs:v0.3.0-victorialogs
     volumes:
diff --git a/deployment/docker/victorialogs/filebeat-syslog/docker-compose.yml b/deployment/docker/victorialogs/filebeat-syslog/docker-compose.yml
index 471376e68..ba5fb63e7 100644
--- a/deployment/docker/victorialogs/filebeat-syslog/docker-compose.yml
+++ b/deployment/docker/victorialogs/filebeat-syslog/docker-compose.yml
@@ -12,7 +12,6 @@ services:
     ports:
       - "5140:5140"
 
-  # Run `make package-victoria-logs` to build victoria-logs image
   victorialogs:
     image: docker.io/victoriametrics/victoria-logs:v0.3.0-victorialogs
     volumes:
diff --git a/deployment/docker/victorialogs/fluentbit-docker/docker-compose.yml b/deployment/docker/victorialogs/fluentbit-docker/docker-compose.yml
index 76b76a457..31b1341e7 100644
--- a/deployment/docker/victorialogs/fluentbit-docker/docker-compose.yml
+++ b/deployment/docker/victorialogs/fluentbit-docker/docker-compose.yml
@@ -10,7 +10,6 @@ services:
     ports:
       - "5140:5140"
 
-  # Run `make package-victoria-logs` to build victoria-logs image
   victorialogs:
     image: docker.io/victoriametrics/victoria-logs:v0.3.0-victorialogs
     volumes:
diff --git a/deployment/docker/victorialogs/fluentbit-docker/fluent-bit.conf b/deployment/docker/victorialogs/fluentbit-docker/fluent-bit.conf
index 8cca3a70e..42d349338 100644
--- a/deployment/docker/victorialogs/fluentbit-docker/fluent-bit.conf
+++ b/deployment/docker/victorialogs/fluentbit-docker/fluent-bit.conf
@@ -1,6 +1,7 @@
 [INPUT]
     name              tail
     path              /var/lib/docker/containers/**/*.log
+    path_key         path
     multiline.parser  docker, cri
     Parser docker
     Docker_Mode  On
@@ -22,8 +23,8 @@
     host victorialogs
     port 9428
     compress gzip
-    uri /insert/jsonline?_stream_fields=stream&_msg_field=log&_time_field=date
+    uri /insert/jsonline?_stream_fields=stream,path&_msg_field=log&_time_field=date
     format json_lines
     json_date_format iso8601
     header AccountID 0
-    header ProjectID 0
\ No newline at end of file
+    header ProjectID 0
diff --git a/deployment/docker/victorialogs/logstash/docker-compose.yml b/deployment/docker/victorialogs/logstash/docker-compose.yml
index d45b6f33c..f2bb4db47 100644
--- a/deployment/docker/victorialogs/logstash/docker-compose.yml
+++ b/deployment/docker/victorialogs/logstash/docker-compose.yml
@@ -13,7 +13,6 @@ services:
     ports:
       - "5140:5140"
 
-  # Run `make package-victoria-logs` to build victoria-logs image
   victorialogs:
     image: docker.io/victoriametrics/victoria-logs:v0.3.0-victorialogs
     volumes:
diff --git a/deployment/docker/victorialogs/promtail/docker-compose.yml b/deployment/docker/victorialogs/promtail/docker-compose.yml
index 6a51c81ed..4ba245197 100644
--- a/deployment/docker/victorialogs/promtail/docker-compose.yml
+++ b/deployment/docker/victorialogs/promtail/docker-compose.yml
@@ -11,7 +11,6 @@ services:
     ports:
       - "5140:5140"
 
-  # Run `make package-victoria-logs` to build victoria-logs image
   vlogs:
     image: docker.io/victoriametrics/victoria-logs:v0.3.0-victorialogs
     volumes:
diff --git a/deployment/docker/victorialogs/vector-docker/docker-compose.yml b/deployment/docker/victorialogs/vector-docker/docker-compose.yml
index 39b636093..d4677c2b7 100644
--- a/deployment/docker/victorialogs/vector-docker/docker-compose.yml
+++ b/deployment/docker/victorialogs/vector-docker/docker-compose.yml
@@ -21,7 +21,6 @@ services:
       victoriametrics:
         condition: service_healthy
 
-  # Run `make package-victoria-logs` to build victoria-logs image
   victorialogs:
     image: docker.io/victoriametrics/victoria-logs:v0.3.0-victorialogs
     volumes:
diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md
index 304ed980d..697fd8a20 100644
--- a/docs/CHANGELOG.md
+++ b/docs/CHANGELOG.md
@@ -36,6 +36,7 @@ The following `tip` changes can be tested by building VictoriaMetrics components
 * FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): improve accessibility score to 100 according to [Google's Lighthouse](https://developer.chrome.com/docs/lighthouse/accessibility/) tests.
 * FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): organize `min`, `max`, `median` values on the chart legend and tooltips for better visibility.
 * FEATURE: dashboards: provide copies of Grafana dashboards alternated with VictoriaMetrics datasource at [dashboards/vm](https://github.com/VictoriaMetrics/VictoriaMetrics/tree/master/dashboards/vm).
+* FEATURE: [vmauth](https://docs.victoriametrics.com/vmauth.html): added ability to set, override and clear request and response headers on a per-user and per-path basis. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/4825) and [these docs](https://docs.victoriametrics.com/vmauth.html#auth-config) for details.
 
 * BUGFIX: [build](https://docs.victoriametrics.com/): fix Docker builds for old Docker releases. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/4907).
 * BUGFIX: [vmagent](https://docs.victoriametrics.com/vmagent.html): consistently set `User-Agent` header to `vm_promscrape` during scraping with enabled or disabled [stream parsing mode](https://docs.victoriametrics.com/vmagent.html#stream-parsing-mode). See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/4884).
diff --git a/docs/vmauth.md b/docs/vmauth.md
index 4dd06f501..e8f4cb243 100644
--- a/docs/vmauth.md
+++ b/docs/vmauth.md
@@ -128,11 +128,16 @@ users:
 
   # Requests with the 'Authorization: Bearer YYY' header are proxied to http://localhost:8428 ,
   # The `X-Scope-OrgID: foobar` http header is added to every proxied request.
+  # The `X-Server-Hostname` http header is removed from the proxied response.
   # For example, http://vmauth:8427/api/v1/query is proxied to http://localhost:8428/api/v1/query
 - bearer_token: "YYY"
   url_prefix: "http://localhost:8428"
+  # extra headers to add to the request or remove from the request (if header value is empty)
   headers:
-  - "X-Scope-OrgID: foobar"
+    - "X-Scope-OrgID: foobar"
+  # extra headers to add to the response or remove from the response (if header value is empty)
+  response_headers:
+    - "X-Server-Hostname:" # empty value means the header will be removed from the response
 
   # All the requests to http://vmauth:8427 with the given Basic Auth (username:password)
   # are proxied to http://localhost:8428 .
@@ -186,6 +191,7 @@ users:
   #
   # - Requests to http://vmauth:8427/api/v1/write are proxied to http://vminsert:8480/insert/42/prometheus/api/v1/write .
   #   The "X-Scope-OrgID: abc" http header is added to these requests.
+  #   The "X-Server-Hostname" http header is removed from the proxied response.
   #
   # Request which do not match `src_paths` from the `url_map` are proxied to the urls from `default_url`
   # in a round-robin manner. The original request path is passed in `request_path` query arg.
@@ -205,6 +211,8 @@ users:
     url_prefix: "http://vminsert:8480/insert/42/prometheus"
     headers:
     - "X-Scope-OrgID: abc"
+    response_headers:
+    - "X-Server-Hostname:" # empty value means the header will be removed from the response
     ip_filters:
       deny_list: [127.0.0.1]
   default_url:
diff --git a/lib/flagutil/duration.go b/lib/flagutil/duration.go
index 87d602c6e..d42e20831 100644
--- a/lib/flagutil/duration.go
+++ b/lib/flagutil/duration.go
@@ -5,6 +5,7 @@ import (
 	"fmt"
 	"strconv"
 	"strings"
+	"time"
 
 	"github.com/VictoriaMetrics/metricsql"
 )
@@ -30,6 +31,11 @@ type Duration struct {
 	valueString string
 }
 
+// Duration convert to time.Duration.
+func (d *Duration) Duration() time.Duration {
+	return time.Millisecond * time.Duration(d.Msecs)
+}
+
 // String implements flag.Value interface
 func (d *Duration) String() string {
 	return d.valueString
diff --git a/lib/logstorage/block_search.go b/lib/logstorage/block_search.go
index 4157189d0..15f4cbcb4 100644
--- a/lib/logstorage/block_search.go
+++ b/lib/logstorage/block_search.go
@@ -120,7 +120,7 @@ func (bs *blockSearch) search(bsw *blockSearchWork) {
 		case "_stream":
 			bs.br.addStreamColumn(bs)
 		case "_time":
-			bs.br.addTimeColumn(bs)
+			bs.br.addTimeColumn()
 		default:
 			v := bs.csh.getConstColumnValue(columnName)
 			if v != "" {
@@ -452,7 +452,7 @@ func (br *blockResult) addColumn(bs *blockSearch, ch *columnHeader, bm *filterBi
 	br.valuesBuf = valuesBuf
 }
 
-func (br *blockResult) addTimeColumn(bs *blockSearch) {
+func (br *blockResult) addTimeColumn() {
 	br.cs = append(br.cs, blockResultColumn{
 		isTime: true,
 	})
diff --git a/lib/logstorage/filters.go b/lib/logstorage/filters.go
index 9a978ce44..168b40d54 100644
--- a/lib/logstorage/filters.go
+++ b/lib/logstorage/filters.go
@@ -155,11 +155,11 @@ func (nf *noopFilter) String() string {
 	return ""
 }
 
-func (nf *noopFilter) updateReferencedColumnNames(m map[string]struct{}) {
+func (nf *noopFilter) updateReferencedColumnNames(_ map[string]struct{}) {
 	// nothing to do
 }
 
-func (nf *noopFilter) apply(bs *blockSearch, bm *filterBitmap) {
+func (nf *noopFilter) apply(_ *blockSearch, _ *filterBitmap) {
 	// nothing to do
 }
 
diff --git a/lib/mergeset/table.go b/lib/mergeset/table.go
index 21df81c72..8b916770e 100644
--- a/lib/mergeset/table.go
+++ b/lib/mergeset/table.go
@@ -662,14 +662,14 @@ func (tb *Table) flushInmemoryParts(isFinal bool) {
 func (riss *rawItemsShards) flush(tb *Table, dst []*inmemoryBlock, isFinal bool) []*inmemoryBlock {
 	tb.rawItemsPendingFlushesWG.Add(1)
 	for i := range riss.shards {
-		dst = riss.shards[i].appendBlocksToFlush(dst, tb, isFinal)
+		dst = riss.shards[i].appendBlocksToFlush(dst, isFinal)
 	}
 	tb.flushBlocksToParts(dst, isFinal)
 	tb.rawItemsPendingFlushesWG.Done()
 	return dst
 }
 
-func (ris *rawItemsShard) appendBlocksToFlush(dst []*inmemoryBlock, tb *Table, isFinal bool) []*inmemoryBlock {
+func (ris *rawItemsShard) appendBlocksToFlush(dst []*inmemoryBlock, isFinal bool) []*inmemoryBlock {
 	currentTime := fasttime.UnixTimestamp()
 	flushSeconds := int64(pendingItemsFlushInterval.Seconds())
 	if flushSeconds <= 0 {
diff --git a/lib/netutil/conn.go b/lib/netutil/conn.go
index 9db9b469a..651752f74 100644
--- a/lib/netutil/conn.go
+++ b/lib/netutil/conn.go
@@ -62,10 +62,9 @@ func (sc *statConn) Read(p []byte) (int, error) {
 	if err != nil && err != io.EOF {
 		var ne net.Error
 		if errors.As(err, &ne) && ne.Timeout() {
-			if fasttime.UnixTimestamp()-startTime <= 1 {
-				// Ignore artificial timeout generated by net/http.Server
-				// See https://cs.opensource.google/go/go/+/refs/tags/go1.20.5:src/net/http/server.go;l=701
-			} else {
+			// Ignore artificial timeout generated by net/http.Server
+			// See https://cs.opensource.google/go/go/+/refs/tags/go1.20.5:src/net/http/server.go;l=701
+			if fasttime.UnixTimestamp()-startTime > 1 {
 				sc.cm.readTimeouts.Inc()
 			}
 		} else {
diff --git a/lib/promscrape/discovery/dns/dns.go b/lib/promscrape/discovery/dns/dns.go
index c08741746..5b7244841 100644
--- a/lib/promscrape/discovery/dns/dns.go
+++ b/lib/promscrape/discovery/dns/dns.go
@@ -31,7 +31,7 @@ type SDConfig struct {
 }
 
 // GetLabels returns DNS labels according to sdc.
-func (sdc *SDConfig) GetLabels(baseDir string) ([]*promutils.Labels, error) {
+func (sdc *SDConfig) GetLabels(_ string) ([]*promutils.Labels, error) {
 	if len(sdc.Names) == 0 {
 		return nil, fmt.Errorf("`names` cannot be empty in `dns_sd_config`")
 	}
diff --git a/lib/promscrape/discovery/ec2/ec2.go b/lib/promscrape/discovery/ec2/ec2.go
index afed1bde3..868353ae9 100644
--- a/lib/promscrape/discovery/ec2/ec2.go
+++ b/lib/promscrape/discovery/ec2/ec2.go
@@ -35,7 +35,7 @@ type SDConfig struct {
 }
 
 // GetLabels returns ec2 labels according to sdc.
-func (sdc *SDConfig) GetLabels(baseDir string) ([]*promutils.Labels, error) {
+func (sdc *SDConfig) GetLabels(_ string) ([]*promutils.Labels, error) {
 	cfg, err := getAPIConfig(sdc)
 	if err != nil {
 		return nil, fmt.Errorf("cannot get API config: %w", err)
diff --git a/lib/promscrape/discovery/gce/gce.go b/lib/promscrape/discovery/gce/gce.go
index a52e35962..9fd323ad5 100644
--- a/lib/promscrape/discovery/gce/gce.go
+++ b/lib/promscrape/discovery/gce/gce.go
@@ -62,7 +62,7 @@ func (z ZoneYAML) MarshalYAML() (interface{}, error) {
 }
 
 // GetLabels returns gce labels according to sdc.
-func (sdc *SDConfig) GetLabels(baseDir string) ([]*promutils.Labels, error) {
+func (sdc *SDConfig) GetLabels(_ string) ([]*promutils.Labels, error) {
 	cfg, err := getAPIConfig(sdc)
 	if err != nil {
 		return nil, fmt.Errorf("cannot get API config: %w", err)
diff --git a/lib/promscrape/discovery/kubernetes/ingress.go b/lib/promscrape/discovery/kubernetes/ingress.go
index 59ff90904..3f7841990 100644
--- a/lib/promscrape/discovery/kubernetes/ingress.go
+++ b/lib/promscrape/discovery/kubernetes/ingress.go
@@ -91,7 +91,7 @@ type HTTPIngressPath struct {
 // getTargetLabels returns labels for ig.
 //
 // See https://prometheus.io/docs/prometheus/latest/configuration/configuration/#ingress
-func (ig *Ingress) getTargetLabels(gw *groupWatcher) []*promutils.Labels {
+func (ig *Ingress) getTargetLabels(_ *groupWatcher) []*promutils.Labels {
 	var ms []*promutils.Labels
 	for _, r := range ig.Spec.Rules {
 		paths := getIngressRulePaths(r.HTTP.Paths)
diff --git a/lib/promscrape/discovery/kubernetes/node.go b/lib/promscrape/discovery/kubernetes/node.go
index 038c39d96..279bc7918 100644
--- a/lib/promscrape/discovery/kubernetes/node.go
+++ b/lib/promscrape/discovery/kubernetes/node.go
@@ -85,7 +85,7 @@ type NodeDaemonEndpoints struct {
 // getTargetLabels returns labels for the given n.
 //
 // See https://prometheus.io/docs/prometheus/latest/configuration/configuration/#node
-func (n *Node) getTargetLabels(gw *groupWatcher) []*promutils.Labels {
+func (n *Node) getTargetLabels(_ *groupWatcher) []*promutils.Labels {
 	addr := getNodeAddr(n.Status.Addresses)
 	if len(addr) == 0 {
 		// Skip node without address
diff --git a/lib/promscrape/discovery/kubernetes/service.go b/lib/promscrape/discovery/kubernetes/service.go
index 1306673be..8dcd2e9bb 100644
--- a/lib/promscrape/discovery/kubernetes/service.go
+++ b/lib/promscrape/discovery/kubernetes/service.go
@@ -73,7 +73,7 @@ type ServicePort struct {
 // getTargetLabels returns labels for each port of the given s.
 //
 // See https://prometheus.io/docs/prometheus/latest/configuration/configuration/#service
-func (s *Service) getTargetLabels(gw *groupWatcher) []*promutils.Labels {
+func (s *Service) getTargetLabels(_ *groupWatcher) []*promutils.Labels {
 	host := fmt.Sprintf("%s.%s.svc", s.Metadata.Name, s.Metadata.Namespace)
 	var ms []*promutils.Labels
 	for _, sp := range s.Spec.Ports {
diff --git a/lib/promscrape/statconn.go b/lib/promscrape/statconn.go
index 96a38fe15..8757342cc 100644
--- a/lib/promscrape/statconn.go
+++ b/lib/promscrape/statconn.go
@@ -15,7 +15,7 @@ import (
 	"github.com/VictoriaMetrics/metrics"
 )
 
-func statStdDial(ctx context.Context, networkUnused, addr string) (net.Conn, error) {
+func statStdDial(ctx context.Context, _, addr string) (net.Conn, error) {
 	d := getStdDialer()
 	network := netutil.GetTCPNetwork()
 	conn, err := d.DialContext(ctx, network, addr)
diff --git a/lib/storage/block_stream_merger.go b/lib/storage/block_stream_merger.go
index 77992295e..7faba9611 100644
--- a/lib/storage/block_stream_merger.go
+++ b/lib/storage/block_stream_merger.go
@@ -61,7 +61,7 @@ func (bsm *blockStreamMerger) Init(bsrs []*blockStreamReader, retentionDeadline
 	bsm.nextBlockNoop = true
 }
 
-func (bsm *blockStreamMerger) getRetentionDeadline(bh *blockHeader) int64 {
+func (bsm *blockStreamMerger) getRetentionDeadline(_ *blockHeader) int64 {
 	return bsm.retentionDeadline
 }
 
diff --git a/lib/storage/index_db.go b/lib/storage/index_db.go
index d4f853168..7c99809c6 100644
--- a/lib/storage/index_db.go
+++ b/lib/storage/index_db.go
@@ -1432,7 +1432,7 @@ func (th *topHeap) Swap(i, j int) {
 	a[j], a[i] = a[i], a[j]
 }
 
-func (th *topHeap) Push(x interface{}) {
+func (th *topHeap) Push(_ interface{}) {
 	panic(fmt.Errorf("BUG: Push shouldn't be called"))
 }
 
diff --git a/lib/storage/partition.go b/lib/storage/partition.go
index 3f48ae224..da66c9c88 100644
--- a/lib/storage/partition.go
+++ b/lib/storage/partition.go
@@ -881,13 +881,13 @@ func (pt *partition) flushInmemoryParts(isFinal bool) {
 
 func (rrss *rawRowsShards) flush(pt *partition, dst []rawRow, isFinal bool) []rawRow {
 	for i := range rrss.shards {
-		dst = rrss.shards[i].appendRawRowsToFlush(dst, pt, isFinal)
+		dst = rrss.shards[i].appendRawRowsToFlush(dst, isFinal)
 	}
 	pt.flushRowsToParts(dst)
 	return dst
 }
 
-func (rrs *rawRowsShard) appendRawRowsToFlush(dst []rawRow, pt *partition, isFinal bool) []rawRow {
+func (rrs *rawRowsShard) appendRawRowsToFlush(dst []rawRow, isFinal bool) []rawRow {
 	currentTime := fasttime.UnixTimestamp()
 	flushSeconds := int64(pendingRowsFlushInterval.Seconds())
 	if flushSeconds <= 0 {
diff --git a/lib/storage/storage.go b/lib/storage/storage.go
index e61c0f0c0..9514ea8c9 100644
--- a/lib/storage/storage.go
+++ b/lib/storage/storage.go
@@ -33,8 +33,8 @@ import (
 )
 
 const (
-	msecsPerMonth     = 31 * 24 * 3600 * 1000
-	maxRetentionMsecs = 100 * 12 * msecsPerMonth
+	retentionMonth = 31 * 24 * time.Hour
+	retentionMax   = 100 * 12 * retentionMonth
 )
 
 // Storage represents TSDB storage.
@@ -154,21 +154,18 @@ type Storage struct {
 }
 
 // MustOpenStorage opens storage on the given path with the given retentionMsecs.
-func MustOpenStorage(path string, retentionMsecs int64, maxHourlySeries, maxDailySeries int) *Storage {
+func MustOpenStorage(path string, retention time.Duration, maxHourlySeries, maxDailySeries int) *Storage {
 	path, err := filepath.Abs(path)
 	if err != nil {
 		logger.Panicf("FATAL: cannot determine absolute path for %q: %s", path, err)
 	}
-	if retentionMsecs <= 0 {
-		retentionMsecs = maxRetentionMsecs
-	}
-	if retentionMsecs > maxRetentionMsecs {
-		retentionMsecs = maxRetentionMsecs
+	if retention <= 0 || retention > retentionMax {
+		retention = retentionMax
 	}
 	s := &Storage{
 		path:           path,
 		cachePath:      filepath.Join(path, cacheDirname),
-		retentionMsecs: retentionMsecs,
+		retentionMsecs: retention.Milliseconds(),
 		stop:           make(chan struct{}),
 	}
 	fs.MustMkdirIfNotExist(path)
@@ -246,7 +243,8 @@ func MustOpenStorage(path string, retentionMsecs int64, maxHourlySeries, maxDail
 
 	// Initialize nextRotationTimestamp
 	nowSecs := time.Now().UnixNano() / 1e9
-	nextRotationTimestamp := nextRetentionDeadlineSeconds(nowSecs, retentionMsecs/1000, retentionTimezoneOffsetSecs)
+	retentionSecs := retention.Milliseconds() / 1000 // not .Seconds() because unnecessary float64 conversion
+	nextRotationTimestamp := nextRetentionDeadlineSeconds(nowSecs, retentionSecs, retentionTimezoneOffsetSecs)
 	atomic.StoreInt64(&s.nextRotationTimestamp, nextRotationTimestamp)
 
 	// Load nextDayMetricIDs cache
diff --git a/lib/streamaggr/avg.go b/lib/streamaggr/avg.go
index ce9becc82..3f5463649 100644
--- a/lib/streamaggr/avg.go
+++ b/lib/streamaggr/avg.go
@@ -22,7 +22,7 @@ func newAvgAggrState() *avgAggrState {
 	return &avgAggrState{}
 }
 
-func (as *avgAggrState) pushSample(inputKey, outputKey string, value float64) {
+func (as *avgAggrState) pushSample(_, outputKey string, value float64) {
 again:
 	v, ok := as.m.Load(outputKey)
 	if !ok {
diff --git a/lib/streamaggr/count_samples.go b/lib/streamaggr/count_samples.go
index ddf582e8c..b83d83268 100644
--- a/lib/streamaggr/count_samples.go
+++ b/lib/streamaggr/count_samples.go
@@ -21,7 +21,7 @@ func newCountSamplesAggrState() *countSamplesAggrState {
 	return &countSamplesAggrState{}
 }
 
-func (as *countSamplesAggrState) pushSample(inputKey, outputKey string, value float64) {
+func (as *countSamplesAggrState) pushSample(_, outputKey string, _ float64) {
 again:
 	v, ok := as.m.Load(outputKey)
 	if !ok {
diff --git a/lib/streamaggr/count_series.go b/lib/streamaggr/count_series.go
index ffb8e5578..b4a409e71 100644
--- a/lib/streamaggr/count_series.go
+++ b/lib/streamaggr/count_series.go
@@ -22,7 +22,7 @@ func newCountSeriesAggrState() *countSeriesAggrState {
 	return &countSeriesAggrState{}
 }
 
-func (as *countSeriesAggrState) pushSample(inputKey, outputKey string, value float64) {
+func (as *countSeriesAggrState) pushSample(inputKey, outputKey string, _ float64) {
 again:
 	v, ok := as.m.Load(outputKey)
 	if !ok {
diff --git a/lib/streamaggr/histogram_bucket.go b/lib/streamaggr/histogram_bucket.go
index 4e27e3cd2..32568179c 100644
--- a/lib/streamaggr/histogram_bucket.go
+++ b/lib/streamaggr/histogram_bucket.go
@@ -30,7 +30,7 @@ func newHistogramBucketAggrState(stalenessInterval time.Duration) *histogramBuck
 	}
 }
 
-func (as *histogramBucketAggrState) pushSample(inputKey, outputKey string, value float64) {
+func (as *histogramBucketAggrState) pushSample(_, outputKey string, value float64) {
 	currentTime := fasttime.UnixTimestamp()
 	deleteDeadline := currentTime + as.stalenessSecs
 
diff --git a/lib/streamaggr/last.go b/lib/streamaggr/last.go
index c81a41db6..b51a8baf3 100644
--- a/lib/streamaggr/last.go
+++ b/lib/streamaggr/last.go
@@ -21,7 +21,7 @@ func newLastAggrState() *lastAggrState {
 	return &lastAggrState{}
 }
 
-func (as *lastAggrState) pushSample(inputKey, outputKey string, value float64) {
+func (as *lastAggrState) pushSample(_, outputKey string, value float64) {
 again:
 	v, ok := as.m.Load(outputKey)
 	if !ok {
diff --git a/lib/streamaggr/max.go b/lib/streamaggr/max.go
index 09122ee64..fdb91aa27 100644
--- a/lib/streamaggr/max.go
+++ b/lib/streamaggr/max.go
@@ -21,7 +21,7 @@ func newMaxAggrState() *maxAggrState {
 	return &maxAggrState{}
 }
 
-func (as *maxAggrState) pushSample(inputKey, outputKey string, value float64) {
+func (as *maxAggrState) pushSample(_, outputKey string, value float64) {
 again:
 	v, ok := as.m.Load(outputKey)
 	if !ok {
diff --git a/lib/streamaggr/min.go b/lib/streamaggr/min.go
index e9e5dfcd2..707ffb7ec 100644
--- a/lib/streamaggr/min.go
+++ b/lib/streamaggr/min.go
@@ -21,7 +21,7 @@ func newMinAggrState() *minAggrState {
 	return &minAggrState{}
 }
 
-func (as *minAggrState) pushSample(inputKey, outputKey string, value float64) {
+func (as *minAggrState) pushSample(_, outputKey string, value float64) {
 again:
 	v, ok := as.m.Load(outputKey)
 	if !ok {
diff --git a/lib/streamaggr/quantiles.go b/lib/streamaggr/quantiles.go
index ebd9590fb..31b7affe7 100644
--- a/lib/streamaggr/quantiles.go
+++ b/lib/streamaggr/quantiles.go
@@ -28,7 +28,7 @@ func newQuantilesAggrState(phis []float64) *quantilesAggrState {
 	}
 }
 
-func (as *quantilesAggrState) pushSample(inputKey, outputKey string, value float64) {
+func (as *quantilesAggrState) pushSample(_, outputKey string, value float64) {
 again:
 	v, ok := as.m.Load(outputKey)
 	if !ok {
diff --git a/lib/streamaggr/stddev.go b/lib/streamaggr/stddev.go
index 107c78896..9d270bcb5 100644
--- a/lib/streamaggr/stddev.go
+++ b/lib/streamaggr/stddev.go
@@ -24,7 +24,7 @@ func newStddevAggrState() *stddevAggrState {
 	return &stddevAggrState{}
 }
 
-func (as *stddevAggrState) pushSample(inputKey, outputKey string, value float64) {
+func (as *stddevAggrState) pushSample(_, outputKey string, value float64) {
 again:
 	v, ok := as.m.Load(outputKey)
 	if !ok {
diff --git a/lib/streamaggr/stdvar.go b/lib/streamaggr/stdvar.go
index d9a19e592..f2e03313b 100644
--- a/lib/streamaggr/stdvar.go
+++ b/lib/streamaggr/stdvar.go
@@ -23,7 +23,7 @@ func newStdvarAggrState() *stdvarAggrState {
 	return &stdvarAggrState{}
 }
 
-func (as *stdvarAggrState) pushSample(inputKey, outputKey string, value float64) {
+func (as *stdvarAggrState) pushSample(_, outputKey string, value float64) {
 again:
 	v, ok := as.m.Load(outputKey)
 	if !ok {
diff --git a/lib/streamaggr/sum_samples.go b/lib/streamaggr/sum_samples.go
index ba349a080..918ecf449 100644
--- a/lib/streamaggr/sum_samples.go
+++ b/lib/streamaggr/sum_samples.go
@@ -21,7 +21,7 @@ func newSumSamplesAggrState() *sumSamplesAggrState {
 	return &sumSamplesAggrState{}
 }
 
-func (as *sumSamplesAggrState) pushSample(inputKey, outputKey string, value float64) {
+func (as *sumSamplesAggrState) pushSample(_, outputKey string, value float64) {
 again:
 	v, ok := as.m.Load(outputKey)
 	if !ok {


Test patch:
diff --git a/app/vmalert/alerting_test.go b/app/vmalert/alerting_test.go
index 5f8f72558..9c779d738 100644
--- a/app/vmalert/alerting_test.go
+++ b/app/vmalert/alerting_test.go
@@ -872,7 +872,6 @@ func TestAlertingRule_Template(t *testing.T) {
 				gotAlert := tc.rule.alerts[hash]
 				if gotAlert == nil {
 					t.Fatalf("alert %d is missing; labels: %v; annotations: %v", hash, expAlert.Labels, expAlert.Annotations)
-					break
 				}
 				if !reflect.DeepEqual(expAlert.Annotations, gotAlert.Annotations) {
 					t.Fatalf("expected to have annotations %#v; got %#v", expAlert.Annotations, gotAlert.Annotations)
diff --git a/app/vmalert/rule_test.go b/app/vmalert/rule_test.go
index 15aed5c21..1c5c61451 100644
--- a/app/vmalert/rule_test.go
+++ b/app/vmalert/rule_test.go
@@ -79,7 +79,7 @@ func TestRule_state(t *testing.T) {
 // TestRule_stateConcurrent supposed to test concurrent
 // execution of state updates.
 // Should be executed with -race flag
-func TestRule_stateConcurrent(t *testing.T) {
+func TestRule_stateConcurrent(_ *testing.T) {
 	state := newRuleState(20)
 
 	const workers = 50
diff --git a/app/vmalert/utils/err_group_test.go b/app/vmalert/utils/err_group_test.go
index de57ee32d..366b508ab 100644
--- a/app/vmalert/utils/err_group_test.go
+++ b/app/vmalert/utils/err_group_test.go
@@ -41,7 +41,7 @@ func TestErrGroup(t *testing.T) {
 // TestErrGroupConcurrent supposed to test concurrent
 // use of error group.
 // Should be executed with -race flag
-func TestErrGroupConcurrent(t *testing.T) {
+func TestErrGroupConcurrent(_ *testing.T) {
 	eg := new(ErrGroup)
 
 	const writersN = 4
diff --git a/app/vmauth/auth_config_test.go b/app/vmauth/auth_config_test.go
index e794231aa..dc465bcfc 100644
--- a/app/vmauth/auth_config_test.go
+++ b/app/vmauth/auth_config_test.go
@@ -299,14 +299,16 @@ users:
 						"http://vminsert1/insert/0/prometheus",
 						"http://vminsert2/insert/0/prometheus",
 					}),
-					Headers: []Header{
-						{
-							Name:  "foo",
-							Value: "bar",
-						},
-						{
-							Name:  "xxx",
-							Value: "y",
+					HeadersConf: HeadersConf{
+						RequestHeaders: []Header{
+							{
+								Name:  "foo",
+								Value: "bar",
+							},
+							{
+								Name:  "xxx",
+								Value: "y",
+							},
 						},
 					},
 				},
@@ -325,14 +327,16 @@ users:
 						"http://vminsert1/insert/0/prometheus",
 						"http://vminsert2/insert/0/prometheus",
 					}),
-					Headers: []Header{
-						{
-							Name:  "foo",
-							Value: "bar",
-						},
-						{
-							Name:  "xxx",
-							Value: "y",
+					HeadersConf: HeadersConf{
+						RequestHeaders: []Header{
+							{
+								Name:  "foo",
+								Value: "bar",
+							},
+							{
+								Name:  "xxx",
+								Value: "y",
+							},
 						},
 					},
 				},
@@ -389,14 +393,16 @@ users:
 						"http://vminsert1/insert/0/prometheus",
 						"http://vminsert2/insert/0/prometheus",
 					}),
-					Headers: []Header{
-						{
-							Name:  "foo",
-							Value: "bar",
-						},
-						{
-							Name:  "xxx",
-							Value: "y",
+					HeadersConf: HeadersConf{
+						RequestHeaders: []Header{
+							{
+								Name:  "foo",
+								Value: "bar",
+							},
+							{
+								Name:  "xxx",
+								Value: "y",
+							},
 						},
 					},
 				},
@@ -419,14 +425,16 @@ users:
 						"http://vminsert1/insert/0/prometheus",
 						"http://vminsert2/insert/0/prometheus",
 					}),
-					Headers: []Header{
-						{
-							Name:  "foo",
-							Value: "bar",
-						},
-						{
-							Name:  "xxx",
-							Value: "y",
+					HeadersConf: HeadersConf{
+						RequestHeaders: []Header{
+							{
+								Name:  "foo",
+								Value: "bar",
+							},
+							{
+								Name:  "xxx",
+								Value: "y",
+							},
 						},
 					},
 				},
diff --git a/app/vmauth/target_url_test.go b/app/vmauth/target_url_test.go
index 482138e17..9a35d19d3 100644
--- a/app/vmauth/target_url_test.go
+++ b/app/vmauth/target_url_test.go
@@ -7,7 +7,7 @@ import (
 )
 
 func TestCreateTargetURLSuccess(t *testing.T) {
-	f := func(ui *UserInfo, requestURI, expectedTarget, expectedHeaders string) {
+	f := func(ui *UserInfo, requestURI, expectedTarget, expectedRequestHeaders, expectedResponseHeaders string) {
 		t.Helper()
 		u, err := url.Parse(requestURI)
 		if err != nil {
@@ -24,37 +24,39 @@ func TestCreateTargetURLSuccess(t *testing.T) {
 		if target.String() != expectedTarget {
 			t.Fatalf("unexpected target; got %q; want %q", target, expectedTarget)
 		}
-		headersStr := fmt.Sprintf("%q", headers)
-		if headersStr != expectedHeaders {
-			t.Fatalf("unexpected headers; got %s; want %s", headersStr, expectedHeaders)
+		headersStr := fmt.Sprintf("%q", headers.RequestHeaders)
+		if headersStr != expectedRequestHeaders {
+			t.Fatalf("unexpected headers; got %s; want %s", headersStr, expectedRequestHeaders)
 		}
 	}
 	// Simple routing with `url_prefix`
 	f(&UserInfo{
 		URLPrefix: mustParseURL("http://foo.bar"),
-	}, "", "http://foo.bar/.", "[]")
+	}, "", "http://foo.bar/.", "[]", "[]")
 	f(&UserInfo{
 		URLPrefix: mustParseURL("http://foo.bar"),
-		Headers: []Header{{
-			Name:  "bb",
-			Value: "aaa",
-		}},
-	}, "/", "http://foo.bar", `[{"bb" "aaa"}]`)
+		HeadersConf: HeadersConf{
+			RequestHeaders: []Header{{
+				Name:  "bb",
+				Value: "aaa",
+			}},
+		},
+	}, "/", "http://foo.bar", `[{"bb" "aaa"}]`, `[]`)
 	f(&UserInfo{
 		URLPrefix: mustParseURL("http://foo.bar/federate"),
-	}, "/", "http://foo.bar/federate", "[]")
+	}, "/", "http://foo.bar/federate", "[]", "[]")
 	f(&UserInfo{
 		URLPrefix: mustParseURL("http://foo.bar"),
-	}, "a/b?c=d", "http://foo.bar/a/b?c=d", "[]")
+	}, "a/b?c=d", "http://foo.bar/a/b?c=d", "[]", "[]")
 	f(&UserInfo{
 		URLPrefix: mustParseURL("https://sss:3894/x/y"),
-	}, "/z", "https://sss:3894/x/y/z", "[]")
+	}, "/z", "https://sss:3894/x/y/z", "[]", "[]")
 	f(&UserInfo{
 		URLPrefix: mustParseURL("https://sss:3894/x/y"),
-	}, "/../../aaa", "https://sss:3894/x/y/aaa", "[]")
+	}, "/../../aaa", "https://sss:3894/x/y/aaa", "[]", "[]")
 	f(&UserInfo{
 		URLPrefix: mustParseURL("https://sss:3894/x/y"),
-	}, "/./asd/../../aaa?a=d&s=s/../d", "https://sss:3894/x/y/aaa?a=d&s=s%2F..%2Fd", "[]")
+	}, "/./asd/../../aaa?a=d&s=s/../d", "https://sss:3894/x/y/aaa?a=d&s=s%2F..%2Fd", "[]", "[]")
 
 	// Complex routing with `url_map`
 	ui := &UserInfo{
@@ -62,14 +64,22 @@ func TestCreateTargetURLSuccess(t *testing.T) {
 			{
 				SrcPaths:  getSrcPaths([]string{"/api/v1/query"}),
 				URLPrefix: mustParseURL("http://vmselect/0/prometheus"),
-				Headers: []Header{
-					{
-						Name:  "xx",
-						Value: "aa",
+				HeadersConf: HeadersConf{
+					RequestHeaders: []Header{
+						{
+							Name:  "xx",
+							Value: "aa",
+						},
+						{
+							Name:  "yy",
+							Value: "asdf",
+						},
 					},
-					{
-						Name:  "yy",
-						Value: "asdf",
+					ResponseHeaders: []Header{
+						{
+							Name:  "qwe",
+							Value: "rty",
+						},
 					},
 				},
 			},
@@ -79,14 +89,20 @@ func TestCreateTargetURLSuccess(t *testing.T) {
 			},
 		},
 		URLPrefix: mustParseURL("http://default-server"),
-		Headers: []Header{{
-			Name:  "bb",
-			Value: "aaa",
-		}},
+		HeadersConf: HeadersConf{
+			RequestHeaders: []Header{{
+				Name:  "bb",
+				Value: "aaa",
+			}},
+			ResponseHeaders: []Header{{
+				Name:  "x",
+				Value: "y",
+			}},
+		},
 	}
-	f(ui, "/api/v1/query?query=up", "http://vmselect/0/prometheus/api/v1/query?query=up", `[{"xx" "aa"} {"yy" "asdf"}]`)
-	f(ui, "/api/v1/write", "http://vminsert/0/prometheus/api/v1/write", "[]")
-	f(ui, "/api/v1/query_range", "http://default-server/api/v1/query_range", `[{"bb" "aaa"}]`)
+	f(ui, "/api/v1/query?query=up", "http://vmselect/0/prometheus/api/v1/query?query=up", `[{"xx" "aa"} {"yy" "asdf"}]`, `[{"qwe" "rty"}]`)
+	f(ui, "/api/v1/write", "http://vminsert/0/prometheus/api/v1/write", "[]", "[]")
+	f(ui, "/api/v1/query_range", "http://default-server/api/v1/query_range", `[{"bb" "aaa"}]`, `[{"x" "y"}]`)
 
 	// Complex routing regexp paths in `url_map`
 	ui = &UserInfo{
@@ -102,18 +118,17 @@ func TestCreateTargetURLSuccess(t *testing.T) {
 		},
 		URLPrefix: mustParseURL("http://default-server"),
 	}
-	f(ui, "/api/v1/query?query=up", "http://vmselect/0/prometheus/api/v1/query?query=up", "[]")
-	f(ui, "/api/v1/query_range?query=up", "http://vmselect/0/prometheus/api/v1/query_range?query=up", "[]")
-	f(ui, "/api/v1/label/foo/values", "http://vmselect/0/prometheus/api/v1/label/foo/values", "[]")
-	f(ui, "/api/v1/write", "http://vminsert/0/prometheus/api/v1/write", "[]")
-	f(ui, "/api/v1/foo/bar", "http://default-server/api/v1/foo/bar", "[]")
+	f(ui, "/api/v1/query?query=up", "http://vmselect/0/prometheus/api/v1/query?query=up", "[]", "[]")
+	f(ui, "/api/v1/query_range?query=up", "http://vmselect/0/prometheus/api/v1/query_range?query=up", "[]", "[]")
+	f(ui, "/api/v1/label/foo/values", "http://vmselect/0/prometheus/api/v1/label/foo/values", "[]", "[]")
+	f(ui, "/api/v1/write", "http://vminsert/0/prometheus/api/v1/write", "[]", "[]")
+	f(ui, "/api/v1/foo/bar", "http://default-server/api/v1/foo/bar", "[]", "[]")
 	f(&UserInfo{
 		URLPrefix: mustParseURL("http://foo.bar?extra_label=team=dev"),
-	}, "/api/v1/query", "http://foo.bar/api/v1/query?extra_label=team=dev", "[]")
+	}, "/api/v1/query", "http://foo.bar/api/v1/query?extra_label=team=dev", "[]", "[]")
 	f(&UserInfo{
 		URLPrefix: mustParseURL("http://foo.bar?extra_label=team=mobile"),
-	}, "/api/v1/query?extra_label=team=dev", "http://foo.bar/api/v1/query?extra_label=team%3Dmobile", "[]")
-
+	}, "/api/v1/query?extra_label=team=dev", "http://foo.bar/api/v1/query?extra_label=team%3Dmobile", "[]", "[]")
 }
 
 func TestCreateTargetURLFailure(t *testing.T) {
@@ -128,7 +143,10 @@ func TestCreateTargetURLFailure(t *testing.T) {
 		if up != nil {
 			t.Fatalf("unexpected non-empty up=%#v", up)
 		}
-		if headers != nil {
+		if headers.RequestHeaders != nil {
+			t.Fatalf("unexpected non-empty headers=%q", headers)
+		}
+		if headers.ResponseHeaders != nil {
 			t.Fatalf("unexpected non-empty headers=%q", headers)
 		}
 	}
diff --git a/app/vmselect/promql/rollup_test.go b/app/vmselect/promql/rollup_test.go
index 970b0b3ba..5b967b13d 100644
--- a/app/vmselect/promql/rollup_test.go
+++ b/app/vmselect/promql/rollup_test.go
@@ -160,7 +160,7 @@ func TestDerivValues(t *testing.T) {
 	testRowsEqual(t, values, timestamps, valuesExpected, timestamps)
 }
 
-func testRollupFunc(t *testing.T, funcName string, args []interface{}, meExpected *metricsql.MetricExpr, vExpected float64) {
+func testRollupFunc(t *testing.T, funcName string, args []interface{}, vExpected float64) {
 	t.Helper()
 	nrf := getRollupFunc(funcName)
 	if nrf == nil {
@@ -203,7 +203,7 @@ func TestRollupDurationOverTime(t *testing.T) {
 		}}
 		var me metricsql.MetricExpr
 		args := []interface{}{&metricsql.RollupExpr{Expr: &me}, maxIntervals}
-		testRollupFunc(t, "duration_over_time", args, &me, dExpected)
+		testRollupFunc(t, "duration_over_time", args, dExpected)
 	}
 	f(-123, 0)
 	f(0, 0)
@@ -224,7 +224,7 @@ func TestRollupShareLEOverTime(t *testing.T) {
 		}}
 		var me metricsql.MetricExpr
 		args := []interface{}{&metricsql.RollupExpr{Expr: &me}, les}
-		testRollupFunc(t, "share_le_over_time", args, &me, vExpected)
+		testRollupFunc(t, "share_le_over_time", args, vExpected)
 	}
 
 	f(-123, 0)
@@ -247,7 +247,7 @@ func TestRollupShareGTOverTime(t *testing.T) {
 		}}
 		var me metricsql.MetricExpr
 		args := []interface{}{&metricsql.RollupExpr{Expr: &me}, gts}
-		testRollupFunc(t, "share_gt_over_time", args, &me, vExpected)
+		testRollupFunc(t, "share_gt_over_time", args, vExpected)
 	}
 
 	f(-123, 1)
@@ -270,7 +270,7 @@ func TestRollupShareEQOverTime(t *testing.T) {
 		}}
 		var me metricsql.MetricExpr
 		args := []interface{}{&metricsql.RollupExpr{Expr: &me}, eqs}
-		testRollupFunc(t, "share_eq_over_time", args, &me, vExpected)
+		testRollupFunc(t, "share_eq_over_time", args, vExpected)
 	}
 
 	f(-123, 0)
@@ -289,7 +289,7 @@ func TestRollupCountLEOverTime(t *testing.T) {
 		}}
 		var me metricsql.MetricExpr
 		args := []interface{}{&metricsql.RollupExpr{Expr: &me}, les}
-		testRollupFunc(t, "count_le_over_time", args, &me, vExpected)
+		testRollupFunc(t, "count_le_over_time", args, vExpected)
 	}
 
 	f(-123, 0)
@@ -312,7 +312,7 @@ func TestRollupCountGTOverTime(t *testing.T) {
 		}}
 		var me metricsql.MetricExpr
 		args := []interface{}{&metricsql.RollupExpr{Expr: &me}, gts}
-		testRollupFunc(t, "count_gt_over_time", args, &me, vExpected)
+		testRollupFunc(t, "count_gt_over_time", args, vExpected)
 	}
 
 	f(-123, 12)
@@ -335,7 +335,7 @@ func TestRollupCountEQOverTime(t *testing.T) {
 		}}
 		var me metricsql.MetricExpr
 		args := []interface{}{&metricsql.RollupExpr{Expr: &me}, eqs}
-		testRollupFunc(t, "count_eq_over_time", args, &me, vExpected)
+		testRollupFunc(t, "count_eq_over_time", args, vExpected)
 	}
 
 	f(-123, 0)
@@ -354,7 +354,7 @@ func TestRollupCountNEOverTime(t *testing.T) {
 		}}
 		var me metricsql.MetricExpr
 		args := []interface{}{&metricsql.RollupExpr{Expr: &me}, nes}
-		testRollupFunc(t, "count_ne_over_time", args, &me, vExpected)
+		testRollupFunc(t, "count_ne_over_time", args, vExpected)
 	}
 
 	f(-123, 12)
@@ -373,7 +373,7 @@ func TestRollupQuantileOverTime(t *testing.T) {
 		}}
 		var me metricsql.MetricExpr
 		args := []interface{}{phis, &metricsql.RollupExpr{Expr: &me}}
-		testRollupFunc(t, "quantile_over_time", args, &me, vExpected)
+		testRollupFunc(t, "quantile_over_time", args, vExpected)
 	}
 
 	f(-123, math.Inf(-1))
@@ -395,7 +395,7 @@ func TestRollupPredictLinear(t *testing.T) {
 		}}
 		var me metricsql.MetricExpr
 		args := []interface{}{&metricsql.RollupExpr{Expr: &me}, secs}
-		testRollupFunc(t, "predict_linear", args, &me, vExpected)
+		testRollupFunc(t, "predict_linear", args, vExpected)
 	}
 
 	f(0e-3, 65.07405077267295)
@@ -434,7 +434,7 @@ func TestRollupHoltWinters(t *testing.T) {
 		}}
 		var me metricsql.MetricExpr
 		args := []interface{}{&metricsql.RollupExpr{Expr: &me}, sfs, tfs}
-		testRollupFunc(t, "holt_winters", args, &me, vExpected)
+		testRollupFunc(t, "holt_winters", args, vExpected)
 	}
 
 	f(-1, 0.5, nan)
@@ -462,7 +462,7 @@ func TestRollupHoeffdingBoundLower(t *testing.T) {
 		}}
 		var me metricsql.MetricExpr
 		args := []interface{}{phis, &metricsql.RollupExpr{Expr: &me}}
-		testRollupFunc(t, "hoeffding_bound_lower", args, &me, vExpected)
+		testRollupFunc(t, "hoeffding_bound_lower", args, vExpected)
 	}
 
 	f(0.5, 28.21949401521037)
@@ -483,7 +483,7 @@ func TestRollupHoeffdingBoundUpper(t *testing.T) {
 		}}
 		var me metricsql.MetricExpr
 		args := []interface{}{phis, &metricsql.RollupExpr{Expr: &me}}
-		testRollupFunc(t, "hoeffding_bound_upper", args, &me, vExpected)
+		testRollupFunc(t, "hoeffding_bound_upper", args, vExpected)
 	}
 
 	f(0.5, 65.9471726514563)
@@ -500,7 +500,7 @@ func TestRollupNewRollupFuncSuccess(t *testing.T) {
 		t.Helper()
 		var me metricsql.MetricExpr
 		args := []interface{}{&metricsql.RollupExpr{Expr: &me}}
-		testRollupFunc(t, funcName, args, &me, vExpected)
+		testRollupFunc(t, funcName, args, vExpected)
 	}
 
 	f("default_rollup", 34)
diff --git a/lib/blockcache/blockcache_test.go b/lib/blockcache/blockcache_test.go
index 842516c10..fde5d0156 100644
--- a/lib/blockcache/blockcache_test.go
+++ b/lib/blockcache/blockcache_test.go
@@ -121,7 +121,7 @@ func TestCache(t *testing.T) {
 	}
 }
 
-func TestCacheConcurrentAccess(t *testing.T) {
+func TestCacheConcurrentAccess(_ *testing.T) {
 	const sizeMaxBytes = 16 * 1024 * 1024
 	getMaxSize := func() int {
 		return sizeMaxBytes
diff --git a/lib/bytesutil/internstring_test.go b/lib/bytesutil/internstring_test.go
index 8d5630967..0d352e78d 100644
--- a/lib/bytesutil/internstring_test.go
+++ b/lib/bytesutil/internstring_test.go
@@ -7,7 +7,7 @@ import (
 )
 
 func TestInternStringSerial(t *testing.T) {
-	if err := testInternString(t); err != nil {
+	if err := testInternString(); err != nil {
 		t.Fatalf("unexpected error: %s", err)
 	}
 }
@@ -17,7 +17,7 @@ func TestInternStringConcurrent(t *testing.T) {
 	resultCh := make(chan error, concurrency)
 	for i := 0; i < concurrency; i++ {
 		go func() {
-			resultCh <- testInternString(t)
+			resultCh <- testInternString()
 		}()
 	}
 	timer := time.NewTimer(5 * time.Second)
@@ -33,7 +33,7 @@ func TestInternStringConcurrent(t *testing.T) {
 	}
 }
 
-func testInternString(t *testing.T) error {
+func testInternString() error {
 	for i := 0; i < 1000; i++ {
 		s := fmt.Sprintf("foo_%d", i)
 		s1 := InternString(s)
diff --git a/lib/flagutil/duration_test.go b/lib/flagutil/duration_test.go
index bd26d47b8..b899c45a8 100644
--- a/lib/flagutil/duration_test.go
+++ b/lib/flagutil/duration_test.go
@@ -3,6 +3,7 @@ package flagutil
 import (
 	"strings"
 	"testing"
+	"time"
 )
 
 func TestDurationSetFailure(t *testing.T) {
@@ -59,3 +60,22 @@ func TestDurationSetSuccess(t *testing.T) {
 	f("1w", 7*24*3600*1000)
 	f("0.25y", 0.25*365*24*3600*1000)
 }
+
+func TestDurationDuration(t *testing.T) {
+	f := func(value string, expected time.Duration) {
+		t.Helper()
+		var d Duration
+		if err := d.Set(value); err != nil {
+			t.Fatalf("unexpected error in d.Set(%q): %s", value, err)
+		}
+		if d.Duration() != expected {
+			t.Fatalf("unexpected result; got %v; want %v", d.Duration().String(), expected.String())
+		}
+	}
+	f("0", 0)
+	f("1", 31*24*time.Hour)
+	f("1h", time.Hour)
+	f("1.5d", 1.5*24*time.Hour)
+	f("1w", 7*24*time.Hour)
+	f("0.25y", 0.25*365*24*time.Hour)
+}
diff --git a/lib/logstorage/storage_test.go b/lib/logstorage/storage_test.go
index e66db5717..193179bb1 100644
--- a/lib/logstorage/storage_test.go
+++ b/lib/logstorage/storage_test.go
@@ -7,7 +7,7 @@ import (
 	"github.com/VictoriaMetrics/VictoriaMetrics/lib/fs"
 )
 
-func TestStorageLifecycle(t *testing.T) {
+func TestStorageLifecycle(_ *testing.T) {
 	const path = "TestStorageLifecycle"
 
 	for i := 0; i < 3; i++ {
diff --git a/lib/lrucache/lrucache_test.go b/lib/lrucache/lrucache_test.go
index 160deb86e..a83f35cc8 100644
--- a/lib/lrucache/lrucache_test.go
+++ b/lib/lrucache/lrucache_test.go
@@ -85,7 +85,7 @@ func TestCache(t *testing.T) {
 	}
 }
 
-func TestCacheConcurrentAccess(t *testing.T) {
+func TestCacheConcurrentAccess(_ *testing.T) {
 	const sizeMaxBytes = 16 * 1024 * 1024
 	getMaxSize := func() int {
 		return sizeMaxBytes
diff --git a/lib/persistentqueue/fastqueue_test.go b/lib/persistentqueue/fastqueue_test.go
index 33274d3b1..0fd1fb847 100644
--- a/lib/persistentqueue/fastqueue_test.go
+++ b/lib/persistentqueue/fastqueue_test.go
@@ -7,7 +7,7 @@ import (
 	"time"
 )
 
-func TestFastQueueOpenClose(t *testing.T) {
+func TestFastQueueOpenClose(_ *testing.T) {
 	path := "fast-queue-open-close"
 	mustDeleteDir(path)
 	for i := 0; i < 10; i++ {
diff --git a/lib/promscrape/discoveryutils/utils_test.go b/lib/promscrape/discoveryutils/utils_test.go
index b63817b18..842281d11 100644
--- a/lib/promscrape/discoveryutils/utils_test.go
+++ b/lib/promscrape/discoveryutils/utils_test.go
@@ -63,8 +63,5 @@ func testSanitizeLabelName() error {
 	if err := f("foo", "foo"); err != nil {
 		return err
 	}
-	if err := f("foo-bar/baz", "foo_bar_baz"); err != nil {
-		return err
-	}
-	return nil
+	return f("foo-bar/baz", "foo_bar_baz")
 }
diff --git a/lib/protoparser/common/lines_reader_test.go b/lib/protoparser/common/lines_reader_test.go
index e37ac720c..b4bce3514 100644
--- a/lib/protoparser/common/lines_reader_test.go
+++ b/lib/protoparser/common/lines_reader_test.go
@@ -52,19 +52,19 @@ func TestReadLinesBlockFailure(t *testing.T) {
 
 type failureReader struct{}
 
-func (fr *failureReader) Read(p []byte) (int, error) {
+func (fr *failureReader) Read(_ []byte) (int, error) {
 	return 0, fmt.Errorf("some error")
 }
 
 type unexpectedEOF struct{}
 
-func (un unexpectedEOF) Read(p []byte) (int, error) {
+func (un unexpectedEOF) Read(_ []byte) (int, error) {
 	return 0, io.ErrUnexpectedEOF
 }
 
 type eofErr struct{}
 
-func (eo eofErr) Read(p []byte) (int, error) {
+func (eo eofErr) Read(_ []byte) (int, error) {
 	return 0, io.EOF
 }
 
diff --git a/lib/storage/block_stream_reader_test.go b/lib/storage/block_stream_reader_test.go
index 5a05449e4..f2aff233e 100644
--- a/lib/storage/block_stream_reader_test.go
+++ b/lib/storage/block_stream_reader_test.go
@@ -128,7 +128,7 @@ func testBlockStreamReaderReadRows(mp *inmemoryPart, rows []rawRow) error {
 func testBlocksStreamReader(t *testing.T, rows []rawRow, expectedBlocksCount int) {
 	t.Helper()
 
-	bsr := newTestBlockStreamReader(t, rows)
+	bsr := newTestBlockStreamReader(rows)
 	blocksCount := 0
 	rowsCount := 0
 	for bsr.NextBlock() {
@@ -151,7 +151,7 @@ func testBlocksStreamReader(t *testing.T, rows []rawRow, expectedBlocksCount int
 	}
 }
 
-func newTestBlockStreamReader(t *testing.T, rows []rawRow) *blockStreamReader {
+func newTestBlockStreamReader(rows []rawRow) *blockStreamReader {
 	var mp inmemoryPart
 	mp.InitFromRows(rows)
 	var bsr blockStreamReader
diff --git a/lib/storage/index_db_test.go b/lib/storage/index_db_test.go
index 074613f33..742592b95 100644
--- a/lib/storage/index_db_test.go
+++ b/lib/storage/index_db_test.go
@@ -508,7 +508,7 @@ func TestIndexDB(t *testing.T) {
 
 	t.Run("serial", func(t *testing.T) {
 		const path = "TestIndexDB-serial"
-		s := MustOpenStorage(path, maxRetentionMsecs, 0, 0)
+		s := MustOpenStorage(path, retentionMax, 0, 0)
 
 		db := s.idb()
 		mns, tsids, err := testIndexDBGetOrCreateTSIDByName(db, metricGroups)
@@ -521,7 +521,7 @@ func TestIndexDB(t *testing.T) {
 
 		// Re-open the storage and verify it works as expected.
 		s.MustClose()
-		s = MustOpenStorage(path, maxRetentionMsecs, 0, 0)
+		s = MustOpenStorage(path, retentionMax, 0, 0)
 
 		db = s.idb()
 		if err := testIndexDBCheckTSIDByName(db, mns, tsids, false); err != nil {
@@ -534,7 +534,7 @@ func TestIndexDB(t *testing.T) {
 
 	t.Run("concurrent", func(t *testing.T) {
 		const path = "TestIndexDB-concurrent"
-		s := MustOpenStorage(path, maxRetentionMsecs, 0, 0)
+		s := MustOpenStorage(path, retentionMax, 0, 0)
 		db := s.idb()
 
 		ch := make(chan error, 3)
@@ -1429,7 +1429,7 @@ func TestMatchTagFilters(t *testing.T) {
 func TestIndexDBRepopulateAfterRotation(t *testing.T) {
 	r := rand.New(rand.NewSource(1))
 	path := "TestIndexRepopulateAfterRotation"
-	s := MustOpenStorage(path, msecsPerMonth, 1e5, 1e5)
+	s := MustOpenStorage(path, retentionMonth, 1e5, 1e5)
 
 	db := s.idb()
 	if db.generation == 0 {
@@ -1516,7 +1516,7 @@ func TestIndexDBRepopulateAfterRotation(t *testing.T) {
 
 func TestSearchTSIDWithTimeRange(t *testing.T) {
 	const path = "TestSearchTSIDWithTimeRange"
-	s := MustOpenStorage(path, maxRetentionMsecs, 0, 0)
+	s := MustOpenStorage(path, retentionMax, 0, 0)
 	db := s.idb()
 
 	is := db.getIndexSearch(noDeadline)
@@ -1966,7 +1966,7 @@ func newTestStorage() *Storage {
 		metricNameCache:   workingsetcache.New(1234),
 		tsidCache:         workingsetcache.New(1234),
 		dateMetricIDCache: newDateMetricIDCache(),
-		retentionMsecs:    maxRetentionMsecs,
+		retentionMsecs:    retentionMax.Milliseconds(),
 	}
 	s.setDeletedMetricIDs(&uint64set.Set{})
 	return s
diff --git a/lib/storage/index_db_timing_test.go b/lib/storage/index_db_timing_test.go
index 0a2c48c19..d67bebaa7 100644
--- a/lib/storage/index_db_timing_test.go
+++ b/lib/storage/index_db_timing_test.go
@@ -40,7 +40,7 @@ func BenchmarkRegexpFilterMismatch(b *testing.B) {
 
 func BenchmarkIndexDBAddTSIDs(b *testing.B) {
 	const path = "BenchmarkIndexDBAddTSIDs"
-	s := MustOpenStorage(path, maxRetentionMsecs, 0, 0)
+	s := MustOpenStorage(path, retentionMax, 0, 0)
 	db := s.idb()
 
 	const recordsPerLoop = 1e3
@@ -94,7 +94,7 @@ func BenchmarkHeadPostingForMatchers(b *testing.B) {
 	// This benchmark is equivalent to https://github.com/prometheus/prometheus/blob/23c0299d85bfeb5d9b59e994861553a25ca578e5/tsdb/head_bench_test.go#L52
 	// See https://www.robustperception.io/evaluating-performance-and-correctness for more details.
 	const path = "BenchmarkHeadPostingForMatchers"
-	s := MustOpenStorage(path, maxRetentionMsecs, 0, 0)
+	s := MustOpenStorage(path, retentionMax, 0, 0)
 	db := s.idb()
 
 	// Fill the db with data as in https://github.com/prometheus/prometheus/blob/23c0299d85bfeb5d9b59e994861553a25ca578e5/tsdb/head_bench_test.go#L66
@@ -261,7 +261,7 @@ func BenchmarkHeadPostingForMatchers(b *testing.B) {
 
 func BenchmarkIndexDBGetTSIDs(b *testing.B) {
 	const path = "BenchmarkIndexDBGetTSIDs"
-	s := MustOpenStorage(path, maxRetentionMsecs, 0, 0)
+	s := MustOpenStorage(path, retentionMax, 0, 0)
 	db := s.idb()
 
 	const recordsPerLoop = 1000
diff --git a/lib/storage/merge_test.go b/lib/storage/merge_test.go
index 4017e6532..d34e5c0f0 100644
--- a/lib/storage/merge_test.go
+++ b/lib/storage/merge_test.go
@@ -14,7 +14,7 @@ func TestMergeBlockStreamsOneStreamOneRow(t *testing.T) {
 			PrecisionBits: defaultPrecisionBits,
 		},
 	}
-	bsr := newTestBlockStreamReader(t, rows)
+	bsr := newTestBlockStreamReader(rows)
 	bsrs := []*blockStreamReader{bsr}
 	testMergeBlockStreams(t, bsrs, 1, 1, rows[0].Timestamp, rows[0].Timestamp)
 }
@@ -38,7 +38,7 @@ func TestMergeBlockStreamsOneStreamOneBlockManyRows(t *testing.T) {
 			maxTimestamp = r.Timestamp
 		}
 	}
-	bsr := newTestBlockStreamReader(t, rows)
+	bsr := newTestBlockStreamReader(rows)
 	bsrs := []*blockStreamReader{bsr}
 	testMergeBlockStreams(t, bsrs, 1, maxRowsPerBlock, minTimestamp, maxTimestamp)
 }
@@ -65,7 +65,7 @@ func TestMergeBlockStreamsOneStreamManyBlocksOneRow(t *testing.T) {
 			maxTimestamp = r.Timestamp
 		}
 	}
-	bsr := newTestBlockStreamReader(t, rows)
+	bsr := newTestBlockStreamReader(rows)
 	bsrs := []*blockStreamReader{bsr}
 	testMergeBlockStreams(t, bsrs, blocksCount, blocksCount, minTimestamp, maxTimestamp)
 }
@@ -93,7 +93,7 @@ func TestMergeBlockStreamsOneStreamManyBlocksManyRows(t *testing.T) {
 			maxTimestamp = r.Timestamp
 		}
 	}
-	bsr := newTestBlockStreamReader(t, rows)
+	bsr := newTestBlockStreamReader(rows)
 	bsrs := []*blockStreamReader{bsr}
 	testMergeBlockStreams(t, bsrs, blocksCount, rowsCount, minTimestamp, maxTimestamp)
 }
@@ -107,8 +107,8 @@ func TestMergeBlockStreamsTwoStreamsOneBlockTwoRows(t *testing.T) {
 			PrecisionBits: defaultPrecisionBits,
 		},
 	}
-	bsr1 := newTestBlockStreamReader(t, rows)
-	bsr2 := newTestBlockStreamReader(t, rows)
+	bsr1 := newTestBlockStreamReader(rows)
+	bsr2 := newTestBlockStreamReader(rows)
 	bsrs := []*blockStreamReader{bsr1, bsr2}
 	testMergeBlockStreams(t, bsrs, 1, 2, rows[0].Timestamp, rows[0].Timestamp)
 
@@ -122,7 +122,7 @@ func TestMergeBlockStreamsTwoStreamsOneBlockTwoRows(t *testing.T) {
 			PrecisionBits: defaultPrecisionBits,
 		},
 	}
-	bsr1 = newTestBlockStreamReader(t, rows)
+	bsr1 = newTestBlockStreamReader(rows)
 	rows = []rawRow{
 		{
 			Timestamp:     minTimestamp,
@@ -130,7 +130,7 @@ func TestMergeBlockStreamsTwoStreamsOneBlockTwoRows(t *testing.T) {
 			PrecisionBits: defaultPrecisionBits,
 		},
 	}
-	bsr2 = newTestBlockStreamReader(t, rows)
+	bsr2 = newTestBlockStreamReader(rows)
 	bsrs = []*blockStreamReader{bsr1, bsr2}
 	testMergeBlockStreams(t, bsrs, 1, 2, minTimestamp, maxTimestamp)
 }
@@ -149,7 +149,7 @@ func TestMergeBlockStreamsTwoStreamsTwoBlocksOneRow(t *testing.T) {
 			PrecisionBits: defaultPrecisionBits,
 		},
 	}
-	bsr1 := newTestBlockStreamReader(t, rows)
+	bsr1 := newTestBlockStreamReader(rows)
 
 	rows = []rawRow{
 		{
@@ -161,7 +161,7 @@ func TestMergeBlockStreamsTwoStreamsTwoBlocksOneRow(t *testing.T) {
 			PrecisionBits: defaultPrecisionBits,
 		},
 	}
-	bsr2 := newTestBlockStreamReader(t, rows)
+	bsr2 := newTestBlockStreamReader(rows)
 
 	bsrs := []*blockStreamReader{bsr1, bsr2}
 	testMergeBlockStreams(t, bsrs, 2, 2, minTimestamp, maxTimestamp)
@@ -191,7 +191,7 @@ func TestMergeBlockStreamsTwoStreamsManyBlocksManyRows(t *testing.T) {
 			maxTimestamp = r.Timestamp
 		}
 	}
-	bsr1 := newTestBlockStreamReader(t, rows)
+	bsr1 := newTestBlockStreamReader(rows)
 
 	rows = rows[:0]
 	const rowsCount2 = 3281
@@ -208,7 +208,7 @@ func TestMergeBlockStreamsTwoStreamsManyBlocksManyRows(t *testing.T) {
 			maxTimestamp = r.Timestamp
 		}
 	}
-	bsr2 := newTestBlockStreamReader(t, rows)
+	bsr2 := newTestBlockStreamReader(rows)
 
 	bsrs := []*blockStreamReader{bsr1, bsr2}
 	testMergeBlockStreams(t, bsrs, blocksCount, rowsCount1+rowsCount2, minTimestamp, maxTimestamp)
@@ -235,7 +235,7 @@ func TestMergeBlockStreamsTwoStreamsBigOverlappingBlocks(t *testing.T) {
 			maxTimestamp = r.Timestamp
 		}
 	}
-	bsr1 := newTestBlockStreamReader(t, rows)
+	bsr1 := newTestBlockStreamReader(rows)
 
 	rows = rows[:0]
 	const rowsCount2 = maxRowsPerBlock + 2344
@@ -251,7 +251,7 @@ func TestMergeBlockStreamsTwoStreamsBigOverlappingBlocks(t *testing.T) {
 			maxTimestamp = r.Timestamp
 		}
 	}
-	bsr2 := newTestBlockStreamReader(t, rows)
+	bsr2 := newTestBlockStreamReader(rows)
 
 	bsrs := []*blockStreamReader{bsr1, bsr2}
 	testMergeBlockStreams(t, bsrs, 3, rowsCount1+rowsCount2, minTimestamp, maxTimestamp)
@@ -279,7 +279,7 @@ func TestMergeBlockStreamsTwoStreamsBigSequentialBlocks(t *testing.T) {
 		}
 	}
 	maxTimestampB1 := rows[len(rows)-1].Timestamp
-	bsr1 := newTestBlockStreamReader(t, rows)
+	bsr1 := newTestBlockStreamReader(rows)
 
 	rows = rows[:0]
 	const rowsCount2 = maxRowsPerBlock - 233
@@ -295,7 +295,7 @@ func TestMergeBlockStreamsTwoStreamsBigSequentialBlocks(t *testing.T) {
 			maxTimestamp = r.Timestamp
 		}
 	}
-	bsr2 := newTestBlockStreamReader(t, rows)
+	bsr2 := newTestBlockStreamReader(rows)
 
 	bsrs := []*blockStreamReader{bsr1, bsr2}
 	testMergeBlockStreams(t, bsrs, 3, rowsCount1+rowsCount2, minTimestamp, maxTimestamp)
@@ -329,7 +329,7 @@ func TestMergeBlockStreamsManyStreamsManyBlocksManyRows(t *testing.T) {
 				maxTimestamp = r.Timestamp
 			}
 		}
-		bsr := newTestBlockStreamReader(t, rows)
+		bsr := newTestBlockStreamReader(rows)
 		bsrs = append(bsrs, bsr)
 		rowsCount += rowsPerStream
 	}
@@ -363,7 +363,7 @@ func TestMergeForciblyStop(t *testing.T) {
 				maxTimestamp = r.Timestamp
 			}
 		}
-		bsr := newTestBlockStreamReader(t, rows)
+		bsr := newTestBlockStreamReader(rows)
 		bsrs = append(bsrs, bsr)
 	}
 
diff --git a/lib/storage/search_test.go b/lib/storage/search_test.go
index 7cd13703b..69bedac05 100644
--- a/lib/storage/search_test.go
+++ b/lib/storage/search_test.go
@@ -84,7 +84,6 @@ func TestSearch(t *testing.T) {
 	const rowsCount = 2e4
 	const rowsPerBlock = 1e3
 	const metricGroupsCount = rowsCount / 5
-	const accountsCount = 2
 
 	mrs := make([]MetricRow, rowsCount)
 	var mn MetricName
@@ -127,7 +126,7 @@ func TestSearch(t *testing.T) {
 	}
 
 	t.Run("serial", func(t *testing.T) {
-		if err := testSearchInternal(st, tr, mrs, accountsCount); err != nil {
+		if err := testSearchInternal(st, tr, mrs); err != nil {
 			t.Fatalf("unexpected error: %s", err)
 		}
 	})
@@ -136,7 +135,7 @@ func TestSearch(t *testing.T) {
 		ch := make(chan error, 3)
 		for i := 0; i < cap(ch); i++ {
 			go func() {
-				ch <- testSearchInternal(st, tr, mrs, accountsCount)
+				ch <- testSearchInternal(st, tr, mrs)
 			}()
 		}
 		var firstError error
@@ -156,7 +155,7 @@ func TestSearch(t *testing.T) {
 	})
 }
 
-func testSearchInternal(st *Storage, tr TimeRange, mrs []MetricRow, accountsCount int) error {
+func testSearchInternal(st *Storage, tr TimeRange, mrs []MetricRow) error {
 	var s Search
 	for i := 0; i < 10; i++ {
 		// Prepare TagFilters for search.
diff --git a/lib/storage/storage_test.go b/lib/storage/storage_test.go
index a098fc72a..d2bfe3c71 100644
--- a/lib/storage/storage_test.go
+++ b/lib/storage/storage_test.go
@@ -478,15 +478,15 @@ func TestStorageOpenClose(t *testing.T) {
 
 func TestStorageRandTimestamps(t *testing.T) {
 	path := "TestStorageRandTimestamps"
-	retentionMsecs := int64(10 * msecsPerMonth)
-	s := MustOpenStorage(path, retentionMsecs, 0, 0)
+	retention := 10 * retentionMonth
+	s := MustOpenStorage(path, retention, 0, 0)
 	t.Run("serial", func(t *testing.T) {
 		for i := 0; i < 3; i++ {
 			if err := testStorageRandTimestamps(s); err != nil {
 				t.Fatalf("error on iteration %d: %s", i, err)
 			}
 			s.MustClose()
-			s = MustOpenStorage(path, retentionMsecs, 0, 0)
+			s = MustOpenStorage(path, retention, 0, 0)
 		}
 	})
 	t.Run("concurrent", func(t *testing.T) {
@@ -936,8 +936,8 @@ func testStorageRegisterMetricNames(s *Storage) error {
 func TestStorageAddRowsSerial(t *testing.T) {
 	rng := rand.New(rand.NewSource(1))
 	path := "TestStorageAddRowsSerial"
-	retentionMsecs := int64(msecsPerMonth * 10)
-	s := MustOpenStorage(path, retentionMsecs, 1e5, 1e5)
+	retention := 10 * retentionMonth
+	s := MustOpenStorage(path, retention, 1e5, 1e5)
 	if err := testStorageAddRows(rng, s); err != nil {
 		t.Fatalf("unexpected error: %s", err)
 	}
@@ -949,8 +949,8 @@ func TestStorageAddRowsSerial(t *testing.T) {
 
 func TestStorageAddRowsConcurrent(t *testing.T) {
 	path := "TestStorageAddRowsConcurrent"
-	retentionMsecs := int64(msecsPerMonth * 10)
-	s := MustOpenStorage(path, retentionMsecs, 1e5, 1e5)
+	retention := 10 * retentionMonth
+	s := MustOpenStorage(path, retention, 1e5, 1e5)
 	ch := make(chan error, 3)
 	for i := 0; i < cap(ch); i++ {
 		go func(n int) {
@@ -1164,8 +1164,8 @@ func testStorageAddMetrics(s *Storage, workerNum int) error {
 func TestStorageDeleteStaleSnapshots(t *testing.T) {
 	rng := rand.New(rand.NewSource(1))
 	path := "TestStorageDeleteStaleSnapshots"
-	retentionMsecs := int64(msecsPerMonth * 10)
-	s := MustOpenStorage(path, retentionMsecs, 1e5, 1e5)
+	retention := 10 * retentionMonth
+	s := MustOpenStorage(path, retention, 1e5, 1e5)
 	const rowsPerAdd = 1e3
 	const addsCount = 10
 	maxTimestamp := timestampFromTime(time.Now())
diff --git a/lib/storage/table_test.go b/lib/storage/table_test.go
index c664a55fe..49b2881ee 100644
--- a/lib/storage/table_test.go
+++ b/lib/storage/table_test.go
@@ -7,7 +7,7 @@ import (
 
 func TestTableOpenClose(t *testing.T) {
 	const path = "TestTableOpenClose"
-	const retentionMsecs = 123 * msecsPerMonth
+	const retention = 123 * retentionMonth
 
 	if err := os.RemoveAll(path); err != nil {
 		t.Fatalf("cannot remove %q: %s", path, err)
@@ -18,7 +18,7 @@ func TestTableOpenClose(t *testing.T) {
 
 	// Create a new table
 	strg := newTestStorage()
-	strg.retentionMsecs = retentionMsecs
+	strg.retentionMsecs = retention.Milliseconds()
 	tb := mustOpenTable(path, strg)
 
 	// Close it