Repository: VictoriaMetrics/VictoriaMetrics
Pull request #4166 (2023-04-21 09:10:05)

Add s3StorageClass to s3remote
Adds a new flag to `vmbackup` which allows users to specify the StorageClass they wish to use for objects stored in AWS S3

Addresses: https://github.com/VictoriaMetrics/VictoriaMetrics/issues/4164

justcompile: @f41gh7 Have pushed a new commit which is now signed. Apologies as I thought it was already configured
f41gh7: > @f41gh7 Have pushed a new commit which is now signed. Apologies as I thought it was already configured

 It seems that you have to squash all commits and force push it. Github repo policy blocks PRs with unsigned commits.

Patch:
diff --git a/README.md b/README.md
index a2738ccbc..8524cbc42 100644
--- a/README.md
+++ b/README.md
@@ -572,7 +572,8 @@ The `/api/v1/export` endpoint should return the following response:
 ```
 
 Note that InfluxDB line protocol expects [timestamps in *nanoseconds* by default](https://docs.influxdata.com/influxdb/v1.7/write_protocols/line_protocol_tutorial/#timestamp),
-while VictoriaMetrics stores them with *milliseconds* precision.
+while VictoriaMetrics stores them with *milliseconds* precision. It is allowed to ingest timestamps with seconds,
+microseconds or nanoseconds precision - VictoriaMetrics will automatically convert them to milliseconds.
 
 Extra labels may be added to all the written time series by passing `extra_label=name=value` query args.
 For example, `/write?extra_label=foo=bar` would add `{foo="bar"}` label to all the ingested metrics.
diff --git a/app/vmalert/web.go b/app/vmalert/web.go
index 77a06aa0d..ec24a3edb 100644
--- a/app/vmalert/web.go
+++ b/app/vmalert/web.go
@@ -211,7 +211,7 @@ func (rh *requestHandler) groups() []APIGroup {
 	rh.m.groupsMu.RLock()
 	defer rh.m.groupsMu.RUnlock()
 
-	var groups []APIGroup
+	groups := make([]APIGroup, 0)
 	for _, g := range rh.m.groups {
 		groups = append(groups, g.toAPI())
 	}
@@ -276,6 +276,7 @@ func (rh *requestHandler) listAlerts() ([]byte, error) {
 	defer rh.m.groupsMu.RUnlock()
 
 	lr := listAlertsResponse{Status: "success"}
+	lr.Data.Alerts = make([]*APIAlert, 0)
 	for _, g := range rh.m.groups {
 		for _, r := range g.Rules {
 			a, ok := r.(*AlertingRule)
diff --git a/app/vmbackup/main.go b/app/vmbackup/main.go
index c4facc8e6..06c9e475d 100644
--- a/app/vmbackup/main.go
+++ b/app/vmbackup/main.go
@@ -155,7 +155,7 @@ See the docs at https://docs.victoriametrics.com/vmbackup.html .
 }
 
 func newSrcFS() (*fslocal.FS, error) {
-	snapshotPath := *storageDataPath + "/snapshots/" + *snapshotName
+	snapshotPath := filepath.Join(*storageDataPath, "snapshots", *snapshotName)
 
 	// Verify the snapshot exists.
 	f, err := os.Open(snapshotPath)
diff --git a/app/vmselect/main.go b/app/vmselect/main.go
index 0c05ec94f..71c2c00b1 100644
--- a/app/vmselect/main.go
+++ b/app/vmselect/main.go
@@ -115,6 +115,13 @@ func RequestHandler(w http.ResponseWriter, r *http.Request) bool {
 			timerpool.Put(t)
 			qt.Printf("wait in queue because -search.maxConcurrentRequests=%d concurrent requests are executed", *maxConcurrentRequests)
 			defer func() { <-concurrencyLimitCh }()
+		case <-r.Context().Done():
+			timerpool.Put(t)
+			remoteAddr := httpserver.GetQuotedRemoteAddr(r)
+			requestURI := httpserver.GetRequestURI(r)
+			logger.Infof("client has cancelled the request after %.3f seconds: remoteAddr=%s, requestURI: %q",
+				d.Seconds(), remoteAddr, requestURI)
+			return true
 		case <-t.C:
 			timerpool.Put(t)
 			concurrencyLimitTimeout.Inc()
diff --git a/deployment/docker/alerts-health.yml b/deployment/docker/alerts-health.yml
index 551e5d1df..63f66c8c2 100644
--- a/deployment/docker/alerts-health.yml
+++ b/deployment/docker/alerts-health.yml
@@ -6,7 +6,7 @@ groups:
     # note the `job` filter and update accordingly to your setup
     rules:
       - alert: TooManyRestarts
-        expr: changes(process_start_time_seconds{job=~"victoriametrics.*|vmselect.*|vminsert.*|vmstorage.*|vmagent.*|vmalert.*"}[15m]) > 2
+        expr: changes(process_start_time_seconds{job=~"victoriametrics.*|vmselect.*|vminsert.*|vmstorage.*|vmagent.*|vmalert.*|vmsingle.*"}[15m]) > 2
         labels:
           severity: critical
         annotations:
@@ -15,7 +15,7 @@ groups:
             It might be crashlooping."
 
       - alert: ServiceDown
-        expr: up{job=~"victoriametrics.*|vmselect.*|vminsert.*|vmstorage.*|vmagent.*|vmalert.*"} == 0
+        expr: up{job=~"victoriametrics.*|vmselect.*|vminsert.*|vmstorage.*|vmagent.*|vmalert.*|vmsingle.*"} == 0
         for: 2m
         labels:
           severity: critical
diff --git a/docs/Articles.md b/docs/Articles.md
index deed6888a..abf00eb64 100644
--- a/docs/Articles.md
+++ b/docs/Articles.md
@@ -1,5 +1,13 @@
 ---
 sort: 17
+weight: 17
+title: Articles
+menu:
+  docs:
+    parent: "victoriametrics"
+    weight: 17
+aliases:
+- /Articles.html
 ---
 
 # Articles
diff --git a/docs/BestPractices.md b/docs/BestPractices.md
index 066c664ac..e911778be 100644
--- a/docs/BestPractices.md
+++ b/docs/BestPractices.md
@@ -1,5 +1,13 @@
 ---
 sort: 20
+weight: 20
+title: VictoriaMetrics best practices
+menu:
+  docs:
+    parent: "victoriametrics"
+    weight: 20
+aliases:
+- /BestPractices.html
 ---
 
 # VictoriaMetrics best practices
diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md
index 8e1a3d58e..eb88fcff6 100644
--- a/docs/CHANGELOG.md
+++ b/docs/CHANGELOG.md
@@ -1,5 +1,13 @@
 ---
 sort: 16
+weight: 16
+title: CHANGELOG
+menu:
+  docs:
+    parent: "victoriametrics"
+    weight: 16
+aliases:
+- /CHANGELOG.html
 ---
 
 # CHANGELOG
@@ -18,7 +26,6 @@ The following tip changes can be tested by building VictoriaMetrics components f
 
 * SECURITY: serve `/robots.txt` content to disallow indexing of the exposed instances by search engines. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/4128) for details.
 
-* FEATURE: open source [Graphite Render API](https://docs.victoriametrics.com/#graphite-render-api-usage). This API allows using VictoriaMetrics as a drop-in replacement for Graphite at both data ingestion and querying sides and reducing infrastructure costs by up to 10x comparing to Graphite. See [this case study](https://docs.victoriametrics.com/CaseStudies.html#grammarly) as an example.
 * FEATURE: [vmbackup](https://docs.victoriametrics.com/vmbackup.html): store backup creation and completion time in `backup_complete.ignore` file of backup contents. This is useful to determine point in time when backup was created and completed.
 * FEATURE: [vmbackupmanager](https://docs.victoriametrics.com/vmbackupmanager.html): add `created_at` field to the output of `/api/v1/backups` API and `vmbackupmanager backup list` command. See this [doc](https://docs.victoriametrics.com/vmbackupmanager.html#api-methods) for data format details.
 * FEATURE: deprecate `-bigMergeConcurrency` command-line flag, since improper configuration for this flag frequently led to uncontrolled growth of unmerged parts, which, in turn, could lead to queries slowdown and increased CPU usage. The concurrency for [background merges](https://docs.victoriametrics.com/#storage) can be controlled via `-smallMergeConcurrency` command-line flag, though it isn't recommended to do in general case.
@@ -35,11 +42,15 @@ The following tip changes can be tested by building VictoriaMetrics components f
 
 * BUGFIX: reduce the probability of sudden increase in the number of small parts on systems with small number of CPU cores.
 * BUGFIX: [vmctl](https://docs.victoriametrics.com/vmctl.html): fix performance issue when migrating data from VictoriaMetrics according to [these docs](https://docs.victoriametrics.com/vmctl.html#migrating-data-from-victoriametrics). Add the ability to speed up the data migration via `--vm-native-disable-retries` command-line flag. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/4092).
+* BUGFIX: [vmbackup](https://docs.victoriametrics.com/vmbackup.html): fixes compatibility with Windows OS. For details see [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/70)
+* BUGFIX: do not panic at windows during snapshot deletion. Note, at windows process restart required for complete snapshot delete. See [this comment](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/70#issuecomment-1491529183) for details.
 * BUGFIX: [MetricsQL](https://docs.victoriametrics.com/MetricsQL.html): fix a panic when the duration in the query contains uppercase `M` suffix. Such a suffix isn't allowed to use in durations, since it clashes with `a million` suffix, e.g. it isn't clear whether `rate(metric[5M])` means rate over 5 minutes, 5 months or 5 million seconds. See [this](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3589) and [this](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/4120) issues.
 * BUGFIX: [vmalert](https://docs.victoriametrics.com/vmalert.html): retry failed read request on the closed connection one more time. This improves rules execution reliability when connection between vmalert and datasource closes unexpectedly.
 * BUGFIX: [vmui](https://docs.victoriametrics.com/#vmui): fix the display of the tenant selector. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/4160).
 * BUGFIX: [vmui](https://docs.victoriametrics.com/#vmui): fix issue where vmui would freeze when adding a query that returned regular rows alongside a heatmap query.
 * BUGFIX: [vmalert](https://docs.victoriametrics.com/vmalert.html): properly display an error when using `query` function for templating value of `-external.alert.source` flag. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/4181).
+* BUGFIX: [vmalert](https://docs.victoriametrics.com/vmalert.html): properly return empty slices instead of nil for `/api/v1/rules` and `/api/v1/alerts` API handlers. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/4221).
+* BUGFIX: [vmagent](https://docs.victoriametrics.com/vmagent.html): add `__meta_kubernetes_endpoints_name` label for all ports discovered from endpoint. Previously, ports not matched by `Service` did not have this label. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/4154) for details.
 
 ## [v1.90.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.90.0)
 
diff --git a/docs/CaseStudies.md b/docs/CaseStudies.md
index a42926ec7..db7b74558 100644
--- a/docs/CaseStudies.md
+++ b/docs/CaseStudies.md
@@ -1,5 +1,13 @@
 ---
 sort: 12
+weight: 12
+title: Case studies and talks
+menu:
+  docs:
+    parent: "victoriametrics"
+    weight: 12
+aliases:
+- /CaseStudies.html
 ---
 
 # Case studies and talks
diff --git a/docs/Cluster-VictoriaMetrics.md b/docs/Cluster-VictoriaMetrics.md
index bf722d8fd..76f2be147 100644
--- a/docs/Cluster-VictoriaMetrics.md
+++ b/docs/Cluster-VictoriaMetrics.md
@@ -1,5 +1,13 @@
 ---
 sort: 2
+weight: 2
+title: Cluster version
+menu:
+  docs:
+    parent: "victoriametrics"
+    weight: 2
+aliases:
+  - /Cluster-VictoriaMetrics.html
 ---
 
 # Cluster version
diff --git a/docs/ExtendedPromQL.md b/docs/ExtendedPromQL.md
index cb0e106cb..80208247a 100644
--- a/docs/ExtendedPromQL.md
+++ b/docs/ExtendedPromQL.md
@@ -1,5 +1,6 @@
 ---
 sort: 100
+weight: 100
 ---
 
 # MetricsQL old page
diff --git a/docs/FAQ.md b/docs/FAQ.md
index 520e533e6..a3c70cfae 100644
--- a/docs/FAQ.md
+++ b/docs/FAQ.md
@@ -1,5 +1,13 @@
 ---
 sort: 15
+weight: 15
+title: FAQ
+menu:
+  docs:
+    parent: "victoriametrics"
+    weight: 15
+aliases:
+- /FAQ.html
 ---
 
 # FAQ
diff --git a/docs/MetricsQL.md b/docs/MetricsQL.md
index 0e972be8c..0647fa7c7 100644
--- a/docs/MetricsQL.md
+++ b/docs/MetricsQL.md
@@ -1,5 +1,14 @@
 ---
 sort: 14
+weight: 14
+title: MetricsQL
+menu:
+  docs:
+    parent: "victoriametrics"
+    weight: 14
+aliases:
+- /ExtendedPromQL.html
+- /MetricsQL.html
 ---
 
 # MetricsQL
diff --git a/docs/PerTenantStatistic.md b/docs/PerTenantStatistic.md
index deb50c5dd..9b279d40d 100644
--- a/docs/PerTenantStatistic.md
+++ b/docs/PerTenantStatistic.md
@@ -1,5 +1,13 @@
 ---
 sort: 19
+weight: 19
+title: VictoriaMetrics Cluster Per Tenant Statistic
+menu:
+  docs:
+    parent: "victoriametrics"
+    weight: 19
+aliases:
+- /PerTenantStatistic.html
 ---
 
 # VictoriaMetrics Cluster Per Tenant Statistic
diff --git a/docs/Quick-Start.md b/docs/Quick-Start.md
index ffef573f4..7c610ece7 100644
--- a/docs/Quick-Start.md
+++ b/docs/Quick-Start.md
@@ -1,5 +1,13 @@
 ---
 sort: 13
+weight: 13
+title: Quick start
+menu:
+  docs:
+    parent: "victoriametrics"
+    weight: 13
+aliases:
+- /Quick-Start.html
 ---
 
 # Quick start
diff --git a/docs/README.md b/docs/README.md
index e83b65020..5ca518a5f 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -1,4 +1,3 @@
-
 # VictoriaMetrics
 
 [![Latest Release](https://img.shields.io/github/release/VictoriaMetrics/VictoriaMetrics.svg?style=flat-square)](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/latest)
@@ -573,7 +572,8 @@ The `/api/v1/export` endpoint should return the following response:
 ```
 
 Note that InfluxDB line protocol expects [timestamps in *nanoseconds* by default](https://docs.influxdata.com/influxdb/v1.7/write_protocols/line_protocol_tutorial/#timestamp),
-while VictoriaMetrics stores them with *milliseconds* precision.
+while VictoriaMetrics stores them with *milliseconds* precision. It is allowed to ingest timestamps with seconds,
+microseconds or nanoseconds precision - VictoriaMetrics will automatically convert them to milliseconds.
 
 Extra labels may be added to all the written time series by passing `extra_label=name=value` query args.
 For example, `/write?extra_label=foo=bar` would add `{foo="bar"}` label to all the ingested metrics.
diff --git a/docs/Release-Guide.md b/docs/Release-Guide.md
index 07860788c..dd1c99828 100644
--- a/docs/Release-Guide.md
+++ b/docs/Release-Guide.md
@@ -1,5 +1,13 @@
 ---
 sort: 18
+weight: 18
+title: Release process guidance
+menu:
+  docs:
+    parent: "victoriametrics"
+    weight: 18
+aliases:
+- /Release-Guide.html
 ---
 
 # Release process guidance
diff --git a/docs/Single-server-VictoriaMetrics.md b/docs/Single-server-VictoriaMetrics.md
index 6c062c948..ddd83641b 100644
--- a/docs/Single-server-VictoriaMetrics.md
+++ b/docs/Single-server-VictoriaMetrics.md
@@ -576,7 +576,8 @@ The `/api/v1/export` endpoint should return the following response:
 ```
 
 Note that InfluxDB line protocol expects [timestamps in *nanoseconds* by default](https://docs.influxdata.com/influxdb/v1.7/write_protocols/line_protocol_tutorial/#timestamp),
-while VictoriaMetrics stores them with *milliseconds* precision.
+while VictoriaMetrics stores them with *milliseconds* precision. It is allowed to ingest timestamps with seconds,
+microseconds or nanoseconds precision - VictoriaMetrics will automatically convert them to milliseconds.
 
 Extra labels may be added to all the written time series by passing `extra_label=name=value` query args.
 For example, `/write?extra_label=foo=bar` would add `{foo="bar"}` label to all the ingested metrics.
diff --git a/docs/Troubleshooting.md b/docs/Troubleshooting.md
index 695ba48d2..ec5dce237 100644
--- a/docs/Troubleshooting.md
+++ b/docs/Troubleshooting.md
@@ -1,5 +1,13 @@
 ---
 sort: 23
+weight: 23
+title: Troubleshooting
+menu:
+  docs:
+    parent: "victoriametrics"
+    weight: 23
+aliases:
+- /Troubleshooting.html
 ---
 
 # Troubleshooting
diff --git a/docs/enterprise.md b/docs/enterprise.md
index cfadb77f1..1ee1c014c 100644
--- a/docs/enterprise.md
+++ b/docs/enterprise.md
@@ -1,5 +1,13 @@
 ---
 sort: 99
+weight: 99
+title: VictoriaMetrics Enterprise
+menu:
+  docs:
+    parent: "victoriametrics"
+    weight: 99
+aliases:
+- /enterprise.html
 ---
 
 # VictoriaMetrics enterprise
diff --git a/docs/guides/README.md b/docs/guides/README.md
index 64925ad5d..2f0d3a832 100644
--- a/docs/guides/README.md
+++ b/docs/guides/README.md
@@ -1,5 +1,11 @@
 ---
 sort: 26
+weight: 01
+disableToc: true
+menu:
+  docs:
+    parent: "guides"
+    weight: 01
 ---
 
 # Guides
diff --git a/docs/guides/getting-started-with-vm-operator.md b/docs/guides/getting-started-with-vm-operator.md
index 57d2b7c6a..b5926b2da 100644
--- a/docs/guides/getting-started-with-vm-operator.md
+++ b/docs/guides/getting-started-with-vm-operator.md
@@ -1,3 +1,13 @@
+---
+weight: 4
+title: Getting started with VM Operator
+menu:
+  docs:
+    parent: "guides"
+    weight: 4
+aliases:
+- /guides/getting-started-with-vm-operator.html
+---
 # Getting started with VM Operator
 
 **The guide covers:**
diff --git a/docs/guides/grafana-vmgateway-openid-configuration.md b/docs/guides/grafana-vmgateway-openid-configuration.md
index a8f462643..5e505b779 100644
--- a/docs/guides/grafana-vmgateway-openid-configuration.md
+++ b/docs/guides/grafana-vmgateway-openid-configuration.md
@@ -1,3 +1,13 @@
+---
+weight: 5
+title: How to configure vmgateway for multi-tenant access using Grafana and OpenID Connect
+menu:
+  docs:
+    parent: "guides"
+    weight: 5
+aliases:
+- /guides/grafana-vmgateway-openid-configuration.html
+---
 # How to configure vmgateway for multi-tenant access using Grafana and OpenID Connect
 
 Using [Grafana](https://grafana.com/) with [vmgateway](https://docs.victoriametrics.com/vmgateway.html) is a great way to provide [multi-tenant](https://docs.victoriametrics.com/Cluster-VictoriaMetrics.html#multitenancy) access to your metrics.
diff --git a/docs/guides/guide-delete-or-replace-metrics.md b/docs/guides/guide-delete-or-replace-metrics.md
index edce40e58..544c42dc9 100644
--- a/docs/guides/guide-delete-or-replace-metrics.md
+++ b/docs/guides/guide-delete-or-replace-metrics.md
@@ -1,3 +1,13 @@
+---
+weight: 7
+title: How to delete or replace metrics in VictoriaMetrics
+menu:
+  docs:
+    parent: "guides"
+    weight: 7
+aliases:
+- /guides/guide-delete-or-replace-metrics.html
+---
 # How to delete or replace metrics in VictoriaMetrics 
 
 Data deletion is an operation people expect a database to have. [VictoriaMetrics](https://victoriametrics.com) supports 
diff --git a/docs/guides/guide-vmanomaly-vmalert.md b/docs/guides/guide-vmanomaly-vmalert.md
index a9f443fc4..704f7846a 100644
--- a/docs/guides/guide-vmanomaly-vmalert.md
+++ b/docs/guides/guide-vmanomaly-vmalert.md
@@ -1,3 +1,13 @@
+---
+weight: 6
+title: Getting started with vmanomaly
+menu:
+  docs:
+    parent: "guides"
+    weight: 6
+aliases:
+- /guides/guide-vmanomaly-vmalert.html
+---
 # Getting started with vmanomaly
 
 **Prerequisites**
diff --git a/docs/guides/guide-vmcluster-multiple-retention-setup.md b/docs/guides/guide-vmcluster-multiple-retention-setup.md
index cf3ca24ed..841453a57 100644
--- a/docs/guides/guide-vmcluster-multiple-retention-setup.md
+++ b/docs/guides/guide-vmcluster-multiple-retention-setup.md
@@ -1,3 +1,13 @@
+---
+weight: 10
+title: Multi Retention Setup within VictoriaMetrics Cluster
+menu:
+  docs:
+    parent: "guides"
+    weight: 10
+aliases:
+- /guides/guide-vmcluster-multiple-retention-setup.html
+---
 # Multi Retention Setup within VictoriaMetrics Cluster
 
 
diff --git a/docs/guides/k8s-ha-monitoring-via-vm-cluster.md b/docs/guides/k8s-ha-monitoring-via-vm-cluster.md
index 9d1f98a67..aac293af8 100644
--- a/docs/guides/k8s-ha-monitoring-via-vm-cluster.md
+++ b/docs/guides/k8s-ha-monitoring-via-vm-cluster.md
@@ -1,3 +1,13 @@
+---
+weight: 9
+title: HA monitoring setup in Kubernetes via VictoriaMetrics Cluster
+menu:
+  docs:
+    parent: "guides"
+    weight: 9
+aliases:
+- /guides/k8s-ha-monitoring-via-vm-cluster.html
+---
 # HA monitoring setup in Kubernetes via VictoriaMetrics Cluster
 
 
diff --git a/docs/guides/k8s-monitoring-via-vm-cluster.md b/docs/guides/k8s-monitoring-via-vm-cluster.md
index 4cbee6c0d..b8370b927 100644
--- a/docs/guides/k8s-monitoring-via-vm-cluster.md
+++ b/docs/guides/k8s-monitoring-via-vm-cluster.md
@@ -1,3 +1,13 @@
+---
+weight: 3
+title: Kubernetes monitoring with VictoriaMetrics Cluster
+menu:
+  docs:
+    parent: "guides"
+    weight: 3
+aliases:
+- /guides/k8s-monitoring-via-vm-cluster.html
+---
 # Kubernetes monitoring with VictoriaMetrics Cluster
 
 
diff --git a/docs/guides/k8s-monitoring-via-vm-single.md b/docs/guides/k8s-monitoring-via-vm-single.md
index 166c6700b..8be446b7a 100644
--- a/docs/guides/k8s-monitoring-via-vm-single.md
+++ b/docs/guides/k8s-monitoring-via-vm-single.md
@@ -1,3 +1,13 @@
+---
+weight: 2
+title: Kubernetes monitoring via VictoriaMetrics Single
+menu:
+  docs:
+    parent: "guides"
+    weight: 2
+aliases:
+- /guides/k8s-monitoring-via-vm-single.html
+---
 # Kubernetes monitoring via VictoriaMetrics Single
 
 
diff --git a/docs/guides/migrate-from-influx.md b/docs/guides/migrate-from-influx.md
index bd998675a..2deaa9aef 100644
--- a/docs/guides/migrate-from-influx.md
+++ b/docs/guides/migrate-from-influx.md
@@ -1,3 +1,13 @@
+---
+title: Migrate from InfluxDB to VictoriaMetrics
+weight: 8
+menu:
+  docs:
+    parent: "guides"
+    weight: 8
+aliases:
+- /guides/migrate-from-influx.html
+---
 # Migrate from InfluxDB to VictoriaMetrics
 
 InfluxDB is a well-known time series database built for
diff --git a/docs/guides/multi-regional-setup-dedicated-regions.md b/docs/guides/multi-regional-setup-dedicated-regions.md
index 669e14d88..100982ebc 100644
--- a/docs/guides/multi-regional-setup-dedicated-regions.md
+++ b/docs/guides/multi-regional-setup-dedicated-regions.md
@@ -1,3 +1,13 @@
+---
+weight: 11
+title: Multi-regional setup with VictoriaMetrics: Dedicated regions for monitoring
+menu:
+  docs:
+    parent: "guides"
+    weight: 11
+aliases:
+- /guides/multi-regional-setup-dedicated-regions.html
+---
 # Multi-regional setup with VictoriaMetrics: Dedicated regions for monitoring
 
 ### Scenario
diff --git a/docs/guides/understand-your-setup-size.md b/docs/guides/understand-your-setup-size.md
index e428f6859..e3e67c530 100644
--- a/docs/guides/understand-your-setup-size.md
+++ b/docs/guides/understand-your-setup-size.md
@@ -1,3 +1,13 @@
+---
+weight: 9
+title: Understand Your Setup Size
+menu:
+  docs:
+    parent: "guides"
+    weight: 9
+aliases:
+- /guides/understand-your-setup-size.html
+---
 # Understand Your Setup Size
 
 The docs provide a simple and high-level overview of Ingestion Rate, Active Time Series, and Query per Second. These terms are a part of capacity planning ([Single-Node](https://docs.victoriametrics.com/Single-server-VictoriaMetrics.html#capacity-planning), [Cluster](https://docs.victoriametrics.com/Cluster-VictoriaMetrics.html#capacity-planning)) and [Managed VictoriaMetrics](https://docs.victoriametrics.com/managed_victoriametrics/) pricing.
diff --git a/docs/keyConcepts.md b/docs/keyConcepts.md
index d4d13aabc..ae4f124fe 100644
--- a/docs/keyConcepts.md
+++ b/docs/keyConcepts.md
@@ -1,5 +1,13 @@
 ---
 sort: 22
+weight: 22
+title: Key concepts
+menu:
+  docs:
+    parent: "victoriametrics"
+    weight: 22
+aliases:
+- /keyConcepts.html
 ---
 
 # Key concepts
diff --git a/docs/managed-victoriametrics/README.md b/docs/managed-victoriametrics/README.md
index 26dbf11bc..0f1aa62b3 100644
--- a/docs/managed-victoriametrics/README.md
+++ b/docs/managed-victoriametrics/README.md
@@ -1,5 +1,8 @@
 ---
 sort: 28
+title: Managed VictoriaMetrics
+weight: 01
+disableToc: true
 ---
 
 # Managed VictoriaMetrics
diff --git a/docs/managed-victoriametrics/alerting-vmalert-managed-victoria-metrics.md b/docs/managed-victoriametrics/alerting-vmalert-managed-victoria-metrics.md
index c05fc1b9e..a2240828a 100644
--- a/docs/managed-victoriametrics/alerting-vmalert-managed-victoria-metrics.md
+++ b/docs/managed-victoriametrics/alerting-vmalert-managed-victoria-metrics.md
@@ -6,6 +6,8 @@ menu:
   docs:
     parent: "managed"
     weight: 4
+aliases:
+- /managed-victoriametrics/alerting-vmalert-managed-victoria-metrics.html
 ---
 # Alerting with vmalert and Managed VictoriaMetrics
 
diff --git a/docs/managed-victoriametrics/how-to-monitor-k8s.md b/docs/managed-victoriametrics/how-to-monitor-k8s.md
index 11fa37a4d..0254e1333 100644
--- a/docs/managed-victoriametrics/how-to-monitor-k8s.md
+++ b/docs/managed-victoriametrics/how-to-monitor-k8s.md
@@ -6,6 +6,8 @@ menu:
   docs:
     parent: "managed"
     weight: 3
+aliases:
+- /managed-victoriametrics/how-to-monitor-k8s.html
 ---
 # Kubernetes Monitoring with Managed VictoriaMetrics
 
diff --git a/docs/managed-victoriametrics/overview.md b/docs/managed-victoriametrics/overview.md
index 101324896..23163dd54 100644
--- a/docs/managed-victoriametrics/overview.md
+++ b/docs/managed-victoriametrics/overview.md
@@ -6,6 +6,9 @@ menu:
   docs:
     parent: "managed"
     weight: 1
+aliases:
+- /managed-victoriametrics/overview.html
+- 
 ---
 
 # Overview of Managed VictoriaMetrics 
diff --git a/docs/managed-victoriametrics/quickstart.md b/docs/managed-victoriametrics/quickstart.md
index e3d0f213e..7c72c411a 100644
--- a/docs/managed-victoriametrics/quickstart.md
+++ b/docs/managed-victoriametrics/quickstart.md
@@ -6,6 +6,8 @@ menu:
   docs:
     parent: "managed"
     weight: 2
+aliases:
+- /managed-victoriametrics/quickstart.html
 ---
 # Quick Start in Managed VictoriaMetrics
 
diff --git a/docs/managed-victoriametrics/setup-notifications.md b/docs/managed-victoriametrics/setup-notifications.md
index 8560af6cb..bcb8adec2 100644
--- a/docs/managed-victoriametrics/setup-notifications.md
+++ b/docs/managed-victoriametrics/setup-notifications.md
@@ -6,6 +6,8 @@ menu:
   docs:
     parent: "managed"
     weight: 6
+aliases:
+- /managed-victoriametrics/setup-notifications.html
 ---
 # Notifications in Managed VictoriaMetrics
 
diff --git a/docs/managed-victoriametrics/user-managment.md b/docs/managed-victoriametrics/user-managment.md
index c4001e0d0..2800f0468 100644
--- a/docs/managed-victoriametrics/user-managment.md
+++ b/docs/managed-victoriametrics/user-managment.md
@@ -6,6 +6,8 @@ menu:
   docs:
     parent: "managed"
     weight: 5
+aliases:
+- /managed-victoriametrics/user-managment.html
 ---
 # User Management in Managed VictoriaMetrics 
 
diff --git a/docs/relabeling.md b/docs/relabeling.md
index a3832f875..b5a513888 100644
--- a/docs/relabeling.md
+++ b/docs/relabeling.md
@@ -1,5 +1,13 @@
 ---
 sort: 25
+weight: 25
+title: Relabeling cookbook
+menu:
+  docs:
+    parent: "victoriametrics"
+    weight: 25
+aliases:
+- /relabeling.html
 ---
 
 # Relabeling cookbook
diff --git a/docs/sd_configs.md b/docs/sd_configs.md
index a535169d1..cc4250706 100644
--- a/docs/sd_configs.md
+++ b/docs/sd_configs.md
@@ -1,5 +1,13 @@
 ---
 sort: 24
+weight: 24
+title: Prometheus service discovery
+menu:
+  docs:
+    parent: "victoriametrics"
+    weight: 24
+aliases:
+- /sd_configs.html
 ---
 
 # Prometheus service discovery
diff --git a/docs/stream-aggregation.md b/docs/stream-aggregation.md
index 6067b5d7b..2ccb69508 100644
--- a/docs/stream-aggregation.md
+++ b/docs/stream-aggregation.md
@@ -1,5 +1,13 @@
 ---
 sort: 98
+weight: 98
+title: streaming aggregation
+menu:
+  docs:
+    parent: "victoriametrics"
+    weight: 98
+aliases:
+- /stream-aggregation.html
 ---
 
 # streaming aggregation
diff --git a/docs/url-examples.md b/docs/url-examples.md
index 186dab381..ab35d978d 100644
--- a/docs/url-examples.md
+++ b/docs/url-examples.md
@@ -1,5 +1,11 @@
 ---
 sort: 21
+weight: 21
+title: VictoriaMetrics API examples
+menu:
+  docs:
+    parent: "victoriametrics"
+    weight: 21
 ---
 
 # VictoriaMetrics API examples
diff --git a/docs/vmanomaly.md b/docs/vmanomaly.md
index 02a246b76..3472ebc85 100644
--- a/docs/vmanomaly.md
+++ b/docs/vmanomaly.md
@@ -1,5 +1,13 @@
 ---
 sort: 11
+weight: 11
+title: vmanomaly
+menu:
+  docs:
+    parent: "victoriametrics"
+    weight: 11
+aliases:
+- /vmanomaly.html
 ---
 
 # vmanomaly
diff --git a/lib/backup/actions/backup.go b/lib/backup/actions/backup.go
index f01d5b127..5c6c80df9 100644
--- a/lib/backup/actions/backup.go
+++ b/lib/backup/actions/backup.go
@@ -4,7 +4,7 @@ import (
 	"encoding/json"
 	"fmt"
 	"io"
-	"strings"
+	"path/filepath"
 	"sync/atomic"
 	"time"
 
@@ -83,12 +83,7 @@ func (b *Backup) Run() error {
 }
 
 func storeMetadata(src *fslocal.FS, dst common.RemoteFS) error {
-	last := strings.LastIndex(src.Dir, "/")
-	if last < 0 {
-		return fmt.Errorf("cannot decode snapshot location %q", src.Dir)
-	}
-
-	snapshotName := src.Dir[last+1:]
+	snapshotName := filepath.Base(src.Dir)
 	snapshotTime, err := snapshot.Time(snapshotName)
 	if err != nil {
 		return fmt.Errorf("cannot decode snapshot name %q: %w", snapshotName, err)
diff --git a/lib/backup/actions/util.go b/lib/backup/actions/util.go
index a49cd9c53..d82c1a3b0 100644
--- a/lib/backup/actions/util.go
+++ b/lib/backup/actions/util.go
@@ -3,6 +3,7 @@ package actions
 import (
 	"flag"
 	"fmt"
+	"path/filepath"
 	"strings"
 	"sync"
 	"time"
@@ -23,6 +24,9 @@ var (
 		"or if both not set, DefaultSharedConfigProfile is used")
 	customS3Endpoint = flag.String("customS3Endpoint", "", "Custom S3 endpoint for use with S3-compatible storages (e.g. MinIO). S3 is used if not set")
 	s3ForcePathStyle = flag.Bool("s3ForcePathStyle", true, "Prefixing endpoint with bucket name when set false, true by default.")
+	s3StorageClass   = flag.String("s3StorageClass", "", "The Storage Class applied to objects uploaded to AWS S3. Supported values are: GLACIER, "+
+		"DEEP_ARCHIVE, GLACIER_IR, INTELLIGENT_TIERING, ONEZONE_IA, OUTPOSTS, REDUCED_REDUNDANCY, STANDARD, STANDARD_IA.\n"+
+		"See https://docs.aws.amazon.com/AmazonS3/latest/userguide/storage-class-intro.html/")
 )
 
 func runParallel(concurrency int, parts []common.Part, f func(p common.Part) error, progress func(elapsed time.Duration)) error {
@@ -190,7 +194,7 @@ func NewRemoteFS(path string) (common.RemoteFS, error) {
 	dir := path[n+len("://"):]
 	switch scheme {
 	case "fs":
-		if !strings.HasPrefix(dir, "/") {
+		if !filepath.IsAbs(dir) {
 			return nil, fmt.Errorf("dir must be absolute; got %q", dir)
 		}
 		fs := &fsremote.FS{
@@ -239,6 +243,7 @@ func NewRemoteFS(path string) (common.RemoteFS, error) {
 			CredsFilePath:    *credsFilePath,
 			ConfigFilePath:   *configFilePath,
 			CustomEndpoint:   *customS3Endpoint,
+			StorageClass:     s3remote.StringToS3StorageClass(*s3StorageClass),
 			S3ForcePathStyle: *s3ForcePathStyle,
 			ProfileName:      *configProfile,
 			Bucket:           bucket,
diff --git a/lib/backup/common/part.go b/lib/backup/common/part.go
index 35fc6cfe6..1b58b66b1 100644
--- a/lib/backup/common/part.go
+++ b/lib/backup/common/part.go
@@ -51,7 +51,7 @@ func (p *Part) RemotePath(prefix string) string {
 	return fmt.Sprintf("%s/%s/%016X_%016X_%016X", prefix, p.Path, p.FileSize, p.Offset, p.Size)
 }
 
-var partNameRegexp = regexp.MustCompile(`^(.+)/([0-9A-F]{16})_([0-9A-F]{16})_([0-9A-F]{16})$`)
+var partNameRegexp = regexp.MustCompile(`^(.+)[/\\]([0-9A-F]{16})_([0-9A-F]{16})_([0-9A-F]{16})$`)
 
 // ParseFromRemotePath parses p from remotePath.
 //
diff --git a/lib/backup/fscommon/fscommon.go b/lib/backup/fscommon/fscommon.go
index 0221229e9..8897cc6b8 100644
--- a/lib/backup/fscommon/fscommon.go
+++ b/lib/backup/fscommon/fscommon.go
@@ -10,36 +10,6 @@ import (
 	"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
 )
 
-// FsyncFile fsyncs path contents and the parent directory contents.
-func FsyncFile(path string) error {
-	if err := fsync(path); err != nil {
-		_ = os.RemoveAll(path)
-		return fmt.Errorf("cannot fsync file %q: %w", path, err)
-	}
-	dir := filepath.Dir(path)
-	if err := fsync(dir); err != nil {
-		return fmt.Errorf("cannot fsync dir %q: %w", dir, err)
-	}
-	return nil
-}
-
-// FsyncDir fsyncs dir contents.
-func FsyncDir(dir string) error {
-	return fsync(dir)
-}
-
-func fsync(path string) error {
-	f, err := os.Open(path)
-	if err != nil {
-		return err
-	}
-	if err := f.Sync(); err != nil {
-		_ = f.Close()
-		return err
-	}
-	return f.Close()
-}
-
 // AppendFiles appends all the files from dir to dst.
 //
 // All the appended files will have dir prefix.
@@ -77,7 +47,7 @@ func appendFilesInternal(dst []string, d *os.File) ([]string, error) {
 			// Do not take into account special files.
 			continue
 		}
-		path := dir + "/" + name
+		path := filepath.Join(dir, name)
 		if fi.IsDir() {
 			// Process directory
 			dst, err = AppendFiles(dst, path)
@@ -112,13 +82,13 @@ func appendFilesInternal(dst []string, d *os.File) ([]string, error) {
 			if err != nil {
 				return nil, fmt.Errorf("cannot list files at %q from symlink %q: %w", pathReal, path, err)
 			}
-			pathReal += "/"
+			pathReal += string(filepath.Separator)
 			for i := len(dst); i < len(dstNew); i++ {
 				x := dstNew[i]
 				if !strings.HasPrefix(x, pathReal) {
 					return nil, fmt.Errorf("unexpected prefix for path %q; want %q", x, pathReal)
 				}
-				dstNew[i] = path + "/" + x[len(pathReal):]
+				dstNew[i] = filepath.Join(path, x[len(pathReal):])
 			}
 			dst = dstNew
 			continue
@@ -182,7 +152,7 @@ func removeEmptyDirsInternal(d *os.File) (bool, error) {
 		if name == "." || name == ".." {
 			continue
 		}
-		path := dir + "/" + name
+		path := filepath.Join(dir, name)
 		if fi.IsDir() {
 			// Process directory
 			ok, err := removeEmptyDirs(path)
diff --git a/lib/backup/fslocal/fslocal.go b/lib/backup/fslocal/fslocal.go
index 055c38123..1136cee74 100644
--- a/lib/backup/fslocal/fslocal.go
+++ b/lib/backup/fslocal/fslocal.go
@@ -68,7 +68,7 @@ func (fs *FS) ListParts() ([]common.Part, error) {
 	}
 
 	var parts []common.Part
-	dir += "/"
+	dir += string(filepath.Separator)
 	for _, file := range files {
 		if !strings.HasPrefix(file, dir) {
 			logger.Panicf("BUG: unexpected prefix for file %q; want %q", file, dir)
@@ -187,11 +187,7 @@ func (fs *FS) mkdirAll(filePath string) error {
 }
 
 func (fs *FS) path(p common.Part) string {
-	dir := fs.Dir
-	for strings.HasSuffix(dir, "/") {
-		dir = dir[:len(dir)-1]
-	}
-	return fs.Dir + "/" + p.Path
+	return filepath.Join(fs.Dir, p.Path)
 }
 
 type limitedReadCloser struct {
@@ -235,9 +231,11 @@ func (wc *writeCloser) Write(p []byte) (int, error) {
 }
 
 func (wc *writeCloser) Close() error {
+	wc.w.MustFlush(true)
 	wc.w.MustClose()
 	if wc.n != 0 {
 		return fmt.Errorf("missing data writes for %d bytes", wc.n)
 	}
-	return fscommon.FsyncFile(wc.path)
+
+	return nil
 }
diff --git a/lib/backup/fsremote/fsremote.go b/lib/backup/fsremote/fsremote.go
index d2a7ce851..116d8d999 100644
--- a/lib/backup/fsremote/fsremote.go
+++ b/lib/backup/fsremote/fsremote.go
@@ -9,6 +9,7 @@ import (
 
 	"github.com/VictoriaMetrics/VictoriaMetrics/lib/backup/common"
 	"github.com/VictoriaMetrics/VictoriaMetrics/lib/backup/fscommon"
+	libfs "github.com/VictoriaMetrics/VictoriaMetrics/lib/fs"
 	"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
 )
 
@@ -48,7 +49,7 @@ func (fs *FS) ListParts() ([]common.Part, error) {
 	}
 
 	var parts []common.Part
-	dir += "/"
+	dir += string(filepath.Separator)
 	for _, file := range files {
 		if !strings.HasPrefix(file, dir) {
 			logger.Panicf("BUG: unexpected prefix for file %q; want %q", file, dir)
@@ -101,7 +102,8 @@ func (fs *FS) CopyPart(srcFS common.OriginFS, p common.Part) error {
 	}
 	// Attempt to create hardlink from srcPath to dstPath.
 	if err := os.Link(srcPath, dstPath); err == nil {
-		return fscommon.FsyncFile(dstPath)
+		libfs.MustSyncPath(dstPath)
+		return nil
 	}
 
 	// Cannot create hardlink. Just copy file contents
@@ -115,6 +117,9 @@ func (fs *FS) CopyPart(srcFS common.OriginFS, p common.Part) error {
 		return fmt.Errorf("cannot create destination file: %w", err)
 	}
 	n, err := io.Copy(dstFile, srcFile)
+	if err := dstFile.Sync(); err != nil {
+		return fmt.Errorf("cannot fsync dstFile: %q: %w", dstFile.Name(), err)
+	}
 	if err1 := dstFile.Close(); err1 != nil {
 		err = err1
 	}
@@ -129,10 +134,6 @@ func (fs *FS) CopyPart(srcFS common.OriginFS, p common.Part) error {
 		_ = os.RemoveAll(dstPath)
 		return fmt.Errorf("unexpected number of bytes copied from %q to %q; got %d bytes; want %d bytes", srcPath, dstPath, n, p.Size)
 	}
-	if err := fscommon.FsyncFile(dstPath); err != nil {
-		_ = os.RemoveAll(dstPath)
-		return err
-	}
 	return nil
 }
 
@@ -167,6 +168,9 @@ func (fs *FS) UploadPart(p common.Part, r io.Reader) error {
 		return fmt.Errorf("cannot create file %q: %w", path, err)
 	}
 	n, err := io.Copy(w, r)
+	if err := w.Sync(); err != nil {
+		return fmt.Errorf("cannot fsync file: %q: %w", w.Name(), err)
+	}
 	if err1 := w.Close(); err1 != nil && err == nil {
 		err = err1
 	}
@@ -178,10 +182,6 @@ func (fs *FS) UploadPart(p common.Part, r io.Reader) error {
 		_ = os.RemoveAll(path)
 		return fmt.Errorf("wrong data size uploaded to %q; got %d bytes; want %d bytes", path, n, p.Size)
 	}
-	if err := fscommon.FsyncFile(path); err != nil {
-		_ = os.RemoveAll(path)
-		return err
-	}
 	return nil
 }
 
@@ -194,7 +194,7 @@ func (fs *FS) mkdirAll(filePath string) error {
 }
 
 func (fs *FS) path(p common.Part) string {
-	return p.RemotePath(fs.Dir)
+	return filepath.Join(fs.Dir, p.Path, fmt.Sprintf("%016X_%016X_%016X", p.FileSize, p.Offset, p.Size))
 }
 
 // DeleteFile deletes filePath at fs.
diff --git a/lib/backup/s3remote/s3.go b/lib/backup/s3remote/s3.go
index 741ea7144..2c6a56596 100644
--- a/lib/backup/s3remote/s3.go
+++ b/lib/backup/s3remote/s3.go
@@ -11,12 +11,38 @@ import (
 	"github.com/aws/aws-sdk-go-v2/config"
 	"github.com/aws/aws-sdk-go-v2/feature/s3/manager"
 	"github.com/aws/aws-sdk-go-v2/service/s3"
+	s3types "github.com/aws/aws-sdk-go-v2/service/s3/types"
 
 	"github.com/VictoriaMetrics/VictoriaMetrics/lib/backup/common"
 	"github.com/VictoriaMetrics/VictoriaMetrics/lib/backup/fscommon"
 	"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
 )
 
+var (
+	supportedStorageClasses = []s3types.StorageClass{s3types.StorageClassGlacier, s3types.StorageClassDeepArchive, s3types.StorageClassGlacierIr, s3types.StorageClassIntelligentTiering, s3types.StorageClassOnezoneIa, s3types.StorageClassOutposts, s3types.StorageClassReducedRedundancy, s3types.StorageClassStandard, s3types.StorageClassStandardIa}
+)
+
+func validateStorageClass(storageClass s3types.StorageClass) error {
+	// if no storageClass set, no need to validate against supported values
+	// backwards compatibility
+	if len(storageClass) == 0 {
+		return nil
+	}
+
+	for _, supported := range supportedStorageClasses {
+		if supported == storageClass {
+			return nil
+		}
+	}
+
+	return fmt.Errorf("unsupported S3 storage class: %s. Supported values: %v", storageClass, supportedStorageClasses)
+}
+
+// StringToS3StorageClass converts string types to AWS S3 StorageClass type for value comparison
+func StringToS3StorageClass(sc string) s3types.StorageClass {
+	return s3types.StorageClass(sc)
+}
+
 // FS represents filesystem for backups in S3.
 //
 // Init must be called before calling other FS methods.
@@ -39,6 +65,9 @@ type FS struct {
 	// Force to use path style for s3, true by default.
 	S3ForcePathStyle bool
 
+	// Object Storage Class: https://aws.amazon.com/s3/storage-classes/
+	StorageClass s3types.StorageClass
+
 	// The name of S3 config profile to use.
 	ProfileName string
 
@@ -77,6 +106,11 @@ func (fs *FS) Init() error {
 	if err != nil {
 		return fmt.Errorf("cannot load S3 config: %w", err)
 	}
+
+	if err = validateStorageClass(fs.StorageClass); err != nil {
+		return err
+	}
+
 	var outerErr error
 	fs.s3 = s3.NewFromConfig(cfg, func(o *s3.Options) {
 		if len(fs.CustomEndpoint) > 0 {
@@ -188,10 +222,12 @@ func (fs *FS) CopyPart(srcFS common.OriginFS, p common.Part) error {
 	copySource := fmt.Sprintf("/%s/%s", src.Bucket, srcPath)
 
 	input := &s3.CopyObjectInput{
-		Bucket:     aws.String(fs.Bucket),
-		CopySource: aws.String(copySource),
-		Key:        aws.String(dstPath),
+		Bucket:       aws.String(fs.Bucket),
+		CopySource:   aws.String(copySource),
+		Key:          aws.String(dstPath),
+		StorageClass: fs.StorageClass,
 	}
+
 	_, err := fs.s3.CopyObject(context.Background(), input)
 	if err != nil {
 		return fmt.Errorf("cannot copy %q from %s to %s (copySource %q): %w", p.Path, src, fs, copySource, err)
@@ -231,10 +267,12 @@ func (fs *FS) UploadPart(p common.Part, r io.Reader) error {
 		r: r,
 	}
 	input := &s3.PutObjectInput{
-		Bucket: aws.String(fs.Bucket),
-		Key:    aws.String(path),
-		Body:   sr,
+		Bucket:       aws.String(fs.Bucket),
+		Key:          aws.String(path),
+		Body:         sr,
+		StorageClass: fs.StorageClass,
 	}
+
 	_, err := fs.uploader.Upload(context.Background(), input)
 	if err != nil {
 		return fmt.Errorf("cannot upoad data to %q at %s (remote path %q): %w", p.Path, fs, path, err)
@@ -282,9 +320,10 @@ func (fs *FS) CreateFile(filePath string, data []byte) error {
 		r: bytes.NewReader(data),
 	}
 	input := &s3.PutObjectInput{
-		Bucket: aws.String(fs.Bucket),
-		Key:    aws.String(path),
-		Body:   sr,
+		Bucket:       aws.String(fs.Bucket),
+		Key:          aws.String(path),
+		Body:         sr,
+		StorageClass: fs.StorageClass,
 	}
 	_, err := fs.uploader.Upload(context.Background(), input)
 	if err != nil {
diff --git a/lib/fs/fs.go b/lib/fs/fs.go
index 81dbf920f..2392b6178 100644
--- a/lib/fs/fs.go
+++ b/lib/fs/fs.go
@@ -219,17 +219,7 @@ func IsEmptyDir(path string) bool {
 // If the process crashes after the step 1, then the directory must be removed
 // on the next process start by calling MustRemoveTemporaryDirs on the parent directory.
 func MustRemoveDirAtomic(dir string) {
-	if !IsPathExist(dir) {
-		return
-	}
-	n := atomic.AddUint64(&atomicDirRemoveCounter, 1)
-	tmpDir := fmt.Sprintf("%s.must-remove.%d", dir, n)
-	if err := os.Rename(dir, tmpDir); err != nil {
-		logger.Panicf("FATAL: cannot move %s to %s: %s", dir, tmpDir, err)
-	}
-	MustRemoveAll(tmpDir)
-	parentDir := filepath.Dir(dir)
-	MustSyncPath(parentDir)
+	mustRemoveDirAtomic(dir)
 }
 
 var atomicDirRemoveCounter = uint64(time.Now().UnixNano())
diff --git a/lib/fs/fs_nix.go b/lib/fs/fs_nix.go
index 96d37d77f..54dc570c4 100644
--- a/lib/fs/fs_nix.go
+++ b/lib/fs/fs_nix.go
@@ -4,9 +4,29 @@
 package fs
 
 import (
+	"fmt"
+	"os"
+	"path/filepath"
+	"sync/atomic"
+
+	"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
 	"golang.org/x/sys/unix"
 )
 
 func freeSpace(stat unix.Statfs_t) uint64 {
 	return uint64(stat.Bavail) * uint64(stat.Bsize)
 }
+
+func mustRemoveDirAtomic(dir string) {
+	if !IsPathExist(dir) {
+		return
+	}
+	n := atomic.AddUint64(&atomicDirRemoveCounter, 1)
+	tmpDir := fmt.Sprintf("%s.must-remove.%d", dir, n)
+	if err := os.Rename(dir, tmpDir); err != nil {
+		logger.Panicf("FATAL: cannot move %s to %s: %s", dir, tmpDir, err)
+	}
+	MustRemoveAll(tmpDir)
+	parentDir := filepath.Dir(dir)
+	MustSyncPath(parentDir)
+}
diff --git a/lib/fs/fs_openbsd.go b/lib/fs/fs_openbsd.go
index 1ab4b9358..27e3f9dac 100644
--- a/lib/fs/fs_openbsd.go
+++ b/lib/fs/fs_openbsd.go
@@ -1,9 +1,29 @@
 package fs
 
 import (
+	"fmt"
+	"os"
+	"path/filepath"
+	"sync/atomic"
+
+	"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
 	"golang.org/x/sys/unix"
 )
 
 func freeSpace(stat unix.Statfs_t) uint64 {
 	return uint64(stat.F_bavail) * uint64(stat.F_bsize)
 }
+
+func mustRemoveDirAtomic(dir string) {
+	if !IsPathExist(dir) {
+		return
+	}
+	n := atomic.AddUint64(&atomicDirRemoveCounter, 1)
+	tmpDir := fmt.Sprintf("%s.must-remove.%d", dir, n)
+	if err := os.Rename(dir, tmpDir); err != nil {
+		logger.Panicf("FATAL: cannot move %s to %s: %s", dir, tmpDir, err)
+	}
+	MustRemoveAll(tmpDir)
+	parentDir := filepath.Dir(dir)
+	MustSyncPath(parentDir)
+}
diff --git a/lib/fs/fs_solaris.go b/lib/fs/fs_solaris.go
index 385903896..c5cb04ec1 100644
--- a/lib/fs/fs_solaris.go
+++ b/lib/fs/fs_solaris.go
@@ -3,11 +3,27 @@ package fs
 import (
 	"fmt"
 	"os"
+	"path/filepath"
+	"sync/atomic"
 
 	"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
 	"golang.org/x/sys/unix"
 )
 
+func mustRemoveDirAtomic(dir string) {
+	if !IsPathExist(dir) {
+		return
+	}
+	n := atomic.AddUint64(&atomicDirRemoveCounter, 1)
+	tmpDir := fmt.Sprintf("%s.must-remove.%d", dir, n)
+	if err := os.Rename(dir, tmpDir); err != nil {
+		logger.Panicf("FATAL: cannot move %s to %s: %s", dir, tmpDir, err)
+	}
+	MustRemoveAll(tmpDir)
+	parentDir := filepath.Dir(dir)
+	MustSyncPath(parentDir)
+}
+
 func mmap(fd int, length int) (data []byte, err error) {
 	return unix.Mmap(fd, 0, length, unix.PROT_READ, unix.MAP_SHARED)
 
diff --git a/lib/fs/fs_windows.go b/lib/fs/fs_windows.go
index fff0783c7..949829e9b 100644
--- a/lib/fs/fs_windows.go
+++ b/lib/fs/fs_windows.go
@@ -5,6 +5,7 @@ import (
 	"os"
 	"reflect"
 	"sync"
+	"sync/atomic"
 	"unsafe"
 
 	"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
@@ -23,6 +24,21 @@ var (
 func mustSyncPath(path string) {
 }
 
+func mustRemoveDirAtomic(dir string) {
+	if !IsPathExist(dir) {
+		return
+	}
+	n := atomic.AddUint64(&atomicDirRemoveCounter, 1)
+	tmpDir := fmt.Sprintf("%s.must-remove.%d", dir, n)
+	if err := os.Rename(dir, tmpDir); err != nil {
+		logger.Panicf("FATAL: cannot move %s to %s: %s", dir, tmpDir, err)
+	}
+	err := os.RemoveAll(tmpDir)
+	if err != nil {
+		logger.Warnf("cannot remove dir: %q: %s, restart VictoriaMetrics process to complete file deletion", tmpDir, err)
+	}
+}
+
 const (
 	lockfileExclusiveLock = 2
 	fileFlagNormal        = 0x00000080
diff --git a/lib/promscrape/discovery/kubernetes/endpoints.go b/lib/promscrape/discovery/kubernetes/endpoints.go
index f83526ed0..25497f4f0 100644
--- a/lib/promscrape/discovery/kubernetes/endpoints.go
+++ b/lib/promscrape/discovery/kubernetes/endpoints.go
@@ -114,6 +114,10 @@ func (eps *Endpoints) getTargetLabels(gw *groupWatcher) []*promutils.Labels {
 		logger.Warnf(`the number of targets for "role: endpoints" %q exceeds 1000 and will be truncated in the next k8s releases; please use "role: endpointslice" instead`, eps.Metadata.key())
 	}
 
+	// Prometheus sets endpoints_name and namespace labels for all endpoints
+	// Even if port is not matching service port.
+	// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/4154
+	commonEpLabels := eps.getCommonLabels()
 	// Append labels for skipped ports on seen pods.
 	portSeen := func(port int, ports []int) bool {
 		for _, p := range ports {
@@ -134,6 +138,7 @@ func (eps *Endpoints) getTargetLabels(gw *groupWatcher) []*promutils.Labels {
 				m.Add("__address__", addr)
 				p.appendCommonLabels(m, gw)
 				p.appendContainerLabels(m, c, &cp)
+				m.AddFrom(commonEpLabels)
 				if svc != nil {
 					svc.appendCommonLabels(m)
 				}
@@ -146,6 +151,13 @@ func (eps *Endpoints) getTargetLabels(gw *groupWatcher) []*promutils.Labels {
 	return ms
 }
 
+func (eps *Endpoints) getCommonLabels() *promutils.Labels {
+	m := promutils.GetLabels()
+	m.Add("__meta_kubernetes_namespace", eps.Metadata.Namespace)
+	m.Add("__meta_kubernetes_endpoints_name", eps.Metadata.Name)
+	return m
+}
+
 func appendEndpointLabelsForAddresses(ms []*promutils.Labels, gw *groupWatcher, podPortsSeen map[*Pod][]int, eps *Endpoints,
 	eas []EndpointAddress, epp EndpointPort, svc *Service, ready string) []*promutils.Labels {
 	for _, ea := range eas {


Test patch:
diff --git a/app/vmalert/web_test.go b/app/vmalert/web_test.go
index 600010527..3a70f6fef 100644
--- a/app/vmalert/web_test.go
+++ b/app/vmalert/web_test.go
@@ -162,5 +162,59 @@ func TestHandler(t *testing.T) {
 	t.Run("/api/v1/1/0/status", func(t *testing.T) {
 		getResp(ts.URL+"/api/v1/1/0/status", nil, 404)
 	})
+}
+
+func TestEmptyResponse(t *testing.T) {
+	rh := &requestHandler{m: &manager{groups: make(map[uint64]*Group)}}
+	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { rh.handler(w, r) }))
+	defer ts.Close()
+
+	getResp := func(url string, to interface{}, code int) {
+		t.Helper()
+		resp, err := http.Get(url)
+		if err != nil {
+			t.Fatalf("unexpected err %s", err)
+		}
+		if code != resp.StatusCode {
+			t.Errorf("unexpected status code %d want %d", resp.StatusCode, code)
+		}
+		defer func() {
+			if err := resp.Body.Close(); err != nil {
+				t.Errorf("err closing body %s", err)
+			}
+		}()
+		if to != nil {
+			if err = json.NewDecoder(resp.Body).Decode(to); err != nil {
+				t.Errorf("unexpected err %s", err)
+			}
+		}
+	}
+
+	t.Run("/api/v1/alerts", func(t *testing.T) {
+		lr := listAlertsResponse{}
+		getResp(ts.URL+"/api/v1/alerts", &lr, 200)
+		if lr.Data.Alerts == nil {
+			t.Errorf("expected /api/v1/alerts response to have non-nil data")
+		}
+
+		lr = listAlertsResponse{}
+		getResp(ts.URL+"/vmalert/api/v1/alerts", &lr, 200)
+		if lr.Data.Alerts == nil {
+			t.Errorf("expected /api/v1/alerts response to have non-nil data")
+		}
+	})
 
+	t.Run("/api/v1/rules", func(t *testing.T) {
+		lr := listGroupsResponse{}
+		getResp(ts.URL+"/api/v1/rules", &lr, 200)
+		if lr.Data.Groups == nil {
+			t.Errorf("expected /api/v1/rules response to have non-nil data")
+		}
+
+		lr = listGroupsResponse{}
+		getResp(ts.URL+"/vmalert/api/v1/rules", &lr, 200)
+		if lr.Data.Groups == nil {
+			t.Errorf("expected /api/v1/rules response to have non-nil data")
+		}
+	})
 }
diff --git a/lib/promscrape/discovery/kubernetes/endpoints_test.go b/lib/promscrape/discovery/kubernetes/endpoints_test.go
index 402bb18ed..bb6b9e8ac 100644
--- a/lib/promscrape/discovery/kubernetes/endpoints_test.go
+++ b/lib/promscrape/discovery/kubernetes/endpoints_test.go
@@ -293,6 +293,7 @@ func TestGetEndpointsLabels(t *testing.T) {
 			}),
 			promutils.NewLabelsFromMap(map[string]string{
 				"__address__":                                    "192.168.15.1:8428",
+				"__meta_kubernetes_endpoints_name":               "test-eps",
 				"__meta_kubernetes_namespace":                    "default",
 				"__meta_kubernetes_node_label_node_label":        "xyz",
 				"__meta_kubernetes_node_labelpresent_node_label": "true",