Repository: VictoriaMetrics/VictoriaMetrics
Pull request #3436 (2022-12-02 15:44:57)

dashboards: add `Disk space usage %` and `Disk space usage % by type`…
… panels

The new panels have been added to the vmstorage adn drilldown rows.

`Disk space usage %` supposed to show disk space usage percentage. This panel is now also referred by `DiskRunsOutOfSpace` alerting rule. This panel has Drilldown option to show absolute values.

`Disk space usage % by type` shows relation between datapoints and indexdb size. It supposed to help identify cases when indexdb starts to take too much disk space.
This panel has Drilldown option to show absolute values.

Signed-off-by: hagen1778 <roman@victoriametrics.com>


---------------------

<img width="640" alt="image" src="https://user-images.githubusercontent.com/2902918/205331051-4740975c-ff7b-4ec2-b678-f82d401a0070.png">

<img width="631" alt="image" src="https://user-images.githubusercontent.com/2902918/205331091-53d49001-2698-4ee1-b6a4-d657c50aec25.png">




Patch:
diff --git a/app/vmagent/remotewrite/remotewrite.go b/app/vmagent/remotewrite/remotewrite.go
index ae5234ff4..d64d38113 100644
--- a/app/vmagent/remotewrite/remotewrite.go
+++ b/app/vmagent/remotewrite/remotewrite.go
@@ -251,7 +251,7 @@ func Stop() {
 // Push sends wr to remote storage systems set via `-remoteWrite.url`.
 //
 // If at is nil, then the data is pushed to the configured `-remoteWrite.url`.
-// If at isn't nil, the the data is pushed to the configured `-remoteWrite.multitenantURL`.
+// If at isn't nil, the data is pushed to the configured `-remoteWrite.multitenantURL`.
 //
 // Note that wr may be modified by Push due to relabeling and rounding.
 func Push(at *auth.Token, wr *prompbmarshal.WriteRequest) {
diff --git a/app/vmalert/datasource/datasource.go b/app/vmalert/datasource/datasource.go
index 898061fe7..6792efb56 100644
--- a/app/vmalert/datasource/datasource.go
+++ b/app/vmalert/datasource/datasource.go
@@ -54,6 +54,19 @@ func (m *Metric) SetLabel(key, value string) {
 	m.AddLabel(key, value)
 }
 
+// SetLabels sets the given map as Metric labels
+func (m *Metric) SetLabels(ls map[string]string) {
+	var i int
+	m.Labels = make([]Label, len(ls))
+	for k, v := range ls {
+		m.Labels[i] = Label{
+			Name:  k,
+			Value: v,
+		}
+		i++
+	}
+}
+
 // AddLabel appends the given label to the label set
 func (m *Metric) AddLabel(key, value string) {
 	m.Labels = append(m.Labels, Label{Name: key, Value: value})
diff --git a/app/vmalert/datasource/vm_prom_api.go b/app/vmalert/datasource/vm_prom_api.go
index 1b5fd5915..7689626fb 100644
--- a/app/vmalert/datasource/vm_prom_api.go
+++ b/app/vmalert/datasource/vm_prom_api.go
@@ -32,19 +32,17 @@ type promInstant struct {
 }
 
 func (r promInstant) metrics() ([]Metric, error) {
-	var result []Metric
+	result := make([]Metric, len(r.Result))
 	for i, res := range r.Result {
 		f, err := strconv.ParseFloat(res.TV[1].(string), 64)
 		if err != nil {
 			return nil, fmt.Errorf("metric %v, unable to parse float64 from %s: %w", res, res.TV[1], err)
 		}
 		var m Metric
-		for k, v := range r.Result[i].Labels {
-			m.AddLabel(k, v)
-		}
+		m.SetLabels(res.Labels)
 		m.Timestamps = append(m.Timestamps, int64(res.TV[0].(float64)))
 		m.Values = append(m.Values, f)
-		result = append(result, m)
+		result[i] = m
 	}
 	return result, nil
 }
diff --git a/dashboards/victoriametrics-cluster.json b/dashboards/victoriametrics-cluster.json
index 75be1b925..1274cd6f2 100644
--- a/dashboards/victoriametrics-cluster.json
+++ b/dashboards/victoriametrics-cluster.json
@@ -1612,8 +1612,7 @@
                 "mode": "absolute",
                 "steps": [
                   {
-                    "color": "green",
-                    "value": null
+                    "color": "green"
                   },
                   {
                     "color": "red",
@@ -1629,7 +1628,7 @@
             "h": 8,
             "w": 12,
             "x": 0,
-            "y": 14
+            "y": 30
           },
           "id": 66,
           "links": [],
@@ -1724,8 +1723,7 @@
                 "mode": "absolute",
                 "steps": [
                   {
-                    "color": "green",
-                    "value": null
+                    "color": "green"
                   },
                   {
                     "color": "red",
@@ -1741,7 +1739,7 @@
             "h": 8,
             "w": 12,
             "x": 12,
-            "y": 14
+            "y": 30
           },
           "id": 138,
           "links": [],
@@ -1835,8 +1833,7 @@
                 "mode": "absolute",
                 "steps": [
                   {
-                    "color": "green",
-                    "value": null
+                    "color": "green"
                   },
                   {
                     "color": "red",
@@ -1852,7 +1849,7 @@
             "h": 8,
             "w": 12,
             "x": 0,
-            "y": 22
+            "y": 38
           },
           "id": 64,
           "links": [],
@@ -1942,8 +1939,7 @@
                 "mode": "absolute",
                 "steps": [
                   {
-                    "color": "green",
-                    "value": null
+                    "color": "green"
                   },
                   {
                     "color": "red",
@@ -1972,7 +1968,7 @@
             "h": 8,
             "w": 12,
             "x": 12,
-            "y": 22
+            "y": 38
           },
           "id": 122,
           "links": [],
@@ -2080,8 +2076,7 @@
                 "mode": "absolute",
                 "steps": [
                   {
-                    "color": "green",
-                    "value": null
+                    "color": "green"
                   },
                   {
                     "color": "red",
@@ -2113,7 +2108,7 @@
             "h": 8,
             "w": 12,
             "x": 0,
-            "y": 30
+            "y": 46
           },
           "id": 117,
           "links": [],
@@ -2201,8 +2196,7 @@
                 "mode": "absolute",
                 "steps": [
                   {
-                    "color": "green",
-                    "value": null
+                    "color": "green"
                   },
                   {
                     "color": "red",
@@ -2218,7 +2212,7 @@
             "h": 8,
             "w": 12,
             "x": 12,
-            "y": 30
+            "y": 46
           },
           "id": 119,
           "options": {
@@ -2306,8 +2300,7 @@
                 "mode": "absolute",
                 "steps": [
                   {
-                    "color": "green",
-                    "value": null
+                    "color": "green"
                   },
                   {
                     "color": "red",
@@ -2323,7 +2316,7 @@
             "h": 8,
             "w": 12,
             "x": 0,
-            "y": 38
+            "y": 54
           },
           "id": 68,
           "links": [],
@@ -2411,8 +2404,7 @@
                 "mode": "absolute",
                 "steps": [
                   {
-                    "color": "green",
-                    "value": null
+                    "color": "green"
                   },
                   {
                     "color": "red",
@@ -2428,7 +2420,7 @@
             "h": 8,
             "w": 12,
             "x": 12,
-            "y": 38
+            "y": 54
           },
           "id": 120,
           "options": {
@@ -2516,8 +2508,7 @@
                 "mode": "absolute",
                 "steps": [
                   {
-                    "color": "green",
-                    "value": null
+                    "color": "green"
                   },
                   {
                     "color": "red",
@@ -2533,7 +2524,7 @@
             "h": 8,
             "w": 12,
             "x": 0,
-            "y": 46
+            "y": 62
           },
           "id": 70,
           "links": [],
@@ -2675,7 +2666,7 @@
             "h": 8,
             "w": 12,
             "x": 0,
-            "y": 15
+            "y": 31
           },
           "id": 102,
           "options": {
@@ -2789,7 +2780,7 @@
             "h": 8,
             "w": 12,
             "x": 12,
-            "y": 15
+            "y": 31
           },
           "id": 108,
           "options": {
@@ -2890,7 +2881,7 @@
             "h": 8,
             "w": 12,
             "x": 0,
-            "y": 23
+            "y": 39
           },
           "id": 142,
           "links": [
@@ -3001,7 +2992,7 @@
             "h": 8,
             "w": 12,
             "x": 12,
-            "y": 23
+            "y": 39
           },
           "id": 107,
           "options": {
@@ -3100,7 +3091,7 @@
             "h": 8,
             "w": 12,
             "x": 0,
-            "y": 31
+            "y": 47
           },
           "id": 170,
           "links": [],
@@ -3206,7 +3197,7 @@
             "h": 8,
             "w": 12,
             "x": 12,
-            "y": 31
+            "y": 47
           },
           "id": 116,
           "links": [],
@@ -3308,7 +3299,7 @@
             "h": 9,
             "w": 12,
             "x": 0,
-            "y": 39
+            "y": 55
           },
           "id": 144,
           "options": {
@@ -3411,7 +3402,7 @@
             "h": 9,
             "w": 12,
             "x": 12,
-            "y": 39
+            "y": 55
           },
           "id": 58,
           "links": [],
@@ -3515,7 +3506,7 @@
             "h": 7,
             "w": 24,
             "x": 0,
-            "y": 48
+            "y": 64
           },
           "id": 183,
           "options": {
@@ -3663,7 +3654,7 @@
             "h": 9,
             "w": 12,
             "x": 0,
-            "y": 5
+            "y": 21
           },
           "id": 76,
           "links": [],
@@ -3779,7 +3770,7 @@
             "h": 9,
             "w": 12,
             "x": 12,
-            "y": 5
+            "y": 21
           },
           "id": 86,
           "links": [],
@@ -3904,7 +3895,7 @@
             "h": 8,
             "w": 12,
             "x": 0,
-            "y": 14
+            "y": 30
           },
           "id": 80,
           "links": [],
@@ -4009,7 +4000,7 @@
             "h": 8,
             "w": 12,
             "x": 12,
-            "y": 14
+            "y": 30
           },
           "id": 78,
           "links": [],
@@ -4125,7 +4116,7 @@
             "h": 8,
             "w": 12,
             "x": 0,
-            "y": 22
+            "y": 38
           },
           "id": 82,
           "options": {
@@ -4232,7 +4223,7 @@
             "h": 8,
             "w": 12,
             "x": 12,
-            "y": 22
+            "y": 38
           },
           "id": 74,
           "options": {
@@ -4334,7 +4325,8 @@
                 "mode": "absolute",
                 "steps": [
                   {
-                    "color": "green"
+                    "color": "green",
+                    "value": null
                   },
                   {
                     "color": "red",
@@ -4445,7 +4437,8 @@
                 "mode": "absolute",
                 "steps": [
                   {
-                    "color": "green"
+                    "color": "green",
+                    "value": null
                   },
                   {
                     "color": "red",
@@ -4557,7 +4550,8 @@
                 "mode": "absolute",
                 "steps": [
                   {
-                    "color": "green"
+                    "color": "green",
+                    "value": null
                   },
                   {
                     "color": "red",
@@ -4702,7 +4696,8 @@
                 "mode": "absolute",
                 "steps": [
                   {
-                    "color": "green"
+                    "color": "green",
+                    "value": null
                   },
                   {
                     "color": "red",
@@ -4839,7 +4834,8 @@
                 "mode": "absolute",
                 "steps": [
                   {
-                    "color": "green"
+                    "color": "green",
+                    "value": null
                   },
                   {
                     "color": "red",
@@ -4942,7 +4938,8 @@
                 "mode": "absolute",
                 "steps": [
                   {
-                    "color": "green"
+                    "color": "green",
+                    "value": null
                   },
                   {
                     "color": "red",
@@ -5084,7 +5081,8 @@
                 "mode": "absolute",
                 "steps": [
                   {
-                    "color": "green"
+                    "color": "green",
+                    "value": null
                   },
                   {
                     "color": "red",
@@ -5187,7 +5185,8 @@
                 "mode": "absolute",
                 "steps": [
                   {
-                    "color": "green"
+                    "color": "green",
+                    "value": null
                   },
                   {
                     "color": "red",
@@ -5246,7 +5245,7 @@
             "type": "prometheus",
             "uid": "$ds"
           },
-          "description": "Shows amount of on-disk space occupied by data points.",
+          "description": "Shows the percentage of used disk space. It is recommended to have at least 20% of free disk space for the best performance.",
           "fieldConfig": {
             "defaults": {
               "color": {
@@ -5259,7 +5258,7 @@
                 "axisPlacement": "auto",
                 "barAlignment": 0,
                 "drawStyle": "line",
-                "fillOpacity": 10,
+                "fillOpacity": 0,
                 "gradientMode": "none",
                 "hideFrom": {
                   "legend": false,
@@ -5276,20 +5275,27 @@
                 "spanNulls": false,
                 "stacking": {
                   "group": "A",
-                  "mode": "normal"
+                  "mode": "none"
                 },
                 "thresholdsStyle": {
-                  "mode": "off"
+                  "mode": "line"
                 }
               },
-              "links": [],
+              "links": [
+                {
+                  "targetBlank": true,
+                  "title": "Drilldown",
+                  "url": "/d/oS7Bi_0Wz?viewPanel=200&var-ds=$ds&var-instance=$instance&${__url_time_range}"
+                }
+              ],
               "mappings": [],
               "min": 0,
               "thresholds": {
                 "mode": "absolute",
                 "steps": [
                   {
-                    "color": "green"
+                    "color": "green",
+                    "value": null
                   },
                   {
                     "color": "red",
@@ -5297,7 +5303,7 @@
                   }
                 ]
               },
-              "unit": "bytes"
+              "unit": "percentunit"
             },
             "overrides": []
           },
@@ -5307,7 +5313,7 @@
             "x": 0,
             "y": 37
           },
-          "id": 18,
+          "id": 20,
           "links": [],
           "options": {
             "legend": {
@@ -5324,7 +5330,7 @@
             },
             "tooltip": {
               "mode": "multi",
-              "sort": "none"
+              "sort": "desc"
             }
           },
           "pluginVersion": "9.1.0",
@@ -5335,15 +5341,43 @@
                 "uid": "$ds"
               },
               "editorMode": "code",
-              "expr": "sum(vm_data_size_bytes{job=~\"$job_storage\", instance=~\"$instance\", type!=\"indexdb\"}) ",
+              "expr": "max(\n    sum(vm_data_size_bytes{job=~\"$job\", instance=~\"$instance\"}) by(job, instance) /\n    (\n        sum(vm_free_disk_space_bytes{job=~\"$job\", instance=~\"$instance\"}) by(job, instance) +\n        sum(vm_data_size_bytes{job=~\"$job\", instance=~\"$instance\"}) by(job, instance)\n    ) \n)",
               "format": "time_series",
               "intervalFactor": 1,
-              "legendFormat": "disk usage",
+              "legendFormat": "max",
               "range": true,
               "refId": "A"
+            },
+            {
+              "datasource": {
+                "type": "prometheus",
+                "uid": "$ds"
+              },
+              "editorMode": "code",
+              "expr": "min(\n    sum(vm_data_size_bytes{job=~\"$job\", instance=~\"$instance\"}) by(job, instance) /\n    (\n        sum(vm_free_disk_space_bytes{job=~\"$job\", instance=~\"$instance\"}) by(job, instance) +\n        sum(vm_data_size_bytes{job=~\"$job\", instance=~\"$instance\"}) by(job, instance)\n    ) \n)",
+              "format": "time_series",
+              "hide": false,
+              "intervalFactor": 1,
+              "legendFormat": "min",
+              "range": true,
+              "refId": "B"
+            },
+            {
+              "datasource": {
+                "type": "prometheus",
+                "uid": "$ds"
+              },
+              "editorMode": "code",
+              "expr": "avg(\n    sum(vm_data_size_bytes{job=~\"$job\", instance=~\"$instance\"}) by(job, instance) /\n    (\n        sum(vm_free_disk_space_bytes{job=~\"$job\", instance=~\"$instance\"}) by(job, instance) +\n        sum(vm_data_size_bytes{job=~\"$job\", instance=~\"$instance\"}) by(job, instance)\n    ) \n)",
+              "format": "time_series",
+              "hide": false,
+              "intervalFactor": 1,
+              "legendFormat": "avg",
+              "range": true,
+              "refId": "C"
             }
           ],
-          "title": "Disk space usage (datapoints) ($instance)",
+          "title": "Disk space usage %  ($instance)",
           "type": "timeseries"
         },
         {
@@ -5394,7 +5428,8 @@
                 "mode": "absolute",
                 "steps": [
                   {
-                    "color": "green"
+                    "color": "green",
+                    "value": null
                   },
                   {
                     "color": "red",
@@ -5457,7 +5492,7 @@
             "type": "prometheus",
             "uid": "$ds"
           },
-          "description": "Shows amount of on-disk space occupied by inverted index.",
+          "description": "Shows the percentage of used disk space by type: datapoints or indexdb. Normally, indexdb takes much less space comparing to datapoints. But with high churn rate the size of the indexdb could grow significantly.\n\nThe sum of the % can be > 100% since panel shows max % per-job and per-instance. It means different instance can have different ratio between datapoints and indexdb size.",
           "fieldConfig": {
             "defaults": {
               "color": {
@@ -5470,7 +5505,7 @@
                 "axisPlacement": "auto",
                 "barAlignment": 0,
                 "drawStyle": "line",
-                "fillOpacity": 10,
+                "fillOpacity": 0,
                 "gradientMode": "none",
                 "hideFrom": {
                   "legend": false,
@@ -5487,28 +5522,31 @@
                 "spanNulls": false,
                 "stacking": {
                   "group": "A",
-                  "mode": "normal"
+                  "mode": "none"
                 },
                 "thresholdsStyle": {
-                  "mode": "off"
+                  "mode": "line"
                 }
               },
-              "links": [],
+              "links": [
+                {
+                  "targetBlank": true,
+                  "title": "Drilldown",
+                  "url": "/d/oS7Bi_0Wz?viewPanel=201&var-ds=$ds&var-instance=$instance&${__url_time_range}"
+                }
+              ],
               "mappings": [],
               "min": 0,
               "thresholds": {
                 "mode": "absolute",
                 "steps": [
                   {
-                    "color": "green"
-                  },
-                  {
-                    "color": "red",
-                    "value": 80
+                    "color": "green",
+                    "value": null
                   }
                 ]
               },
-              "unit": "bytes"
+              "unit": "percentunit"
             },
             "overrides": []
           },
@@ -5518,7 +5556,7 @@
             "x": 0,
             "y": 45
           },
-          "id": 20,
+          "id": 202,
           "links": [],
           "options": {
             "legend": {
@@ -5535,7 +5573,7 @@
             },
             "tooltip": {
               "mode": "multi",
-              "sort": "none"
+              "sort": "desc"
             }
           },
           "pluginVersion": "9.1.0",
@@ -5546,15 +5584,29 @@
                 "uid": "$ds"
               },
               "editorMode": "code",
-              "expr": "sum(vm_data_size_bytes{job=~\"$job_storage\", instance=~\"$instance\", type=\"indexdb\"})",
+              "expr": "max(\n    sum(vm_data_size_bytes{job=~\"$job\", instance=~\"$instance\", type=\"indexdb\"}) by(job, instance)\n    / \n    sum(vm_data_size_bytes{job=~\"$job\", instance=~\"$instance\"}) by(job, instance)\n)",
               "format": "time_series",
               "intervalFactor": 1,
-              "legendFormat": "disk usage",
+              "legendFormat": "indexdb",
               "range": true,
               "refId": "A"
+            },
+            {
+              "datasource": {
+                "type": "prometheus",
+                "uid": "$ds"
+              },
+              "editorMode": "code",
+              "expr": "max(\n    sum(vm_data_size_bytes{job=~\"$job\", instance=~\"$instance\", type!=\"indexdb\"}) by(job, instance)\n    / \n    sum(vm_data_size_bytes{job=~\"$job\", instance=~\"$instance\"}) by(job, instance)\n)",
+              "format": "time_series",
+              "hide": false,
+              "intervalFactor": 1,
+              "legendFormat": "datapoints",
+              "range": true,
+              "refId": "B"
             }
           ],
-          "title": "Disk space usage (index)  ($instance)",
+          "title": "Disk space usage % by type  ($instance)",
           "type": "timeseries"
         },
         {
@@ -5605,7 +5657,8 @@
                 "mode": "absolute",
                 "steps": [
                   {
-                    "color": "green"
+                    "color": "green",
+                    "value": null
                   },
                   {
                     "color": "red",
@@ -5740,7 +5793,8 @@
                 "mode": "absolute",
                 "steps": [
                   {
-                    "color": "green"
+                    "color": "green",
+                    "value": null
                   },
                   {
                     "color": "red",
@@ -5755,7 +5809,7 @@
           "gridPos": {
             "h": 8,
             "w": 12,
-            "x": 12,
+            "x": 0,
             "y": 53
           },
           "id": 135,
@@ -5862,8 +5916,7 @@
                 "mode": "absolute",
                 "steps": [
                   {
-                    "color": "green",
-                    "value": null
+                    "color": "green"
                   },
                   {
                     "color": "red",
@@ -5879,7 +5932,7 @@
             "h": 8,
             "w": 12,
             "x": 0,
-            "y": 82
+            "y": 98
           },
           "id": 92,
           "links": [],
@@ -5969,8 +6022,7 @@
                 "mode": "absolute",
                 "steps": [
                   {
-                    "color": "green",
-                    "value": null
+                    "color": "green"
                   },
                   {
                     "color": "red",
@@ -6006,7 +6058,7 @@
             "h": 8,
             "w": 12,
             "x": 12,
-            "y": 82
+            "y": 98
           },
           "id": 95,
           "links": [],
@@ -6112,8 +6164,7 @@
                 "mode": "absolute",
                 "steps": [
                   {
-                    "color": "green",
-                    "value": null
+                    "color": "green"
                   },
                   {
                     "color": "red",
@@ -6129,7 +6180,7 @@
             "h": 8,
             "w": 12,
             "x": 0,
-            "y": 90
+            "y": 106
           },
           "id": 163,
           "links": [],
@@ -6257,8 +6308,7 @@
                 "mode": "absolute",
                 "steps": [
                   {
-                    "color": "green",
-                    "value": null
+                    "color": "green"
                   },
                   {
                     "color": "red",
@@ -6274,7 +6324,7 @@
             "h": 8,
             "w": 12,
             "x": 12,
-            "y": 90
+            "y": 106
           },
           "id": 165,
           "links": [],
@@ -6398,8 +6448,7 @@
                 "mode": "absolute",
                 "steps": [
                   {
-                    "color": "green",
-                    "value": null
+                    "color": "green"
                   },
                   {
                     "color": "red",
@@ -6415,7 +6464,7 @@
             "h": 8,
             "w": 12,
             "x": 0,
-            "y": 98
+            "y": 114
           },
           "id": 178,
           "links": [],
@@ -6506,8 +6555,7 @@
                 "mode": "absolute",
                 "steps": [
                   {
-                    "color": "green",
-                    "value": null
+                    "color": "green"
                   },
                   {
                     "color": "red",
@@ -6523,7 +6571,7 @@
             "h": 8,
             "w": 12,
             "x": 12,
-            "y": 98
+            "y": 114
           },
           "id": 180,
           "links": [],
@@ -6630,7 +6678,7 @@
             "h": 8,
             "w": 12,
             "x": 0,
-            "y": 106
+            "y": 122
           },
           "id": 179,
           "links": [],
@@ -6737,7 +6785,7 @@
             "h": 8,
             "w": 12,
             "x": 12,
-            "y": 106
+            "y": 122
           },
           "id": 181,
           "links": [],
@@ -6855,7 +6903,7 @@
             "h": 8,
             "w": 24,
             "x": 0,
-            "y": 114
+            "y": 130
           },
           "id": 93,
           "links": [],
@@ -6991,7 +7039,7 @@
             "h": 8,
             "w": 12,
             "x": 0,
-            "y": 8
+            "y": 24
           },
           "id": 97,
           "links": [],
@@ -7117,7 +7165,7 @@
             "h": 8,
             "w": 12,
             "x": 12,
-            "y": 8
+            "y": 24
           },
           "id": 99,
           "links": [],
@@ -7241,7 +7289,7 @@
             "h": 8,
             "w": 12,
             "x": 0,
-            "y": 16
+            "y": 32
           },
           "id": 185,
           "links": [],
@@ -7385,7 +7433,7 @@
             "h": 8,
             "w": 12,
             "x": 12,
-            "y": 16
+            "y": 32
           },
           "id": 187,
           "links": [],
@@ -7523,7 +7571,7 @@
             "h": 8,
             "w": 12,
             "x": 0,
-            "y": 24
+            "y": 40
           },
           "id": 90,
           "links": [],
@@ -7631,7 +7679,7 @@
             "h": 8,
             "w": 12,
             "x": 12,
-            "y": 24
+            "y": 40
           },
           "id": 88,
           "links": [],
@@ -7738,7 +7786,7 @@
             "h": 8,
             "w": 12,
             "x": 0,
-            "y": 32
+            "y": 48
           },
           "id": 139,
           "links": [],
@@ -7845,7 +7893,7 @@
             "h": 8,
             "w": 12,
             "x": 12,
-            "y": 32
+            "y": 48
           },
           "id": 114,
           "links": [],
@@ -7911,10 +7959,15 @@
           },
           "id": 198,
           "options": {
+            "code": {
+              "language": "plaintext",
+              "showLineNumbers": false,
+              "showMiniMap": false
+            },
             "content": "Drilldown row is used by other panels on the dashboard to show more detailed metrics per-instance.",
             "mode": "markdown"
           },
-          "pluginVersion": "9.1.0",
+          "pluginVersion": "9.2.6",
           "transparent": true,
           "type": "text"
         },
@@ -7966,7 +8019,8 @@
                 "mode": "absolute",
                 "steps": [
                   {
-                    "color": "green"
+                    "color": "green",
+                    "value": null
                   }
                 ]
               },
@@ -8067,7 +8121,8 @@
                 "mode": "absolute",
                 "steps": [
                   {
-                    "color": "green"
+                    "color": "green",
+                    "value": null
                   }
                 ]
               },
@@ -8168,7 +8223,8 @@
                 "mode": "absolute",
                 "steps": [
                   {
-                    "color": "green"
+                    "color": "green",
+                    "value": null
                   }
                 ]
               },
@@ -8271,7 +8327,8 @@
                 "mode": "absolute",
                 "steps": [
                   {
-                    "color": "green"
+                    "color": "green",
+                    "value": null
                   },
                   {
                     "color": "red",
@@ -8328,6 +8385,224 @@
           ],
           "title": "Storage full ETA ($instance)",
           "type": "timeseries"
+        },
+        {
+          "datasource": {
+            "type": "prometheus",
+            "uid": "$ds"
+          },
+          "description": "Shows the percentage of used disk space. It is recommended to have at least 20% of free disk space for the best performance.",
+          "fieldConfig": {
+            "defaults": {
+              "color": {
+                "mode": "palette-classic"
+              },
+              "custom": {
+                "axisCenteredZero": false,
+                "axisColorMode": "text",
+                "axisLabel": "",
+                "axisPlacement": "auto",
+                "barAlignment": 0,
+                "drawStyle": "line",
+                "fillOpacity": 0,
+                "gradientMode": "none",
+                "hideFrom": {
+                  "legend": false,
+                  "tooltip": false,
+                  "viz": false
+                },
+                "lineInterpolation": "linear",
+                "lineWidth": 1,
+                "pointSize": 5,
+                "scaleDistribution": {
+                  "type": "linear"
+                },
+                "showPoints": "never",
+                "spanNulls": false,
+                "stacking": {
+                  "group": "A",
+                  "mode": "none"
+                },
+                "thresholdsStyle": {
+                  "mode": "line"
+                }
+              },
+              "links": [],
+              "mappings": [],
+              "min": 0,
+              "thresholds": {
+                "mode": "absolute",
+                "steps": [
+                  {
+                    "color": "green",
+                    "value": null
+                  }
+                ]
+              },
+              "unit": "decbytes"
+            },
+            "overrides": []
+          },
+          "gridPos": {
+            "h": 8,
+            "w": 12,
+            "x": 0,
+            "y": 26
+          },
+          "id": 200,
+          "links": [],
+          "options": {
+            "legend": {
+              "calcs": [
+                "mean",
+                "lastNotNull",
+                "max"
+              ],
+              "displayMode": "table",
+              "placement": "bottom",
+              "showLegend": true,
+              "sortBy": "Last *",
+              "sortDesc": true
+            },
+            "tooltip": {
+              "mode": "multi",
+              "sort": "desc"
+            }
+          },
+          "pluginVersion": "9.1.0",
+          "targets": [
+            {
+              "datasource": {
+                "type": "prometheus",
+                "uid": "$ds"
+              },
+              "editorMode": "code",
+              "expr": "sum(vm_data_size_bytes{job=~\"$job\", instance=~\"$instance\"}) by(job, instance)",
+              "format": "time_series",
+              "intervalFactor": 1,
+              "legendFormat": "",
+              "range": true,
+              "refId": "A"
+            }
+          ],
+          "title": "Disk space usage ($instance)",
+          "type": "timeseries"
+        },
+        {
+          "datasource": {
+            "type": "prometheus",
+            "uid": "$ds"
+          },
+          "description": "",
+          "fieldConfig": {
+            "defaults": {
+              "color": {
+                "mode": "palette-classic"
+              },
+              "custom": {
+                "axisCenteredZero": false,
+                "axisColorMode": "text",
+                "axisLabel": "",
+                "axisPlacement": "auto",
+                "barAlignment": 0,
+                "drawStyle": "line",
+                "fillOpacity": 0,
+                "gradientMode": "none",
+                "hideFrom": {
+                  "legend": false,
+                  "tooltip": false,
+                  "viz": false
+                },
+                "lineInterpolation": "linear",
+                "lineWidth": 1,
+                "pointSize": 5,
+                "scaleDistribution": {
+                  "type": "linear"
+                },
+                "showPoints": "never",
+                "spanNulls": false,
+                "stacking": {
+                  "group": "A",
+                  "mode": "none"
+                },
+                "thresholdsStyle": {
+                  "mode": "line"
+                }
+              },
+              "links": [],
+              "mappings": [],
+              "min": 0,
+              "thresholds": {
+                "mode": "absolute",
+                "steps": [
+                  {
+                    "color": "green",
+                    "value": null
+                  }
+                ]
+              },
+              "unit": "decbytes"
+            },
+            "overrides": []
+          },
+          "gridPos": {
+            "h": 8,
+            "w": 12,
+            "x": 12,
+            "y": 26
+          },
+          "id": 201,
+          "links": [],
+          "options": {
+            "legend": {
+              "calcs": [
+                "mean",
+                "lastNotNull",
+                "max"
+              ],
+              "displayMode": "table",
+              "placement": "bottom",
+              "showLegend": true,
+              "sortBy": "Last *",
+              "sortDesc": true
+            },
+            "tooltip": {
+              "mode": "multi",
+              "sort": "desc"
+            }
+          },
+          "pluginVersion": "9.1.0",
+          "targets": [
+            {
+              "datasource": {
+                "type": "prometheus",
+                "uid": "$ds"
+              },
+              "editorMode": "code",
+              "expr": "sum(vm_data_size_bytes{job=~\"$job\", instance=~\"$instance\", type=\"indexdb\"}) by(job, instance)",
+              "format": "time_series",
+              "intervalFactor": 1,
+              "legendFormat": "{{job}}:{{instance}} (indexdb)",
+              "range": true,
+              "refId": "A"
+            },
+            {
+              "datasource": {
+                "type": "prometheus",
+                "uid": "$ds"
+              },
+              "editorMode": "code",
+              "expr": "sum(vm_data_size_bytes{job=~\"$job\", instance=~\"$instance\", type!=\"indexdb\"}) by(job, instance)",
+              "format": "time_series",
+              "hide": false,
+              "intervalFactor": 1,
+              "legendFormat": "{{job}}:{{instance}} (datapoints)",
+              "range": true,
+              "refId": "B"
+            }
+          ],
+          "title": "Disk space usage by type ($instance)",
+          "type": "timeseries"
         }
       ],
       "title": "Drilldown",
diff --git a/deployment/docker/alerts-cluster.yml b/deployment/docker/alerts-cluster.yml
index 1a99a08fb..15c305452 100644
--- a/deployment/docker/alerts-cluster.yml
+++ b/deployment/docker/alerts-cluster.yml
@@ -43,7 +43,7 @@ groups:
         labels:
           severity: critical
         annotations:
-          dashboard: http://localhost:3000/d/oS7Bi_0Wz?viewPanel=110&var-instance={{ $labels.instance }}"
+          dashboard: http://localhost:3000/d/oS7Bi_0Wz?viewPanel=200&var-instance={{ $labels.instance }}"
           summary: "Instance {{ $labels.instance }} will run out of disk space soon"
           description: "Disk utilisation on instance {{ $labels.instance }} is more than 80%.\n
             Having less than 20% of free disk space could cripple merges processes and overall performance.
diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md
index 9e81fff05..d1b5507a2 100644
--- a/docs/CHANGELOG.md
+++ b/docs/CHANGELOG.md
@@ -15,14 +15,16 @@ The following tip changes can be tested by building VictoriaMetrics components f
 
 ## tip
 
+**Update note 1:** this release drops support for direct upgrade from VictoriaMetrics versions prior [v1.28.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.28.0). Please upgrade to `v1.84.0`, wait until `finished round 2 of background conversion` line is emitted to log by single-node VictoriaMetrics or by `vmstorage`, and then upgrade to newer releases.
+
 * FEATURE: [vmagent](https://docs.victoriametrics.com/vmagent.html): improve [service discovery](https://docs.victoriametrics.com/sd_configs.html) performance when discovering big number of targets (10K and more).
 * FEATURE: [vmagent](https://docs.victoriametrics.com/vmagent.html): add `exported_` prefix to metric names exported by scrape targets if these metric names clash with [automatically generated metrics](https://docs.victoriametrics.com/vmagent.html#automatically-generated-metrics) such as `up`, `scrape_samples_scraped`, etc. This prevents from corruption of automatically generated metrics. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3406).
 * FEATURE: [VictoriaMetrics cluster](https://docs.victoriametrics.com/Cluster-VictoriaMetrics.html): improve error message when the requested path cannot be properly parsed, so users could identify the issue and properly fix the path. Now the error message links to [url format docs](https://docs.victoriametrics.com/Cluster-VictoriaMetrics.html#url-format). See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3402).
 * FEATURE: [vmctl](https://docs.victoriametrics.com/vmctl.html): add ability to copy data from sources via Prometheus `remote_read` protocol. See [these docs](https://docs.victoriametrics.com/vmctl.html#migrating-data-by-remote-read-protocol). The related issues: [one](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3132) and [two](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1101).
-* FEATURE: [vmalert](https://docs.victoriametrics.com/vmalert.html): add `remoteWrite.sendTimeout` command-line flag to configure timeout for sending data to remote write URL. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3408)
+* FEATURE: [vmalert](https://docs.victoriametrics.com/vmalert.html): add `-remoteWrite.sendTimeout` command-line flag, which allows configuring timeout for sending data to `-remoteWrite.url`. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3408).
 
 * BUGFIX: [vmalert](https://docs.victoriametrics.com/vmalert.html): properly pass HTTP headers during the alert state restore procedure. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3418).
-* BUGFIX: [vmalert](https://docs.victoriametrics.com/vmalert.html): properly specify rule evaluation step during the [replay mode](https://docs.victoriametrics.com/vmalert.html#rules-backfilling). The `step` value was previously overriden by `datasource.queryStep` flag.
+* BUGFIX: [vmalert](https://docs.victoriametrics.com/vmalert.html): properly specify rule evaluation step during the [replay mode](https://docs.victoriametrics.com/vmalert.html#rules-backfilling). The `step` value was previously overriden by `-datasource.queryStep` command-line flag.
 
 
 ## [v1.84.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.84.0)
diff --git a/docs/vmalert.md b/docs/vmalert.md
index 44dd90be4..f73362857 100644
--- a/docs/vmalert.md
+++ b/docs/vmalert.md
@@ -1054,6 +1054,8 @@ The shortlist of configuration flags is the following:
      Optional OAuth2 scopes to use for -notifier.url. Scopes must be delimited by ';'.
   -remoteWrite.oauth2.tokenUrl string
      Optional OAuth2 tokenURL to use for -notifier.url.
+  -remoteWrite.sendTimeout duration
+     Timeout for sending data to the configured -remoteWrite.url. (default 30s)
   -remoteWrite.showURL
      Whether to show -remoteWrite.url in the exported metrics. It is hidden by default, since it can contain sensitive info such as auth key
   -remoteWrite.tlsCAFile string
diff --git a/lib/backup/fscommon/fscommon.go b/lib/backup/fscommon/fscommon.go
index fd9e3506b..37d688aeb 100644
--- a/lib/backup/fscommon/fscommon.go
+++ b/lib/backup/fscommon/fscommon.go
@@ -45,7 +45,7 @@ func fsync(path string) error {
 func AppendFiles(dst []string, dir string) ([]string, error) {
 	d, err := os.Open(dir)
 	if err != nil {
-		return nil, fmt.Errorf("cannot open %q: %w", dir, err)
+		return nil, fmt.Errorf("cannot open directory: %w", err)
 	}
 	dst, err = appendFilesInternal(dst, d)
 	if err1 := d.Close(); err1 != nil {
diff --git a/lib/backup/fslocal/fslocal.go b/lib/backup/fslocal/fslocal.go
index 051182a62..055c38123 100644
--- a/lib/backup/fslocal/fslocal.go
+++ b/lib/backup/fslocal/fslocal.go
@@ -159,7 +159,7 @@ func (fs *FS) DeletePath(path string) (uint64, error) {
 			// The file could be deleted earlier via symlink.
 			return 0, nil
 		}
-		return 0, fmt.Errorf("cannot open %q at %q: %w", path, fullPath, err)
+		return 0, fmt.Errorf("cannot open %q: %w", path, err)
 	}
 	fi, err := f.Stat()
 	_ = f.Close()
diff --git a/lib/backup/fsremote/fsremote.go b/lib/backup/fsremote/fsremote.go
index 4e4939f91..d2a7ce851 100644
--- a/lib/backup/fsremote/fsremote.go
+++ b/lib/backup/fsremote/fsremote.go
@@ -107,12 +107,12 @@ func (fs *FS) CopyPart(srcFS common.OriginFS, p common.Part) error {
 	// Cannot create hardlink. Just copy file contents
 	srcFile, err := os.Open(srcPath)
 	if err != nil {
-		return fmt.Errorf("cannot open file %q: %w", srcPath, err)
+		return fmt.Errorf("cannot open source file: %w", err)
 	}
 	dstFile, err := os.Create(dstPath)
 	if err != nil {
 		_ = srcFile.Close()
-		return fmt.Errorf("cannot create file %q: %w", dstPath, err)
+		return fmt.Errorf("cannot create destination file: %w", err)
 	}
 	n, err := io.Copy(dstFile, srcFile)
 	if err1 := dstFile.Close(); err1 != nil {
@@ -141,7 +141,7 @@ func (fs *FS) DownloadPart(p common.Part, w io.Writer) error {
 	path := fs.path(p)
 	r, err := os.Open(path)
 	if err != nil {
-		return fmt.Errorf("cannot open %q: %w", path, err)
+		return err
 	}
 	n, err := io.Copy(w, r)
 	if err1 := r.Close(); err1 != nil && err == nil {
diff --git a/lib/filestream/filestream.go b/lib/filestream/filestream.go
index 8af10f5ce..2f0e4e595 100644
--- a/lib/filestream/filestream.go
+++ b/lib/filestream/filestream.go
@@ -79,7 +79,7 @@ func OpenReaderAt(path string, offset int64, nocache bool) (*Reader, error) {
 func Open(path string, nocache bool) (*Reader, error) {
 	f, err := os.Open(path)
 	if err != nil {
-		return nil, fmt.Errorf("cannot open file %q: %w", path, err)
+		return nil, err
 	}
 	r := &Reader{
 		f:  f,
@@ -179,7 +179,7 @@ type Writer struct {
 func OpenWriterAt(path string, offset int64, nocache bool) (*Writer, error) {
 	f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE, 0600)
 	if err != nil {
-		return nil, fmt.Errorf("cannot open %q: %w", path, err)
+		return nil, err
 	}
 	n, err := f.Seek(offset, io.SeekStart)
 	if err != nil {
diff --git a/lib/fs/fs.go b/lib/fs/fs.go
index 14ae2d682..92ae1e67d 100644
--- a/lib/fs/fs.go
+++ b/lib/fs/fs.go
@@ -25,11 +25,38 @@ func MustSyncPath(path string) {
 	mustSyncPath(path)
 }
 
+// WriteFileAndSync writes data to the file at path and then calls fsync on the created file.
+//
+// The fsync guarantees that the written data survives hardware reset after successful call.
+//
+// This function may leave the file at the path in inconsistent state on app crash
+// in the middle of the write.
+// Use WriteFileAtomically if the file at the path must be either written in full
+// or not written at all on app crash in the middle of the write.
+func WriteFileAndSync(path string, data []byte) error {
+	f, err := filestream.Create(path, false)
+	if err != nil {
+		return err
+	}
+	if _, err := f.Write(data); err != nil {
+		f.MustClose()
+		// Do not call MustRemoveAll(path), so the user could inpsect
+		// the file contents during investigation of the issue.
+		return fmt.Errorf("cannot write %d bytes to %q: %w", len(data), path, err)
+	}
+	// Sync and close the file.
+	f.MustClose()
+	return nil
+}
+
 // WriteFileAtomically atomically writes data to the given file path.
 //
-// WriteFileAtomically returns only after the file is fully written and synced
+// This function returns only after the file is fully written and synced
 // to the underlying storage.
 //
+// This function guarantees that the file at path either fully written or not written at all on app crash
+// in the middle of the write.
+//
 // If the file at path already exists, then the file is overwritten atomically if canOverwrite is true.
 // Otherwise error is returned.
 func WriteFileAtomically(path string, data []byte, canOverwrite bool) error {
@@ -40,26 +67,18 @@ func WriteFileAtomically(path string, data []byte, canOverwrite bool) error {
 		return fmt.Errorf("cannot create file %q, since it already exists", path)
 	}
 
+	// Write data to a temporary file.
 	n := atomic.AddUint64(&tmpFileNum, 1)
 	tmpPath := fmt.Sprintf("%s.tmp.%d", path, n)
-	f, err := filestream.Create(tmpPath, false)
-	if err != nil {
-		return fmt.Errorf("cannot create file %q: %w", tmpPath, err)
-	}
-	if _, err := f.Write(data); err != nil {
-		f.MustClose()
-		MustRemoveAll(tmpPath)
-		return fmt.Errorf("cannot write %d bytes to file %q: %w", len(data), tmpPath, err)
+	if err := WriteFileAndSync(tmpPath, data); err != nil {
+		return fmt.Errorf("cannot write data to temporary file: %w", err)
 	}
 
-	// Sync and close the file.
-	f.MustClose()
-
-	// Atomically move the file from tmpPath to path.
+	// Atomically move the temporary file from tmpPath to path.
 	if err := os.Rename(tmpPath, path); err != nil {
 		// do not call MustRemoveAll(tmpPath) here, so the user could inspect
-		// the file contents during investigating the issue.
-		return fmt.Errorf("cannot move %q to %q: %w", tmpPath, path, err)
+		// the file contents during investigation of the issue.
+		return fmt.Errorf("cannot move temporary file %q to %q: %w", tmpPath, path, err)
 	}
 
 	// Sync the containing directory, so the file is guaranteed to appear in the directory.
@@ -123,7 +142,7 @@ func RemoveDirContents(dir string) {
 	}
 	d, err := os.Open(dir)
 	if err != nil {
-		logger.Panicf("FATAL: cannot open dir %q: %s", dir, err)
+		logger.Panicf("FATAL: cannot open dir: %s", err)
 	}
 	defer MustClose(d)
 	names, err := d.Readdirnames(-1)
@@ -185,7 +204,7 @@ func IsEmptyDir(path string) bool {
 	// See https://stackoverflow.com/a/30708914/274937
 	f, err := os.Open(path)
 	if err != nil {
-		logger.Panicf("FATAL: unexpected error when opening directory %q: %s", path, err)
+		logger.Panicf("FATAL: cannot open dir: %s", err)
 	}
 	_, err = f.Readdirnames(1)
 	MustClose(f)
@@ -230,7 +249,7 @@ var atomicDirRemoveCounter = uint64(time.Now().UnixNano())
 func MustRemoveTemporaryDirs(dir string) {
 	d, err := os.Open(dir)
 	if err != nil {
-		logger.Panicf("FATAL: cannot open dir %q: %s", dir, err)
+		logger.Panicf("FATAL: cannot open dir: %s", err)
 	}
 	defer MustClose(d)
 	fis, err := d.Readdir(-1)
@@ -259,7 +278,7 @@ func HardLinkFiles(srcDir, dstDir string) error {
 
 	d, err := os.Open(srcDir)
 	if err != nil {
-		return fmt.Errorf("cannot open srcDir=%q: %w", srcDir, err)
+		return fmt.Errorf("cannot open srcDir: %w", err)
 	}
 	defer func() {
 		if err := d.Close(); err != nil {
diff --git a/lib/fs/fs_solaris.go b/lib/fs/fs_solaris.go
index 8cddca829..ac94ea406 100644
--- a/lib/fs/fs_solaris.go
+++ b/lib/fs/fs_solaris.go
@@ -19,7 +19,7 @@ func mUnmap(data []byte) error {
 func mustSyncPath(path string) {
 	d, err := os.Open(path)
 	if err != nil {
-		logger.Panicf("FATAL: cannot open %q: %s", path, err)
+		logger.Panicf("FATAL: cannot open file for fsync: %s", err)
 	}
 	if err := d.Sync(); err != nil {
 		_ = d.Close()
@@ -51,7 +51,7 @@ func createFlockFile(flockFile string) (*os.File, error) {
 func mustGetFreeSpace(path string) uint64 {
 	d, err := os.Open(path)
 	if err != nil {
-		logger.Panicf("FATAL: cannot determine free disk space on %q: %s", path, err)
+		logger.Panicf("FATAL: cannot open dir for determining free disk space: %s", err)
 	}
 	defer MustClose(d)
 
diff --git a/lib/fs/fs_unix.go b/lib/fs/fs_unix.go
index 20cf6f6c0..bcb789c94 100644
--- a/lib/fs/fs_unix.go
+++ b/lib/fs/fs_unix.go
@@ -22,7 +22,7 @@ func mUnmap(data []byte) error {
 func mustSyncPath(path string) {
 	d, err := os.Open(path)
 	if err != nil {
-		logger.Panicf("FATAL: cannot open %q: %s", path, err)
+		logger.Panicf("FATAL: cannot open file for fsync: %s", err)
 	}
 	if err := d.Sync(); err != nil {
 		_ = d.Close()
@@ -47,7 +47,7 @@ func createFlockFile(flockFile string) (*os.File, error) {
 func mustGetFreeSpace(path string) uint64 {
 	d, err := os.Open(path)
 	if err != nil {
-		logger.Panicf("FATAL: cannot determine free disk space on %q: %s", path, err)
+		logger.Panicf("FATAL: cannot open dir for determining free disk space: %s", err)
 	}
 	defer MustClose(d)
 
diff --git a/lib/fs/reader_at.go b/lib/fs/reader_at.go
index 53ccb44c3..abc1c4699 100644
--- a/lib/fs/reader_at.go
+++ b/lib/fs/reader_at.go
@@ -89,7 +89,7 @@ func (r *ReaderAt) MustFadviseSequentialRead(prefetch bool) {
 func MustOpenReaderAt(path string) *ReaderAt {
 	f, err := os.Open(path)
 	if err != nil {
-		logger.Panicf("FATAL: cannot open file %q for reading: %s", path, err)
+		logger.Panicf("FATAL: cannot open file for reading: %s", err)
 	}
 	var r ReaderAt
 	r.f = f
diff --git a/lib/mergeset/block_stream_writer.go b/lib/mergeset/block_stream_writer.go
index b25e47325..9c348fbc2 100644
--- a/lib/mergeset/block_stream_writer.go
+++ b/lib/mergeset/block_stream_writer.go
@@ -63,13 +63,10 @@ func (bsw *blockStreamWriter) reset() {
 	bsw.mrFirstItemCaught = false
 }
 
-func (bsw *blockStreamWriter) InitFromInmemoryPart(mp *inmemoryPart) {
+func (bsw *blockStreamWriter) InitFromInmemoryPart(mp *inmemoryPart, compressLevel int) {
 	bsw.reset()
 
-	// Use the minimum compression level for in-memory blocks,
-	// since they are going to be re-compressed during the merge into file-based blocks.
-	bsw.compressLevel = -5 // See https://github.com/facebook/zstd/releases/tag/v1.3.4
-
+	bsw.compressLevel = compressLevel
 	bsw.metaindexWriter = &mp.metaindexData
 	bsw.indexWriter = &mp.indexData
 	bsw.itemsWriter = &mp.itemsData
diff --git a/lib/mergeset/encoding.go b/lib/mergeset/encoding.go
index 09d1e0f76..b93383e81 100644
--- a/lib/mergeset/encoding.go
+++ b/lib/mergeset/encoding.go
@@ -47,7 +47,9 @@ func (it Item) String(data []byte) string {
 	return *(*string)(unsafe.Pointer(sh))
 }
 
-func (ib *inmemoryBlock) Len() int { return len(ib.items) }
+func (ib *inmemoryBlock) Len() int {
+	return len(ib.items)
+}
 
 func (ib *inmemoryBlock) Less(i, j int) bool {
 	items := ib.items
diff --git a/lib/mergeset/inmemory_part.go b/lib/mergeset/inmemory_part.go
index 9b41faca8..c3caca201 100644
--- a/lib/mergeset/inmemory_part.go
+++ b/lib/mergeset/inmemory_part.go
@@ -60,14 +60,14 @@ func (mp *inmemoryPart) Init(ib *inmemoryBlock) {
 
 	bb := inmemoryPartBytePool.Get()
 	bb.B = mp.bh.Marshal(bb.B[:0])
-	mp.indexData.B = encoding.CompressZSTDLevel(mp.indexData.B[:0], bb.B, 0)
+	mp.indexData.B = encoding.CompressZSTDLevel(mp.indexData.B[:0], bb.B, compressLevel)
 
 	mp.mr.firstItem = append(mp.mr.firstItem[:0], mp.bh.firstItem...)
 	mp.mr.blockHeadersCount = 1
 	mp.mr.indexBlockOffset = 0
 	mp.mr.indexBlockSize = uint32(len(mp.indexData.B))
 	bb.B = mp.mr.Marshal(bb.B[:0])
-	mp.metaindexData.B = encoding.CompressZSTDLevel(mp.metaindexData.B[:0], bb.B, 0)
+	mp.metaindexData.B = encoding.CompressZSTDLevel(mp.metaindexData.B[:0], bb.B, compressLevel)
 	inmemoryPartBytePool.Put(bb)
 }
 
@@ -76,9 +76,8 @@ var inmemoryPartBytePool bytesutil.ByteBufferPool
 // It is safe calling NewPart multiple times.
 // It is unsafe re-using mp while the returned part is in use.
 func (mp *inmemoryPart) NewPart() *part {
-	ph := mp.ph
 	size := mp.size()
-	p, err := newPart(&ph, "", size, mp.metaindexData.NewReader(), &mp.indexData, &mp.itemsData, &mp.lensData)
+	p, err := newPart(&mp.ph, "", size, mp.metaindexData.NewReader(), &mp.indexData, &mp.itemsData, &mp.lensData)
 	if err != nil {
 		logger.Panicf("BUG: cannot create a part from inmemoryPart: %s", err)
 	}
@@ -86,5 +85,5 @@ func (mp *inmemoryPart) NewPart() *part {
 }
 
 func (mp *inmemoryPart) size() uint64 {
-	return uint64(len(mp.metaindexData.B) + len(mp.indexData.B) + len(mp.itemsData.B) + len(mp.lensData.B))
+	return uint64(cap(mp.metaindexData.B) + cap(mp.indexData.B) + cap(mp.itemsData.B) + cap(mp.lensData.B))
 }
diff --git a/lib/mergeset/table.go b/lib/mergeset/table.go
index d4d5f3620..d88cba7cb 100644
--- a/lib/mergeset/table.go
+++ b/lib/mergeset/table.go
@@ -107,12 +107,7 @@ type Table struct {
 
 	stopCh chan struct{}
 
-	// Use syncwg instead of sync, since Add/Wait may be called from concurrent goroutines.
-	partMergersWG syncwg.WaitGroup
-
-	rawItemsFlusherWG sync.WaitGroup
-
-	convertersWG sync.WaitGroup
+	wg sync.WaitGroup
 
 	// Use syncwg instead of sync, since Add/Wait may be called from concurrent goroutines.
 	rawItemsPendingFlushesWG syncwg.WaitGroup
@@ -143,12 +138,11 @@ func (riss *rawItemsShards) init() {
 	riss.shards = make([]rawItemsShard, rawItemsShardsPerTable)
 }
 
-func (riss *rawItemsShards) addItems(tb *Table, items [][]byte) error {
+func (riss *rawItemsShards) addItems(tb *Table, items [][]byte) {
 	n := atomic.AddUint32(&riss.shardIdx, 1)
 	shards := riss.shards
 	idx := n % uint32(len(shards))
-	shard := &shards[idx]
-	return shard.addItems(tb, items)
+	shards[idx].addItems(tb, items)
 }
 
 func (riss *rawItemsShards) Len() int {
@@ -185,8 +179,7 @@ func (ris *rawItemsShard) Len() int {
 	return n
 }
 
-func (ris *rawItemsShard) addItems(tb *Table, items [][]byte) error {
-	var err error
+func (ris *rawItemsShard) addItems(tb *Table, items [][]byte) {
 	var blocksToFlush []*inmemoryBlock
 
 	ris.mu.Lock()
@@ -198,17 +191,18 @@ func (ris *rawItemsShard) addItems(tb *Table, items [][]byte) error {
 	}
 	ib := ibs[len(ibs)-1]
 	for _, item := range items {
-		if !ib.Add(item) {
-			ib = getInmemoryBlock()
-			if !ib.Add(item) {
-				putInmemoryBlock(ib)
-				err = fmt.Errorf("cannot insert an item %q into an empty inmemoryBlock; it looks like the item is too large? len(item)=%d", item, len(item))
-				break
-			}
+		if ib.Add(item) {
+			continue
+		}
+		ib = getInmemoryBlock()
+		if ib.Add(item) {
 			ibs = append(ibs, ib)
-			ris.ibs = ibs
+			continue
 		}
+		putInmemoryBlock(ib)
+		logger.Panicf("BUG: cannot insert too big item into an empty inmemoryBlock len(item)=%d; the caller should be responsible for avoiding too big items", len(item))
 	}
+	ris.ibs = ibs
 	if len(ibs) >= maxBlocksPerShard {
 		blocksToFlush = append(blocksToFlush, ibs...)
 		for i := range ibs {
@@ -220,7 +214,6 @@ func (ris *rawItemsShard) addItems(tb *Table, items [][]byte) error {
 	ris.mu.Unlock()
 
 	tb.mergeRawItemsBlocks(blocksToFlush, false)
-	return err
 }
 
 type partWrapper struct {
@@ -298,20 +291,13 @@ func OpenTable(path string, flushCallback func(), prepareBlock PrepareBlockCallb
 		stopCh:        make(chan struct{}),
 	}
 	tb.rawItems.init()
-	tb.startPartMergers()
-	tb.startRawItemsFlusher()
+	tb.startBackgroundWorkers()
 
 	var m TableMetrics
 	tb.UpdateMetrics(&m)
 	logger.Infof("table %q has been opened in %.3f seconds; partsCount: %d; blocksCount: %d, itemsCount: %d; sizeBytes: %d",
 		path, time.Since(startTime).Seconds(), m.PartsCount, m.BlocksCount, m.ItemsCount, m.SizeBytes)
 
-	tb.convertersWG.Add(1)
-	go func() {
-		tb.convertToV1280()
-		tb.convertersWG.Done()
-	}()
-
 	if flushCallback != nil {
 		tb.flushCallbackWorkerWG.Add(1)
 		go func() {
@@ -336,30 +322,25 @@ func OpenTable(path string, flushCallback func(), prepareBlock PrepareBlockCallb
 	return tb, nil
 }
 
+func (tb *Table) startBackgroundWorkers() {
+	tb.startPartMergers()
+	tb.startRawItemsFlusher()
+}
+
 // MustClose closes the table.
 func (tb *Table) MustClose() {
 	close(tb.stopCh)
 
-	logger.Infof("waiting for raw items flusher to stop on %q...", tb.path)
+	logger.Infof("waiting for background workers to stop on %q...", tb.path)
 	startTime := time.Now()
-	tb.rawItemsFlusherWG.Wait()
-	logger.Infof("raw items flusher stopped in %.3f seconds on %q", time.Since(startTime).Seconds(), tb.path)
-
-	logger.Infof("waiting for converters to stop on %q...", tb.path)
-	startTime = time.Now()
-	tb.convertersWG.Wait()
-	logger.Infof("converters stopped in %.3f seconds on %q", time.Since(startTime).Seconds(), tb.path)
-
-	logger.Infof("waiting for part mergers to stop on %q...", tb.path)
-	startTime = time.Now()
-	tb.partMergersWG.Wait()
-	logger.Infof("part mergers stopped in %.3f seconds on %q", time.Since(startTime).Seconds(), tb.path)
+	tb.wg.Wait()
+	logger.Infof("background workers stopped in %.3f seconds on %q", time.Since(startTime).Seconds(), tb.path)
 
 	logger.Infof("flushing inmemory parts to files on %q...", tb.path)
 	startTime = time.Now()
 
 	// Flush raw items the last time before exit.
-	tb.flushRawItems(true)
+	tb.flushPendingItems(true)
 
 	// Flush inmemory parts to disk.
 	var pws []*partWrapper
@@ -376,7 +357,7 @@ func (tb *Table) MustClose() {
 	}
 	tb.partsLock.Unlock()
 
-	if err := tb.mergePartsOptimal(pws, nil); err != nil {
+	if err := tb.mergePartsOptimal(pws); err != nil {
 		logger.Panicf("FATAL: cannot flush inmemory parts to files in %q: %s", tb.path, err)
 	}
 	logger.Infof("%d inmemory parts have been flushed to files in %.3f seconds on %q", len(pws), time.Since(startTime).Seconds(), tb.path)
@@ -478,17 +459,17 @@ func (tb *Table) UpdateMetrics(m *TableMetrics) {
 }
 
 // AddItems adds the given items to the tb.
-func (tb *Table) AddItems(items [][]byte) error {
-	if err := tb.rawItems.addItems(tb, items); err != nil {
-		return fmt.Errorf("cannot insert data into %q: %w", tb.path, err)
-	}
+//
+// The function panics when items contains an item with length exceeding maxInmemoryBlockSize.
+// It is caller's responsibility to make sure there are no too long items.
+func (tb *Table) AddItems(items [][]byte) {
+	tb.rawItems.addItems(tb, items)
 	atomic.AddUint64(&tb.itemsAdded, uint64(len(items)))
 	n := 0
 	for _, item := range items {
 		n += len(item)
 	}
 	atomic.AddUint64(&tb.itemsAddedSizeBytes, uint64(n))
-	return nil
 }
 
 // getParts appends parts snapshot to dst and returns it.
@@ -513,10 +494,10 @@ func (tb *Table) putParts(pws []*partWrapper) {
 }
 
 func (tb *Table) startRawItemsFlusher() {
-	tb.rawItemsFlusherWG.Add(1)
+	tb.wg.Add(1)
 	go func() {
 		tb.rawItemsFlusher()
-		tb.rawItemsFlusherWG.Done()
+		tb.wg.Done()
 	}()
 }
 
@@ -528,84 +509,24 @@ func (tb *Table) rawItemsFlusher() {
 		case <-tb.stopCh:
 			return
 		case <-ticker.C:
-			tb.flushRawItems(false)
+			tb.flushPendingItems(false)
 		}
 	}
 }
 
-const convertToV1280FileName = "converted-to-v1.28.0"
-
-func (tb *Table) convertToV1280() {
-	// Convert tag->metricID rows into tag->metricIDs rows when upgrading to v1.28.0+.
-	flagFilePath := tb.path + "/" + convertToV1280FileName
-	if fs.IsPathExist(flagFilePath) {
-		// The conversion has been already performed.
-		return
-	}
-
-	getAllPartsForMerge := func() []*partWrapper {
-		var pws []*partWrapper
-		tb.partsLock.Lock()
-		for _, pw := range tb.parts {
-			if pw.isInMerge {
-				continue
-			}
-			pw.isInMerge = true
-			pws = append(pws, pw)
-		}
-		tb.partsLock.Unlock()
-		return pws
-	}
-	pws := getAllPartsForMerge()
-	if len(pws) > 0 {
-		logger.Infof("started round 1 of background conversion of %q to v1.28.0 format; merge %d parts", tb.path, len(pws))
-		startTime := time.Now()
-		if err := tb.mergePartsOptimal(pws, tb.stopCh); err != nil {
-			logger.Errorf("failed round 1 of background conversion of %q to v1.28.0 format: %s", tb.path, err)
-			return
-		}
-		logger.Infof("finished round 1 of background conversion of %q to v1.28.0 format in %.3f seconds", tb.path, time.Since(startTime).Seconds())
-
-		// The second round is needed in order to merge small blocks
-		// with tag->metricIDs rows left after the first round.
-		pws = getAllPartsForMerge()
-		logger.Infof("started round 2 of background conversion of %q to v1.28.0 format; merge %d parts", tb.path, len(pws))
-		startTime = time.Now()
-		if len(pws) > 0 {
-			if err := tb.mergePartsOptimal(pws, tb.stopCh); err != nil {
-				logger.Errorf("failed round 2 of background conversion of %q to v1.28.0 format: %s", tb.path, err)
-				return
-			}
-		}
-		logger.Infof("finished round 2 of background conversion of %q to v1.28.0 format in %.3f seconds", tb.path, time.Since(startTime).Seconds())
-	}
-
-	if err := fs.WriteFileAtomically(flagFilePath, []byte("ok"), false); err != nil {
-		logger.Panicf("FATAL: cannot create %q: %s", flagFilePath, err)
-	}
-}
-
-func (tb *Table) mergePartsOptimal(pws []*partWrapper, stopCh <-chan struct{}) error {
-	defer func() {
-		// Remove isInMerge flag from pws.
-		tb.partsLock.Lock()
-		for _, pw := range pws {
-			// Do not check for pws.isInMerge set to false,
-			// since it may be set to false in mergeParts below.
-			pw.isInMerge = false
-		}
-		tb.partsLock.Unlock()
-	}()
+func (tb *Table) mergePartsOptimal(pws []*partWrapper) error {
 	for len(pws) > defaultPartsToMerge {
-		if err := tb.mergeParts(pws[:defaultPartsToMerge], stopCh, false); err != nil {
+		pwsChunk := pws[:defaultPartsToMerge]
+		pws = pws[defaultPartsToMerge:]
+		if err := tb.mergeParts(pwsChunk, nil, false); err != nil {
+			tb.releasePartsToMerge(pws)
 			return fmt.Errorf("cannot merge %d parts: %w", defaultPartsToMerge, err)
 		}
-		pws = pws[defaultPartsToMerge:]
 	}
 	if len(pws) == 0 {
 		return nil
 	}
-	if err := tb.mergeParts(pws, stopCh, false); err != nil {
+	if err := tb.mergeParts(pws, nil, false); err != nil {
 		return fmt.Errorf("cannot merge %d parts: %w", len(pws), err)
 	}
 	return nil
@@ -616,13 +537,13 @@ func (tb *Table) mergePartsOptimal(pws []*partWrapper, stopCh <-chan struct{}) e
 //
 // This function is only for debugging and testing.
 func (tb *Table) DebugFlush() {
-	tb.flushRawItems(true)
+	tb.flushPendingItems(true)
 
 	// Wait for background flushers to finish.
 	tb.rawItemsPendingFlushesWG.Wait()
 }
 
-func (tb *Table) flushRawItems(isFinal bool) {
+func (tb *Table) flushPendingItems(isFinal bool) {
 	tb.rawItems.flush(tb, isFinal)
 }
 
@@ -665,8 +586,6 @@ func (tb *Table) mergeRawItemsBlocks(ibs []*inmemoryBlock, isFinal bool) {
 	if len(ibs) == 0 {
 		return
 	}
-	tb.partMergersWG.Add(1)
-	defer tb.partMergersWG.Done()
 
 	pws := make([]*partWrapper, 0, (len(ibs)+defaultPartsToMerge-1)/defaultPartsToMerge)
 	var pwsLock sync.Mutex
@@ -734,6 +653,11 @@ func (tb *Table) mergeInmemoryBlocks(ibs []*inmemoryBlock) *partWrapper {
 	atomic.AddUint64(&tb.activeMerges, 1)
 	defer atomic.AddUint64(&tb.activeMerges, ^uint64(0))
 
+	outItemsCount := uint64(0)
+	for _, ib := range ibs {
+		outItemsCount += uint64(ib.Len())
+	}
+
 	// Prepare blockStreamReaders for source blocks.
 	bsrs := make([]*blockStreamReader, 0, len(ibs))
 	for _, ib := range ibs {
@@ -761,9 +685,10 @@ func (tb *Table) mergeInmemoryBlocks(ibs []*inmemoryBlock) *partWrapper {
 	}
 
 	// Prepare blockStreamWriter for destination part.
+	compressLevel := getCompressLevel(outItemsCount)
 	bsw := getBlockStreamWriter()
 	mpDst := &inmemoryPart{}
-	bsw.InitFromInmemoryPart(mpDst)
+	bsw.InitFromInmemoryPart(mpDst, compressLevel)
 
 	// Merge parts.
 	// The merge shouldn't be interrupted by stopCh,
@@ -787,12 +712,12 @@ func (tb *Table) mergeInmemoryBlocks(ibs []*inmemoryBlock) *partWrapper {
 
 func (tb *Table) startPartMergers() {
 	for i := 0; i < mergeWorkersCount; i++ {
-		tb.partMergersWG.Add(1)
+		tb.wg.Add(1)
 		go func() {
 			if err := tb.partMerger(); err != nil {
 				logger.Panicf("FATAL: unrecoverable error when merging parts in %q: %s", tb.path, err)
 			}
-			tb.partMergersWG.Done()
+			tb.wg.Done()
 		}()
 	}
 }
@@ -874,6 +799,17 @@ func (tb *Table) partMerger() error {
 
 var errNothingToMerge = fmt.Errorf("nothing to merge")
 
+func (tb *Table) releasePartsToMerge(pws []*partWrapper) {
+	tb.partsLock.Lock()
+	for _, pw := range pws {
+		if !pw.isInMerge {
+			logger.Panicf("BUG: missing isInMerge flag on the part %q", pw.p.path)
+		}
+		pw.isInMerge = false
+	}
+	tb.partsLock.Unlock()
+}
+
 // mergeParts merges pws.
 //
 // Merging is immediately stopped if stopCh is closed.
@@ -884,6 +820,7 @@ func (tb *Table) mergeParts(pws []*partWrapper, stopCh <-chan struct{}, isOuterP
 		// Nothing to merge.
 		return errNothingToMerge
 	}
+	defer tb.releasePartsToMerge(pws)
 
 	atomic.AddUint64(&tb.mergesCount, 1)
 	atomic.AddUint64(&tb.activeMerges, 1)
@@ -891,18 +828,6 @@ func (tb *Table) mergeParts(pws []*partWrapper, stopCh <-chan struct{}, isOuterP
 
 	startTime := time.Now()
 
-	defer func() {
-		// Remove isInMerge flag from pws.
-		tb.partsLock.Lock()
-		for _, pw := range pws {
-			if !pw.isInMerge {
-				logger.Panicf("BUG: missing isInMerge flag on the part %q", pw.p.path)
-			}
-			pw.isInMerge = false
-		}
-		tb.partsLock.Unlock()
-	}()
-
 	// Prepare blockStreamReaders for source parts.
 	bsrs := make([]*blockStreamReader, 0, len(pws))
 	defer func() {
@@ -942,7 +867,7 @@ func (tb *Table) mergeParts(pws []*partWrapper, stopCh <-chan struct{}, isOuterP
 	mergeIdx := tb.nextMergeIdx()
 	tmpPartPath := fmt.Sprintf("%s/tmp/%016X", tb.path, mergeIdx)
 	bsw := getBlockStreamWriter()
-	compressLevel := getCompressLevelForPartItems(outItemsCount, outBlocksCount)
+	compressLevel := getCompressLevel(outItemsCount)
 	if err := bsw.InitFromFilePart(tmpPartPath, nocache, compressLevel); err != nil {
 		return fmt.Errorf("cannot create destination part %q: %w", tmpPartPath, err)
 	}
@@ -1031,9 +956,7 @@ func (tb *Table) mergeParts(pws []*partWrapper, stopCh <-chan struct{}, isOuterP
 	return nil
 }
 
-func getCompressLevelForPartItems(itemsCount, blocksCount uint64) int {
-	// There is no need in using blocksCount here, since mergeset blocks are usually full.
-
+func getCompressLevel(itemsCount uint64) int {
 	if itemsCount <= 1<<16 {
 		// -5 is the minimum supported compression for zstd.
 		// See https://github.com/facebook/zstd/releases/tag/v1.3.4
@@ -1172,7 +1095,7 @@ func (tb *Table) CreateSnapshotAt(dstDir string) error {
 	}
 
 	// Flush inmemory items to disk.
-	tb.flushRawItems(true)
+	tb.flushPendingItems(true)
 
 	// The snapshot must be created under the lock in order to prevent from
 	// concurrent modifications via runTransaction.
@@ -1196,16 +1119,7 @@ func (tb *Table) CreateSnapshotAt(dstDir string) error {
 	for _, fi := range fis {
 		fn := fi.Name()
 		if !fs.IsDirOrSymlink(fi) {
-			switch fn {
-			case convertToV1280FileName:
-				srcPath := srcDir + "/" + fn
-				dstPath := dstDir + "/" + fn
-				if err := os.Link(srcPath, dstPath); err != nil {
-					return fmt.Errorf("cannot hard link from %q to %q: %w", srcPath, dstPath, err)
-				}
-			default:
-				// Skip other non-directories.
-			}
+			// Skip non-directories.
 			continue
 		}
 		if isSpecialDir(fn) {
diff --git a/lib/promrelabel/graphite.go b/lib/promrelabel/graphite.go
index 015f6bcdc..de39634b2 100644
--- a/lib/promrelabel/graphite.go
+++ b/lib/promrelabel/graphite.go
@@ -106,7 +106,7 @@ func (gmt *graphiteMatchTemplate) Match(dst []string, s string) ([]string, bool)
 			dst = append(dst, s)
 			return dst, true
 		}
-		// Search for the the start of the next part.
+		// Search for the start of the next part.
 		p = parts[i+1]
 		i++
 		n := strings.Index(s, p)
diff --git a/lib/promscrape/config.go b/lib/promscrape/config.go
index aaa6f8a8a..eba623cae 100644
--- a/lib/promscrape/config.go
+++ b/lib/promscrape/config.go
@@ -137,7 +137,7 @@ func (cfg *Config) mustRestart(prevCfg *Config) {
 	// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2884
 	needGlobalRestart := !areEqualGlobalConfigs(&cfg.Global, &prevCfg.Global)
 
-	// Loop over the the new jobs, start new ones and restart updated ones.
+	// Loop over the new jobs, start new ones and restart updated ones.
 	var started, stopped, restarted int
 	currentJobNames := make(map[string]struct{}, len(cfg.ScrapeConfigs))
 	for i, sc := range cfg.ScrapeConfigs {
diff --git a/lib/storage/block_stream_reader.go b/lib/storage/block_stream_reader.go
index aa3149d5b..2f9e6fc59 100644
--- a/lib/storage/block_stream_reader.go
+++ b/lib/storage/block_stream_reader.go
@@ -252,7 +252,7 @@ func (bsr *blockStreamReader) readBlock() error {
 			if err == io.EOF {
 				return io.EOF
 			}
-			return fmt.Errorf("cannot read index block from index data: %w", err)
+			return fmt.Errorf("cannot read index block: %w", err)
 		}
 	}
 
@@ -354,11 +354,11 @@ func (bsr *blockStreamReader) readIndexBlock() error {
 	// Read index block.
 	bsr.compressedIndexData = bytesutil.ResizeNoCopyMayOverallocate(bsr.compressedIndexData, int(bsr.mr.IndexBlockSize))
 	if err := fs.ReadFullData(bsr.indexReader, bsr.compressedIndexData); err != nil {
-		return fmt.Errorf("cannot read index block from index data at offset %d: %w", bsr.indexBlockOffset, err)
+		return fmt.Errorf("cannot read index block at offset %d: %w", bsr.indexBlockOffset, err)
 	}
 	tmpData, err := encoding.DecompressZSTD(bsr.indexData[:0], bsr.compressedIndexData)
 	if err != nil {
-		return fmt.Errorf("cannot decompress index block read at offset %d: %w", bsr.indexBlockOffset, err)
+		return fmt.Errorf("cannot decompress index block at offset %d: %w", bsr.indexBlockOffset, err)
 	}
 	bsr.indexData = tmpData
 	bsr.indexCursor = bsr.indexData
diff --git a/lib/storage/block_stream_writer.go b/lib/storage/block_stream_writer.go
index 790c36366..ff43aa3cd 100644
--- a/lib/storage/block_stream_writer.go
+++ b/lib/storage/block_stream_writer.go
@@ -80,13 +80,10 @@ func (bsw *blockStreamWriter) reset() {
 }
 
 // InitFromInmemoryPart initialzes bsw from inmemory part.
-func (bsw *blockStreamWriter) InitFromInmemoryPart(mp *inmemoryPart) {
+func (bsw *blockStreamWriter) InitFromInmemoryPart(mp *inmemoryPart, compressLevel int) {
 	bsw.reset()
 
-	// Use the minimum compression level for in-memory blocks,
-	// since they are going to be re-compressed during the merge into file-based blocks.
-	bsw.compressLevel = -5 // See https://github.com/facebook/zstd/releases/tag/v1.3.4
-
+	bsw.compressLevel = compressLevel
 	bsw.timestampsWriter = &mp.timestampsData
 	bsw.valuesWriter = &mp.valuesData
 	bsw.indexWriter = &mp.indexData
diff --git a/lib/storage/index_db.go b/lib/storage/index_db.go
index 29658f414..a28a8492f 100644
--- a/lib/storage/index_db.go
+++ b/lib/storage/index_db.go
@@ -400,12 +400,8 @@ func (is *indexSearch) maybeCreateIndexes(tsid *TSID, metricNameRaw []byte, date
 		return false, fmt.Errorf("cannot unmarshal metricNameRaw %q: %w", metricNameRaw, err)
 	}
 	mn.sortTags()
-	if err := is.createGlobalIndexes(tsid, mn); err != nil {
-		return false, fmt.Errorf("cannot create global indexes: %w", err)
-	}
-	if err := is.createPerDayIndexes(date, tsid.MetricID, mn); err != nil {
-		return false, fmt.Errorf("cannot create per-day indexes for date=%s: %w", dateToString(date), err)
-	}
+	is.createGlobalIndexes(tsid, mn)
+	is.createPerDayIndexes(date, tsid.MetricID, mn)
 	PutMetricName(mn)
 	atomic.AddUint64(&is.db.timeseriesRepopulated, 1)
 	return true, nil
@@ -599,12 +595,8 @@ func (is *indexSearch) createTSIDByName(dst *TSID, metricName, metricNameRaw []b
 	if err := is.db.s.registerSeriesCardinality(dst.MetricID, metricNameRaw); err != nil {
 		return err
 	}
-	if err := is.createGlobalIndexes(dst, mn); err != nil {
-		return fmt.Errorf("cannot create global indexes: %w", err)
-	}
-	if err := is.createPerDayIndexes(date, dst.MetricID, mn); err != nil {
-		return fmt.Errorf("cannot create per-day indexes for date=%s: %w", dateToString(date), err)
-	}
+	is.createGlobalIndexes(dst, mn)
+	is.createPerDayIndexes(date, dst.MetricID, mn)
 
 	// There is no need in invalidating tag cache, since it is invalidated
 	// on db.tb flush via invalidateTagFiltersCache flushCallback passed to OpenTable.
@@ -668,7 +660,7 @@ func generateTSID(dst *TSID, mn *MetricName) {
 	dst.MetricID = generateUniqueMetricID()
 }
 
-func (is *indexSearch) createGlobalIndexes(tsid *TSID, mn *MetricName) error {
+func (is *indexSearch) createGlobalIndexes(tsid *TSID, mn *MetricName) {
 	// The order of index items is important.
 	// It guarantees index consistency.
 
@@ -699,7 +691,7 @@ func (is *indexSearch) createGlobalIndexes(tsid *TSID, mn *MetricName) error {
 	ii.registerTagIndexes(prefix.B, mn, tsid.MetricID)
 	kbPool.Put(prefix)
 
-	return is.db.tb.AddItems(ii.Items)
+	is.db.tb.AddItems(ii.Items)
 }
 
 type indexItems struct {
@@ -1640,9 +1632,7 @@ func (db *indexDB) searchMetricNameWithCache(dst []byte, metricID uint64) ([]byt
 
 	// Mark the metricID as deleted, so it will be created again when new data point
 	// for the given time series will arrive.
-	if err := db.deleteMetricIDs([]uint64{metricID}); err != nil {
-		return dst, fmt.Errorf("cannot delete metricID for missing metricID->metricName entry; metricID=%d; error: %w", metricID, err)
-	}
+	db.deleteMetricIDs([]uint64{metricID})
 	return dst, io.EOF
 }
 
@@ -1669,9 +1659,7 @@ func (db *indexDB) DeleteTSIDs(qt *querytracer.Tracer, tfss []*TagFilters) (int,
 	if err != nil {
 		return 0, err
 	}
-	if err := db.deleteMetricIDs(metricIDs); err != nil {
-		return 0, err
-	}
+	db.deleteMetricIDs(metricIDs)
 
 	// Delete TSIDs in the extDB.
 	deletedCount := len(metricIDs)
@@ -1689,10 +1677,10 @@ func (db *indexDB) DeleteTSIDs(qt *querytracer.Tracer, tfss []*TagFilters) (int,
 	return deletedCount, nil
 }
 
-func (db *indexDB) deleteMetricIDs(metricIDs []uint64) error {
+func (db *indexDB) deleteMetricIDs(metricIDs []uint64) {
 	if len(metricIDs) == 0 {
 		// Nothing to delete
-		return nil
+		return
 	}
 
 	// atomically add deleted metricIDs to an inmemory map.
@@ -1717,9 +1705,8 @@ func (db *indexDB) deleteMetricIDs(metricIDs []uint64) error {
 		items.B = encoding.MarshalUint64(items.B, metricID)
 		items.Next()
 	}
-	err := db.tb.AddItems(items.Items)
+	db.tb.AddItems(items.Items)
 	putIndexItems(items)
-	return err
 }
 
 func (db *indexDB) loadDeletedMetricIDs() (*uint64set.Set, error) {
@@ -2793,7 +2780,7 @@ const (
 	int64Max = int64((1 << 63) - 1)
 )
 
-func (is *indexSearch) createPerDayIndexes(date, metricID uint64, mn *MetricName) error {
+func (is *indexSearch) createPerDayIndexes(date, metricID uint64, mn *MetricName) {
 	ii := getIndexItems()
 	defer putIndexItems(ii)
 
@@ -2808,11 +2795,8 @@ func (is *indexSearch) createPerDayIndexes(date, metricID uint64, mn *MetricName
 	kb.B = marshalCommonPrefix(kb.B[:0], nsPrefixDateTagToMetricIDs)
 	kb.B = encoding.MarshalUint64(kb.B, date)
 	ii.registerTagIndexes(kb.B, mn, metricID)
-	if err := is.db.tb.AddItems(ii.Items); err != nil {
-		return fmt.Errorf("cannot add per-day entires for metricID %d: %w", metricID, err)
-	}
+	is.db.tb.AddItems(ii.Items)
 	is.db.s.dateMetricIDCache.Set(date, metricID)
-	return nil
 }
 
 func (ii *indexItems) registerTagIndexes(prefix []byte, mn *MetricName, metricID uint64) {
diff --git a/lib/storage/inmemory_part.go b/lib/storage/inmemory_part.go
index 3f78e24b7..b0681c984 100644
--- a/lib/storage/inmemory_part.go
+++ b/lib/storage/inmemory_part.go
@@ -49,9 +49,12 @@ func (mp *inmemoryPart) InitFromRows(rows []rawRow) {
 // It is safe calling NewPart multiple times.
 // It is unsafe re-using mp while the returned part is in use.
 func (mp *inmemoryPart) NewPart() (*part, error) {
-	ph := mp.ph
-	size := uint64(len(mp.timestampsData.B) + len(mp.valuesData.B) + len(mp.indexData.B) + len(mp.metaindexData.B))
-	return newPart(&ph, "", size, mp.metaindexData.NewReader(), &mp.timestampsData, &mp.valuesData, &mp.indexData)
+	size := mp.size()
+	return newPart(&mp.ph, "", size, mp.metaindexData.NewReader(), &mp.timestampsData, &mp.valuesData, &mp.indexData)
+}
+
+func (mp *inmemoryPart) size() uint64 {
+	return uint64(cap(mp.timestampsData.B) + cap(mp.valuesData.B) + cap(mp.indexData.B) + cap(mp.metaindexData.B))
 }
 
 func getInmemoryPart() *inmemoryPart {
diff --git a/lib/storage/merge.go b/lib/storage/merge.go
index 7207fd213..8759c0992 100644
--- a/lib/storage/merge.go
+++ b/lib/storage/merge.go
@@ -178,6 +178,10 @@ func mergeBlocks(ob, ib1, ib2 *Block, retentionDeadline int64, rowsDeleted *uint
 }
 
 func skipSamplesOutsideRetention(b *Block, retentionDeadline int64, rowsDeleted *uint64) {
+	if b.bh.MinTimestamp >= retentionDeadline {
+		// Fast path - the block contains only samples with timestamps bigger than retentionDeadline.
+		return
+	}
 	timestamps := b.timestamps
 	nextIdx := b.nextIdx
 	nextIdxOrig := nextIdx
diff --git a/lib/storage/part_search.go b/lib/storage/part_search.go
index 1ceacd828..88e92c501 100644
--- a/lib/storage/part_search.go
+++ b/lib/storage/part_search.go
@@ -228,24 +228,29 @@ func (ps *partSearch) readIndexBlock(mr *metaindexRow) (*indexBlock, error) {
 }
 
 func (ps *partSearch) searchBHS() bool {
-	for i := range ps.bhs {
-		bh := &ps.bhs[i]
-
-	nextTSID:
-		if bh.TSID.Less(&ps.BlockRef.bh.TSID) {
-			// Skip blocks with small tsid values.
-			continue
+	bhs := ps.bhs
+	for len(bhs) > 0 {
+		// Skip block headers with tsids smaller than the given tsid.
+		tsid := &ps.BlockRef.bh.TSID
+		n := sort.Search(len(bhs), func(i int) bool {
+			return !bhs[i].TSID.Less(tsid)
+		})
+		if n == len(bhs) {
+			// Nothing found.
+			break
 		}
+		bhs = bhs[n:]
 
-		// Invariant: ps.BlockRef.bh.TSID <= bh.TSID
+		// Invariant: tsid <= bh.TSID
 
-		if bh.TSID.MetricID != ps.BlockRef.bh.TSID.MetricID {
-			// ps.BlockRef.bh.TSID < bh.TSID: no more blocks with the given tsid.
+		bh := &bhs[0]
+		if bh.TSID.MetricID != tsid.MetricID {
+			// tsid < bh.TSID: no more blocks with the given tsid.
 			// Proceed to the next (bigger) tsid.
 			if !ps.nextTSID() {
 				return false
 			}
-			goto nextTSID
+			continue
 		}
 
 		// Found the block with the given tsid. Verify timestamp range.
@@ -254,6 +259,7 @@ func (ps *partSearch) searchBHS() bool {
 		// So use linear search instead of binary search.
 		if bh.MaxTimestamp < ps.tr.MinTimestamp {
 			// Skip the block with too small timestamps.
+			bhs = bhs[1:]
 			continue
 		}
 		if bh.MinTimestamp > ps.tr.MaxTimestamp {
@@ -269,10 +275,9 @@ func (ps *partSearch) searchBHS() bool {
 		// Read it.
 		ps.BlockRef.init(ps.p, bh)
 
-		ps.bhs = ps.bhs[i+1:]
+		ps.bhs = bhs[1:]
 		return true
 	}
-
 	ps.bhs = nil
 	return false
 }
diff --git a/lib/storage/partition.go b/lib/storage/partition.go
index 797c57bdb..17e8aa8ba 100644
--- a/lib/storage/partition.go
+++ b/lib/storage/partition.go
@@ -145,11 +145,7 @@ type partition struct {
 
 	stopCh chan struct{}
 
-	smallPartsMergerWG     sync.WaitGroup
-	bigPartsMergerWG       sync.WaitGroup
-	rawRowsFlusherWG       sync.WaitGroup
-	inmemoryPartsFlusherWG sync.WaitGroup
-	stalePartsRemoverWG    sync.WaitGroup
+	wg sync.WaitGroup
 }
 
 // partWrapper is a wrapper for the part.
@@ -208,16 +204,20 @@ func createPartition(timestamp int64, smallPartitionsPath, bigPartitionsPath str
 
 	pt := newPartition(name, smallPartsPath, bigPartsPath, s)
 	pt.tr.fromPartitionTimestamp(timestamp)
-	pt.startMergeWorkers()
-	pt.startRawRowsFlusher()
-	pt.startInmemoryPartsFlusher()
-	pt.startStalePartsRemover()
+	pt.startBackgroundWorkers()
 
 	logger.Infof("partition %q has been created", name)
 
 	return pt, nil
 }
 
+func (pt *partition) startBackgroundWorkers() {
+	pt.startMergeWorkers()
+	pt.startRawRowsFlusher()
+	pt.startInmemoryPartsFlusher()
+	pt.startStalePartsRemover()
+}
+
 // Drop drops all the data on the storage for the given pt.
 //
 // The pt must be detached from table before calling pt.Drop.
@@ -262,10 +262,7 @@ func openPartition(smallPartsPath, bigPartsPath string, s *Storage) (*partition,
 	if err := pt.tr.fromPartitionName(name); err != nil {
 		return nil, fmt.Errorf("cannot obtain partition time range from smallPartsPath %q: %w", smallPartsPath, err)
 	}
-	pt.startMergeWorkers()
-	pt.startRawRowsFlusher()
-	pt.startInmemoryPartsFlusher()
-	pt.startStalePartsRemover()
+	pt.startBackgroundWorkers()
 
 	return pt, nil
 }
@@ -620,36 +617,16 @@ func (pt *partition) MustClose() {
 	// Wait until all the pending transaction deletions are finished.
 	pendingTxnDeletionsWG.Wait()
 
-	logger.Infof("waiting for stale parts remover to stop on %q...", pt.smallPartsPath)
+	logger.Infof("waiting for service workers to stop on %q...", pt.smallPartsPath)
 	startTime := time.Now()
-	pt.stalePartsRemoverWG.Wait()
-	logger.Infof("stale parts remover stopped in %.3f seconds on %q", time.Since(startTime).Seconds(), pt.smallPartsPath)
-
-	logger.Infof("waiting for inmemory parts flusher to stop on %q...", pt.smallPartsPath)
-	startTime = time.Now()
-	pt.inmemoryPartsFlusherWG.Wait()
-	logger.Infof("inmemory parts flusher stopped in %.3f seconds on %q", time.Since(startTime).Seconds(), pt.smallPartsPath)
-
-	logger.Infof("waiting for raw rows flusher to stop on %q...", pt.smallPartsPath)
-	startTime = time.Now()
-	pt.rawRowsFlusherWG.Wait()
-	logger.Infof("raw rows flusher stopped in %.3f seconds on %q", time.Since(startTime).Seconds(), pt.smallPartsPath)
-
-	logger.Infof("waiting for small part mergers to stop on %q...", pt.smallPartsPath)
-	startTime = time.Now()
-	pt.smallPartsMergerWG.Wait()
-	logger.Infof("small part mergers stopped in %.3f seconds on %q", time.Since(startTime).Seconds(), pt.smallPartsPath)
-
-	logger.Infof("waiting for big part mergers to stop on %q...", pt.bigPartsPath)
-	startTime = time.Now()
-	pt.bigPartsMergerWG.Wait()
-	logger.Infof("big part mergers stopped in %.3f seconds on %q", time.Since(startTime).Seconds(), pt.bigPartsPath)
+	pt.wg.Wait()
+	logger.Infof("service workers stopped in %.3f seconds on %q", time.Since(startTime).Seconds(), pt.smallPartsPath)
 
 	logger.Infof("flushing inmemory parts to files on %q...", pt.smallPartsPath)
 	startTime = time.Now()
 
 	// Flush raw rows the last time before exit.
-	pt.flushRawRows(true)
+	pt.flushPendingRows(true)
 
 	// Flush inmemory parts to disk.
 	var pws []*partWrapper
@@ -695,10 +672,10 @@ func (pt *partition) MustClose() {
 }
 
 func (pt *partition) startRawRowsFlusher() {
-	pt.rawRowsFlusherWG.Add(1)
+	pt.wg.Add(1)
 	go func() {
 		pt.rawRowsFlusher()
-		pt.rawRowsFlusherWG.Done()
+		pt.wg.Done()
 	}()
 }
 
@@ -710,12 +687,12 @@ func (pt *partition) rawRowsFlusher() {
 		case <-pt.stopCh:
 			return
 		case <-ticker.C:
-			pt.flushRawRows(false)
+			pt.flushPendingRows(false)
 		}
 	}
 }
 
-func (pt *partition) flushRawRows(isFinal bool) {
+func (pt *partition) flushPendingRows(isFinal bool) {
 	pt.rawRows.flush(pt, isFinal)
 }
 
@@ -748,10 +725,10 @@ func (rrs *rawRowsShard) appendRawRowsToFlush(dst []rawRow, pt *partition, isFin
 }
 
 func (pt *partition) startInmemoryPartsFlusher() {
-	pt.inmemoryPartsFlusherWG.Add(1)
+	pt.wg.Add(1)
 	go func() {
 		pt.inmemoryPartsFlusher()
-		pt.inmemoryPartsFlusherWG.Done()
+		pt.wg.Done()
 	}()
 }
 
@@ -800,21 +777,13 @@ func (pt *partition) flushInmemoryParts(dstPws []*partWrapper, force bool) ([]*p
 }
 
 func (pt *partition) mergePartsOptimal(pws []*partWrapper, stopCh <-chan struct{}) error {
-	defer func() {
-		// Remove isInMerge flag from pws.
-		pt.partsLock.Lock()
-		for _, pw := range pws {
-			// Do not check for pws.isInMerge set to false,
-			// since it may be set to false in mergeParts below.
-			pw.isInMerge = false
-		}
-		pt.partsLock.Unlock()
-	}()
 	for len(pws) > defaultPartsToMerge {
-		if err := pt.mergeParts(pws[:defaultPartsToMerge], stopCh); err != nil {
+		pwsChunk := pws[:defaultPartsToMerge]
+		pws = pws[defaultPartsToMerge:]
+		if err := pt.mergeParts(pwsChunk, stopCh); err != nil {
+			pt.releasePartsToMerge(pws)
 			return fmt.Errorf("cannot merge %d parts: %w", defaultPartsToMerge, err)
 		}
-		pws = pws[defaultPartsToMerge:]
 	}
 	if len(pws) == 0 {
 		return nil
@@ -917,17 +886,17 @@ func SetSmallMergeWorkersCount(n int) {
 
 func (pt *partition) startMergeWorkers() {
 	for i := 0; i < smallMergeWorkersCount; i++ {
-		pt.smallPartsMergerWG.Add(1)
+		pt.wg.Add(1)
 		go func() {
 			pt.smallPartsMerger()
-			pt.smallPartsMergerWG.Done()
+			pt.wg.Done()
 		}()
 	}
 	for i := 0; i < bigMergeWorkersCount; i++ {
-		pt.bigPartsMergerWG.Add(1)
+		pt.wg.Add(1)
 		go func() {
 			pt.bigPartsMerger()
-			pt.bigPartsMergerWG.Done()
+			pt.wg.Done()
 		}()
 	}
 }
@@ -1189,7 +1158,8 @@ func (pt *partition) mergeParts(pws []*partWrapper, stopCh <-chan struct{}) erro
 	mergeIdx := pt.nextMergeIdx()
 	tmpPartPath := fmt.Sprintf("%s/tmp/%016X", ptPath, mergeIdx)
 	bsw := getBlockStreamWriter()
-	compressLevel := getCompressLevelForRowsCount(outRowsCount, outBlocksCount)
+	rowsPerBlock := float64(outRowsCount) / float64(outBlocksCount)
+	compressLevel := getCompressLevel(rowsPerBlock)
 	if err := bsw.InitFromFilePart(tmpPartPath, nocache, compressLevel); err != nil {
 		return fmt.Errorf("cannot create destination part %q: %w", tmpPartPath, err)
 	}
@@ -1309,28 +1279,27 @@ func (pt *partition) mergeParts(pws []*partWrapper, stopCh <-chan struct{}) erro
 	return nil
 }
 
-func getCompressLevelForRowsCount(rowsCount, blocksCount uint64) int {
-	avgRowsPerBlock := rowsCount / blocksCount
+func getCompressLevel(rowsPerBlock float64) int {
 	// See https://github.com/facebook/zstd/releases/tag/v1.3.4 about negative compression levels.
-	if avgRowsPerBlock <= 10 {
+	if rowsPerBlock <= 10 {
 		return -5
 	}
-	if avgRowsPerBlock <= 50 {
+	if rowsPerBlock <= 50 {
 		return -2
 	}
-	if avgRowsPerBlock <= 200 {
+	if rowsPerBlock <= 200 {
 		return -1
 	}
-	if avgRowsPerBlock <= 500 {
+	if rowsPerBlock <= 500 {
 		return 1
 	}
-	if avgRowsPerBlock <= 1000 {
+	if rowsPerBlock <= 1000 {
 		return 2
 	}
-	if avgRowsPerBlock <= 2000 {
+	if rowsPerBlock <= 2000 {
 		return 3
 	}
-	if avgRowsPerBlock <= 4000 {
+	if rowsPerBlock <= 4000 {
 		return 4
 	}
 	return 5
@@ -1354,10 +1323,10 @@ func removeParts(pws []*partWrapper, partsToRemove map[*partWrapper]bool, isBig
 }
 
 func (pt *partition) startStalePartsRemover() {
-	pt.stalePartsRemoverWG.Add(1)
+	pt.wg.Add(1)
 	go func() {
 		pt.stalePartsRemover()
-		pt.stalePartsRemoverWG.Done()
+		pt.wg.Done()
 	}()
 }
 
@@ -1647,7 +1616,7 @@ func (pt *partition) CreateSnapshotAt(smallPath, bigPath string) error {
 	startTime := time.Now()
 
 	// Flush inmemory data to disk.
-	pt.flushRawRows(true)
+	pt.flushPendingRows(true)
 	if _, err := pt.flushInmemoryParts(nil, true); err != nil {
 		return fmt.Errorf("cannot flush inmemory parts: %w", err)
 	}
diff --git a/lib/storage/raw_row.go b/lib/storage/raw_row.go
index b1d978d33..206805d62 100644
--- a/lib/storage/raw_row.go
+++ b/lib/storage/raw_row.go
@@ -86,7 +86,10 @@ func (rrm *rawRowsMarshaler) marshalToInmemoryPart(mp *inmemoryPart, rows []rawR
 		logger.Panicf("BUG: rows count must be smaller than 2^32; got %d", len(rows))
 	}
 
-	rrm.bsw.InitFromInmemoryPart(mp)
+	// Use the minimum compression level for first-level in-memory blocks,
+	// since they are going to be re-compressed during subsequent merges.
+	const compressLevel = -5 // See https://github.com/facebook/zstd/releases/tag/v1.3.4
+	rrm.bsw.InitFromInmemoryPart(mp, compressLevel)
 
 	ph := &mp.ph
 	ph.Reset()
diff --git a/lib/storage/storage.go b/lib/storage/storage.go
index 016830cb6..1b686140b 100644
--- a/lib/storage/storage.go
+++ b/lib/storage/storage.go
@@ -306,7 +306,7 @@ func (s *Storage) updateDeletedMetricIDs(metricIDs *uint64set.Set) {
 
 // DebugFlush flushes recently added storage data, so it becomes visible to search.
 func (s *Storage) DebugFlush() {
-	s.tb.flushRawRows()
+	s.tb.flushPendingRows()
 	s.idb().tb.DebugFlush()
 }
 
@@ -378,13 +378,13 @@ func (s *Storage) ListSnapshots() ([]string, error) {
 	snapshotsPath := s.path + "/snapshots"
 	d, err := os.Open(snapshotsPath)
 	if err != nil {
-		return nil, fmt.Errorf("cannot open %q: %w", snapshotsPath, err)
+		return nil, fmt.Errorf("cannot open snapshots directory: %w", err)
 	}
 	defer fs.MustClose(d)
 
 	fnames, err := d.Readdirnames(-1)
 	if err != nil {
-		return nil, fmt.Errorf("cannot read contents of %q: %w", snapshotsPath, err)
+		return nil, fmt.Errorf("cannot read snapshots directory at %q: %w", snapshotsPath, err)
 	}
 	snapshotNames := make([]string, 0, len(fnames))
 	for _, fname := range fnames {
@@ -2070,12 +2070,7 @@ func (s *Storage) updatePerDateData(rows []rawRow, mrs []*MetricRow) error {
 				continue
 			}
 			mn.sortTags()
-			if err := is.createPerDayIndexes(date, metricID, mn); err != nil {
-				if firstError == nil {
-					firstError = fmt.Errorf("error when storing per-date inverted index for (date=%s, metricID=%d): %w", dateToString(date), metricID, err)
-				}
-				continue
-			}
+			is.createPerDayIndexes(date, metricID, mn)
 		}
 		dateMetricIDsForCache = append(dateMetricIDsForCache, dateMetricID{
 			date:     date,
@@ -2309,7 +2304,7 @@ func (s *Storage) updateNextDayMetricIDs(date uint64) {
 	if e.date == date {
 		pendingMetricIDs.Union(&e.v)
 	} else {
-		// Do not add pendingMetricIDs from the previous day to the cyrrent day,
+		// Do not add pendingMetricIDs from the previous day to the current day,
 		// since this may result in missing registration of the metricIDs in the per-day inverted index.
 		// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3309
 		pendingMetricIDs = &uint64set.Set{}
@@ -2337,14 +2332,15 @@ func (s *Storage) updateCurrHourMetricIDs(hour uint64) {
 	var m *uint64set.Set
 	if hm.hour == hour {
 		m = hm.m.Clone()
-	} else {
-		m = &uint64set.Set{}
-	}
-	if hour%24 != 0 {
-		// Do not add pending metricIDs from the previous hour to the current hour on the next day,
-		// since this may result in missing registration of the metricIDs in the per-day inverted index.
-		// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3309
 		m.Union(newMetricIDs)
+	} else {
+		m = newMetricIDs
+		if hour%24 == 0 {
+			// Do not add pending metricIDs from the previous hour to the current hour on the next day,
+			// since this may result in missing registration of the metricIDs in the per-day inverted index.
+			// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3309
+			m = &uint64set.Set{}
+		}
 	}
 	hmNew := &hourMetricIDs{
 		m:    m,
diff --git a/lib/storage/table.go b/lib/storage/table.go
index 5b7ffbce7..e4b603044 100644
--- a/lib/storage/table.go
+++ b/lib/storage/table.go
@@ -215,15 +215,15 @@ func (tb *table) MustClose() {
 	}
 }
 
-// flushRawRows flushes all the pending rows, so they become visible to search.
+// flushPendingRows flushes all the pending rows, so they become visible to search.
 //
 // This function is for debug purposes only.
-func (tb *table) flushRawRows() {
+func (tb *table) flushPendingRows() {
 	ptws := tb.GetPartitions(nil)
 	defer tb.PutPartitions(ptws)
 
 	for _, ptw := range ptws {
-		ptw.pt.flushRawRows(true)
+		ptw.pt.flushPendingRows(true)
 	}
 }
 


Test patch:
diff --git a/app/vmalert/datasource/vm_prom_api_test.go b/app/vmalert/datasource/vm_prom_api_test.go
new file mode 100644
index 000000000..0a0105810
--- /dev/null
+++ b/app/vmalert/datasource/vm_prom_api_test.go
@@ -0,0 +1,20 @@
+package datasource
+
+import (
+	"encoding/json"
+	"testing"
+)
+
+func BenchmarkMetrics(b *testing.B) {
+	payload := []byte(`[{"metric":{"__name__":"vm_rows"},"value":[1583786142,"13763"]},{"metric":{"__name__":"vm_requests", "foo":"bar", "baz": "qux"},"value":[1583786140,"2000"]}]`)
+
+	var pi promInstant
+	if err := json.Unmarshal(payload, &pi.Result); err != nil {
+		b.Fatalf(err.Error())
+	}
+	b.Run("Instant", func(b *testing.B) {
+		for i := 0; i < b.N; i++ {
+			_, _ = pi.metrics()
+		}
+	})
+}
diff --git a/app/vmalert/datasource/vm_test.go b/app/vmalert/datasource/vm_test.go
index 1932b892d..e9d728763 100644
--- a/app/vmalert/datasource/vm_test.go
+++ b/app/vmalert/datasource/vm_test.go
@@ -7,6 +7,7 @@ import (
 	"net/http/httptest"
 	"net/url"
 	"reflect"
+	"sort"
 	"strconv"
 	"strings"
 	"testing"
@@ -74,7 +75,7 @@ func TestVMInstantQuery(t *testing.T) {
 		case 5:
 			w.Write([]byte(`{"status":"success","data":{"resultType":"matrix"}}`))
 		case 6:
-			w.Write([]byte(`{"status":"success","data":{"resultType":"vector","result":[{"metric":{"__name__":"vm_rows"},"value":[1583786142,"13763"]},{"metric":{"__name__":"vm_requests"},"value":[1583786140,"2000"]}]}}`))
+			w.Write([]byte(`{"status":"success","data":{"resultType":"vector","result":[{"metric":{"__name__":"vm_rows","foo":"bar"},"value":[1583786142,"13763"]},{"metric":{"__name__":"vm_requests","foo":"baz"},"value":[1583786140,"2000"]}]}}`))
 		case 7:
 			w.Write([]byte(`{"status":"success","data":{"resultType":"scalar","result":[1583786142, "1"]}}`))
 		}
@@ -115,19 +116,17 @@ func TestVMInstantQuery(t *testing.T) {
 	}
 	expected := []Metric{
 		{
-			Labels:     []Label{{Value: "vm_rows", Name: "__name__"}},
+			Labels:     []Label{{Value: "vm_rows", Name: "__name__"}, {Value: "bar", Name: "foo"}},
 			Timestamps: []int64{1583786142},
 			Values:     []float64{13763},
 		},
 		{
-			Labels:     []Label{{Value: "vm_requests", Name: "__name__"}},
+			Labels:     []Label{{Value: "vm_requests", Name: "__name__"}, {Value: "baz", Name: "foo"}},
 			Timestamps: []int64{1583786140},
 			Values:     []float64{2000},
 		},
 	}
-	if !reflect.DeepEqual(m, expected) {
-		t.Fatalf("unexpected metric %+v want %+v", m, expected)
-	}
+	metricsEqual(t, m, expected)
 
 	m, req, err := pq.Query(ctx, query, ts) // 7 - scalar
 	if err != nil {
@@ -158,13 +157,36 @@ func TestVMInstantQuery(t *testing.T) {
 	if len(m) != 1 {
 		t.Fatalf("expected 1 metric  got %d in %+v", len(m), m)
 	}
-	exp := Metric{
-		Labels:     []Label{{Value: "constantLine(10)", Name: "name"}},
-		Timestamps: []int64{1611758403},
-		Values:     []float64{10},
+	exp := []Metric{
+		{
+			Labels:     []Label{{Value: "constantLine(10)", Name: "name"}},
+			Timestamps: []int64{1611758403},
+			Values:     []float64{10},
+		},
 	}
-	if !reflect.DeepEqual(m[0], exp) {
-		t.Fatalf("unexpected metric %+v want %+v", m[0], expected)
+	metricsEqual(t, m, exp)
+}
+
+func metricsEqual(t *testing.T, gotM, expectedM []Metric) {
+	for i, exp := range expectedM {
+		got := gotM[i]
+		gotTS, expTS := got.Timestamps, exp.Timestamps
+		if !reflect.DeepEqual(gotTS, expTS) {
+			t.Fatalf("unexpected timestamps %+v want %+v", gotTS, expTS)
+		}
+		gotV, expV := got.Values, exp.Values
+		if !reflect.DeepEqual(gotV, expV) {
+			t.Fatalf("unexpected values %+v want %+v", gotV, expV)
+		}
+		sort.Slice(got.Labels, func(i, j int) bool {
+			return got.Labels[i].Name < got.Labels[j].Name
+		})
+		sort.Slice(exp.Labels, func(i, j int) bool {
+			return exp.Labels[i].Name < exp.Labels[j].Name
+		})
+		if !reflect.DeepEqual(exp.Labels, got.Labels) {
+			t.Fatalf("unexpected labels %+v want %+v", got.Labels, exp.Labels)
+		}
 	}
 }
 
diff --git a/lib/mergeset/encoding_test.go b/lib/mergeset/encoding_test.go
index 48398ae2f..8e6ecffd0 100644
--- a/lib/mergeset/encoding_test.go
+++ b/lib/mergeset/encoding_test.go
@@ -115,7 +115,7 @@ func TestInmemoryBlockMarshalUnmarshal(t *testing.T) {
 	var itemsLen uint32
 	var mt marshalType
 
-	for i := 0; i < 1000; i++ {
+	for i := 0; i < 1000; i += 10 {
 		var items []string
 		totalLen := 0
 		ib.Reset()
diff --git a/lib/mergeset/merge_test.go b/lib/mergeset/merge_test.go
index f042bae0f..6ba874a67 100644
--- a/lib/mergeset/merge_test.go
+++ b/lib/mergeset/merge_test.go
@@ -30,14 +30,14 @@ func TestMultilevelMerge(t *testing.T) {
 	// First level merge
 	var dstIP1 inmemoryPart
 	var bsw1 blockStreamWriter
-	bsw1.InitFromInmemoryPart(&dstIP1)
+	bsw1.InitFromInmemoryPart(&dstIP1, -5)
 	if err := mergeBlockStreams(&dstIP1.ph, &bsw1, bsrs[:5], nil, nil, &itemsMerged); err != nil {
 		t.Fatalf("cannot merge first level part 1: %s", err)
 	}
 
 	var dstIP2 inmemoryPart
 	var bsw2 blockStreamWriter
-	bsw2.InitFromInmemoryPart(&dstIP2)
+	bsw2.InitFromInmemoryPart(&dstIP2, -5)
 	if err := mergeBlockStreams(&dstIP2.ph, &bsw2, bsrs[5:], nil, nil, &itemsMerged); err != nil {
 		t.Fatalf("cannot merge first level part 2: %s", err)
 	}
@@ -54,7 +54,7 @@ func TestMultilevelMerge(t *testing.T) {
 		newTestBlockStreamReader(&dstIP1),
 		newTestBlockStreamReader(&dstIP2),
 	}
-	bsw.InitFromInmemoryPart(&dstIP)
+	bsw.InitFromInmemoryPart(&dstIP, 1)
 	if err := mergeBlockStreams(&dstIP.ph, &bsw, bsrsTop, nil, nil, &itemsMerged); err != nil {
 		t.Fatalf("cannot merge second level: %s", err)
 	}
@@ -73,7 +73,7 @@ func TestMergeForciblyStop(t *testing.T) {
 	bsrs, _ := newTestInmemoryBlockStreamReaders(20, 4000)
 	var dstIP inmemoryPart
 	var bsw blockStreamWriter
-	bsw.InitFromInmemoryPart(&dstIP)
+	bsw.InitFromInmemoryPart(&dstIP, 1)
 	ch := make(chan struct{})
 	var itemsMerged uint64
 	close(ch)
@@ -120,7 +120,7 @@ func testMergeBlockStreamsSerial(blocksToMerge, maxItemsPerBlock int) error {
 	var itemsMerged uint64
 	var dstIP inmemoryPart
 	var bsw blockStreamWriter
-	bsw.InitFromInmemoryPart(&dstIP)
+	bsw.InitFromInmemoryPart(&dstIP, -4)
 	if err := mergeBlockStreams(&dstIP.ph, &bsw, bsrs, nil, nil, &itemsMerged); err != nil {
 		return fmt.Errorf("cannot merge block streams: %w", err)
 	}
diff --git a/lib/mergeset/part_search_test.go b/lib/mergeset/part_search_test.go
index fb178bcde..c042d44ac 100644
--- a/lib/mergeset/part_search_test.go
+++ b/lib/mergeset/part_search_test.go
@@ -149,7 +149,7 @@ func newTestPart(blocksCount, maxItemsPerBlock int) (*part, []string, error) {
 	var itemsMerged uint64
 	var ip inmemoryPart
 	var bsw blockStreamWriter
-	bsw.InitFromInmemoryPart(&ip)
+	bsw.InitFromInmemoryPart(&ip, -3)
 	if err := mergeBlockStreams(&ip.ph, &bsw, bsrs, nil, nil, &itemsMerged); err != nil {
 		return nil, nil, fmt.Errorf("cannot merge blocks: %w", err)
 	}
diff --git a/lib/mergeset/table_search_test.go b/lib/mergeset/table_search_test.go
index 249aa3109..f0ec1f888 100644
--- a/lib/mergeset/table_search_test.go
+++ b/lib/mergeset/table_search_test.go
@@ -161,9 +161,7 @@ func newTestTable(path string, itemsCount int) (*Table, []string, error) {
 	items := make([]string, itemsCount)
 	for i := 0; i < itemsCount; i++ {
 		item := fmt.Sprintf("%d:%d", rand.Intn(1e9), i)
-		if err := tb.AddItems([][]byte{[]byte(item)}); err != nil {
-			return nil, nil, fmt.Errorf("cannot add item: %w", err)
-		}
+		tb.AddItems([][]byte{[]byte(item)})
 		items[i] = item
 	}
 	tb.DebugFlush()
diff --git a/lib/mergeset/table_test.go b/lib/mergeset/table_test.go
index f99ab937c..6a7968537 100644
--- a/lib/mergeset/table_test.go
+++ b/lib/mergeset/table_test.go
@@ -7,8 +7,6 @@ import (
 	"sync"
 	"sync/atomic"
 	"testing"
-
-	"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
 )
 
 func TestTableOpenClose(t *testing.T) {
@@ -31,7 +29,7 @@ func TestTableOpenClose(t *testing.T) {
 	tb.MustClose()
 
 	// Re-open created table multiple times.
-	for i := 0; i < 10; i++ {
+	for i := 0; i < 4; i++ {
 		tb, err := OpenTable(path, nil, nil, &isReadOnly)
 		if err != nil {
 			t.Fatalf("cannot open created table: %s", err)
@@ -53,7 +51,7 @@ func TestTableOpenMultipleTimes(t *testing.T) {
 	}
 	defer tb1.MustClose()
 
-	for i := 0; i < 10; i++ {
+	for i := 0; i < 4; i++ {
 		tb2, err := OpenTable(path, nil, nil, &isReadOnly)
 		if err == nil {
 			tb2.MustClose()
@@ -62,8 +60,8 @@ func TestTableOpenMultipleTimes(t *testing.T) {
 	}
 }
 
-func TestTableAddItemSerial(t *testing.T) {
-	const path = "TestTableAddItemSerial"
+func TestTableAddItemsSerial(t *testing.T) {
+	const path = "TestTableAddItemsSerial"
 	if err := os.RemoveAll(path); err != nil {
 		t.Fatalf("cannot remove %q: %s", path, err)
 	}
@@ -81,7 +79,7 @@ func TestTableAddItemSerial(t *testing.T) {
 		t.Fatalf("cannot open %q: %s", path, err)
 	}
 
-	const itemsCount = 1e5
+	const itemsCount = 10e3
 	testAddItemsSerial(tb, itemsCount)
 
 	// Verify items count after pending items flush.
@@ -98,7 +96,7 @@ func TestTableAddItemSerial(t *testing.T) {
 
 	tb.MustClose()
 
-	// Re-open the table and make sure ItemsCount remains the same.
+	// Re-open the table and make sure itemsCount remains the same.
 	testReopenTable(t, path, itemsCount)
 
 	// Add more items in order to verify merge between inmemory parts and file-based parts.
@@ -110,7 +108,7 @@ func TestTableAddItemSerial(t *testing.T) {
 	testAddItemsSerial(tb, moreItemsCount)
 	tb.MustClose()
 
-	// Re-open the table and verify ItemsCount again.
+	// Re-open the table and verify itemsCount again.
 	testReopenTable(t, path, itemsCount+moreItemsCount)
 }
 
@@ -120,9 +118,7 @@ func testAddItemsSerial(tb *Table, itemsCount int) {
 		if len(item) > maxInmemoryBlockSize {
 			item = item[:maxInmemoryBlockSize]
 		}
-		if err := tb.AddItems([][]byte{item}); err != nil {
-			logger.Panicf("BUG: cannot add item to table: %s", err)
-		}
+		tb.AddItems([][]byte{item})
 	}
 }
 
@@ -146,9 +142,7 @@ func TestTableCreateSnapshotAt(t *testing.T) {
 	const itemsCount = 3e5
 	for i := 0; i < itemsCount; i++ {
 		item := []byte(fmt.Sprintf("item %d", i))
-		if err := tb.AddItems([][]byte{item}); err != nil {
-			t.Fatalf("cannot add item to table: %s", err)
-		}
+		tb.AddItems([][]byte{item})
 	}
 	tb.DebugFlush()
 
@@ -221,9 +215,7 @@ func TestTableAddItemsConcurrent(t *testing.T) {
 	flushCallback := func() {
 		atomic.AddUint64(&flushes, 1)
 	}
-	var itemsMerged uint64
 	prepareBlock := func(data []byte, items []Item) ([]byte, []Item) {
-		atomic.AddUint64(&itemsMerged, uint64(len(items)))
 		return data, items
 	}
 	var isReadOnly uint32
@@ -232,7 +224,7 @@ func TestTableAddItemsConcurrent(t *testing.T) {
 		t.Fatalf("cannot open %q: %s", path, err)
 	}
 
-	const itemsCount = 1e5
+	const itemsCount = 10e3
 	testAddItemsConcurrent(tb, itemsCount)
 
 	// Verify items count after pending items flush.
@@ -240,10 +232,6 @@ func TestTableAddItemsConcurrent(t *testing.T) {
 	if atomic.LoadUint64(&flushes) == 0 {
 		t.Fatalf("unexpected zero flushes")
 	}
-	n := atomic.LoadUint64(&itemsMerged)
-	if n < itemsCount {
-		t.Fatalf("too low number of items merged; got %v; must be at least %v", n, itemsCount)
-	}
 
 	var m TableMetrics
 	tb.UpdateMetrics(&m)
@@ -253,7 +241,7 @@ func TestTableAddItemsConcurrent(t *testing.T) {
 
 	tb.MustClose()
 
-	// Re-open the table and make sure ItemsCount remains the same.
+	// Re-open the table and make sure itemsCount remains the same.
 	testReopenTable(t, path, itemsCount)
 
 	// Add more items in order to verify merge between inmemory parts and file-based parts.
@@ -265,7 +253,7 @@ func TestTableAddItemsConcurrent(t *testing.T) {
 	testAddItemsConcurrent(tb, moreItemsCount)
 	tb.MustClose()
 
-	// Re-open the table and verify ItemsCount again.
+	// Re-open the table and verify itemsCount again.
 	testReopenTable(t, path, itemsCount+moreItemsCount)
 }
 
@@ -282,9 +270,7 @@ func testAddItemsConcurrent(tb *Table, itemsCount int) {
 				if len(item) > maxInmemoryBlockSize {
 					item = item[:maxInmemoryBlockSize]
 				}
-				if err := tb.AddItems([][]byte{item}); err != nil {
-					logger.Panicf("BUG: cannot add item to table: %s", err)
-				}
+				tb.AddItems([][]byte{item})
 			}
 		}()
 	}
diff --git a/lib/storage/block_stream_writer_timing_test.go b/lib/storage/block_stream_writer_timing_test.go
index 8e51d06a2..95ecbbe35 100644
--- a/lib/storage/block_stream_writer_timing_test.go
+++ b/lib/storage/block_stream_writer_timing_test.go
@@ -47,7 +47,7 @@ func benchmarkBlockStreamWriter(b *testing.B, ebs []Block, rowsCount int, writeR
 				}
 			}
 
-			bsw.InitFromInmemoryPart(&mp)
+			bsw.InitFromInmemoryPart(&mp, -5)
 			for i := range ebsCopy {
 				bsw.WriteExternalBlock(&ebsCopy[i], &ph, &rowsMerged)
 			}
diff --git a/lib/storage/index_db_test.go b/lib/storage/index_db_test.go
index 9f00a579c..5268a24fb 100644
--- a/lib/storage/index_db_test.go
+++ b/lib/storage/index_db_test.go
@@ -523,22 +523,13 @@ func TestIndexDB(t *testing.T) {
 			}
 		}()
 
-		if err := testIndexDBBigMetricName(db); err != nil {
-			t.Fatalf("unexpected error: %s", err)
-		}
 		mns, tsids, err := testIndexDBGetOrCreateTSIDByName(db, metricGroups)
 		if err != nil {
 			t.Fatalf("unexpected error: %s", err)
 		}
-		if err := testIndexDBBigMetricName(db); err != nil {
-			t.Fatalf("unexpected error: %s", err)
-		}
 		if err := testIndexDBCheckTSIDByName(db, mns, tsids, false); err != nil {
 			t.Fatalf("unexpected error: %s", err)
 		}
-		if err := testIndexDBBigMetricName(db); err != nil {
-			t.Fatalf("unexpected error: %s", err)
-		}
 
 		// Re-open the db and verify it works as expected.
 		db.MustClose()
@@ -546,15 +537,9 @@ func TestIndexDB(t *testing.T) {
 		if err != nil {
 			t.Fatalf("cannot open indexDB: %s", err)
 		}
-		if err := testIndexDBBigMetricName(db); err != nil {
-			t.Fatalf("unexpected error: %s", err)
-		}
 		if err := testIndexDBCheckTSIDByName(db, mns, tsids, false); err != nil {
 			t.Fatalf("unexpected error: %s", err)
 		}
-		if err := testIndexDBBigMetricName(db); err != nil {
-			t.Fatalf("unexpected error: %s", err)
-		}
 	})
 
 	t.Run("concurrent", func(t *testing.T) {
@@ -577,27 +562,15 @@ func TestIndexDB(t *testing.T) {
 		ch := make(chan error, 3)
 		for i := 0; i < cap(ch); i++ {
 			go func() {
-				if err := testIndexDBBigMetricName(db); err != nil {
-					ch <- err
-					return
-				}
 				mns, tsid, err := testIndexDBGetOrCreateTSIDByName(db, metricGroups)
 				if err != nil {
 					ch <- err
 					return
 				}
-				if err := testIndexDBBigMetricName(db); err != nil {
-					ch <- err
-					return
-				}
 				if err := testIndexDBCheckTSIDByName(db, mns, tsid, true); err != nil {
 					ch <- err
 					return
 				}
-				if err := testIndexDBBigMetricName(db); err != nil {
-					ch <- err
-					return
-				}
 				ch <- nil
 			}()
 		}
@@ -618,74 +591,6 @@ func TestIndexDB(t *testing.T) {
 	})
 }
 
-func testIndexDBBigMetricName(db *indexDB) error {
-	var bigBytes []byte
-	for i := 0; i < 128*1000; i++ {
-		bigBytes = append(bigBytes, byte(i))
-	}
-	var mn MetricName
-	var tsid TSID
-
-	is := db.getIndexSearch(noDeadline)
-	defer db.putIndexSearch(is)
-
-	// Try creating too big metric group
-	mn.Reset()
-	mn.MetricGroup = append(mn.MetricGroup[:0], bigBytes...)
-	mn.sortTags()
-	metricName := mn.Marshal(nil)
-	metricNameRaw := mn.marshalRaw(nil)
-	if err := is.GetOrCreateTSIDByName(&tsid, metricName, metricNameRaw, 0); err == nil {
-		return fmt.Errorf("expecting non-nil error on an attempt to insert metric with too big MetricGroup")
-	}
-
-	// Try creating too big tag key
-	mn.Reset()
-	mn.MetricGroup = append(mn.MetricGroup[:0], "xxx"...)
-	mn.Tags = []Tag{{
-		Key:   append([]byte(nil), bigBytes...),
-		Value: []byte("foobar"),
-	}}
-	mn.sortTags()
-	metricName = mn.Marshal(nil)
-	metricNameRaw = mn.marshalRaw(nil)
-	if err := is.GetOrCreateTSIDByName(&tsid, metricName, metricNameRaw, 0); err == nil {
-		return fmt.Errorf("expecting non-nil error on an attempt to insert metric with too big tag key")
-	}
-
-	// Try creating too big tag value
-	mn.Reset()
-	mn.MetricGroup = append(mn.MetricGroup[:0], "xxx"...)
-	mn.Tags = []Tag{{
-		Key:   []byte("foobar"),
-		Value: append([]byte(nil), bigBytes...),
-	}}
-	mn.sortTags()
-	metricName = mn.Marshal(nil)
-	metricNameRaw = mn.marshalRaw(nil)
-	if err := is.GetOrCreateTSIDByName(&tsid, metricName, metricNameRaw, 0); err == nil {
-		return fmt.Errorf("expecting non-nil error on an attempt to insert metric with too big tag value")
-	}
-
-	// Try creating metric name with too many tags
-	mn.Reset()
-	mn.MetricGroup = append(mn.MetricGroup[:0], "xxx"...)
-	for i := 0; i < 60000; i++ {
-		mn.Tags = append(mn.Tags, Tag{
-			Key:   []byte(fmt.Sprintf("foobar %d", i)),
-			Value: []byte(fmt.Sprintf("sdfjdslkfj %d", i)),
-		})
-	}
-	mn.sortTags()
-	metricName = mn.Marshal(nil)
-	metricNameRaw = mn.marshalRaw(nil)
-	if err := is.GetOrCreateTSIDByName(&tsid, metricName, metricNameRaw, 0); err == nil {
-		return fmt.Errorf("expecting non-nil error on an attempt to insert metric with too many tags")
-	}
-
-	return nil
-}
-
 func testIndexDBGetOrCreateTSIDByName(db *indexDB, metricGroups int) ([]MetricName, []TSID, error) {
 	// Create tsids.
 	var mns []MetricName
@@ -727,9 +632,7 @@ func testIndexDBGetOrCreateTSIDByName(db *indexDB, metricGroups int) ([]MetricNa
 	date := uint64(timestampFromTime(time.Now())) / msecPerDay
 	for i := range tsids {
 		tsid := &tsids[i]
-		if err := is.createPerDayIndexes(date, tsid.MetricID, &mns[i]); err != nil {
-			return nil, nil, fmt.Errorf("error in createPerDayIndexes(%d, %d): %w", date, tsid.MetricID, err)
-		}
+		is.createPerDayIndexes(date, tsid.MetricID, &mns[i])
 	}
 
 	// Flush index to disk, so it becomes visible for search
@@ -1549,11 +1452,10 @@ func TestMatchTagFilters(t *testing.T) {
 
 func TestIndexDBRepopulateAfterRotation(t *testing.T) {
 	path := "TestIndexRepopulateAfterRotation"
-	s, err := OpenStorage(path, 0, 1e5, 1e5)
+	s, err := OpenStorage(path, msecsPerMonth, 1e5, 1e5)
 	if err != nil {
 		t.Fatalf("cannot open storage: %s", err)
 	}
-	s.retentionMsecs = msecsPerMonth
 	defer func() {
 		s.MustClose()
 		if err := os.RemoveAll(path); err != nil {
@@ -1721,9 +1623,7 @@ func TestSearchTSIDWithTimeRange(t *testing.T) {
 		for i := range tsids {
 			tsid := &tsids[i]
 			metricIDs.Add(tsid.MetricID)
-			if err := is.createPerDayIndexes(date, tsid.MetricID, &mns[i]); err != nil {
-				t.Fatalf("error in createPerDayIndexes(%d, %d): %s", date, tsid.MetricID, err)
-			}
+			is.createPerDayIndexes(date, tsid.MetricID, &mns[i])
 		}
 		allMetricIDs.Union(&metricIDs)
 		perDayMetricIDs[date] = &metricIDs
diff --git a/lib/storage/merge_test.go b/lib/storage/merge_test.go
index 189935c41..276abd3aa 100644
--- a/lib/storage/merge_test.go
+++ b/lib/storage/merge_test.go
@@ -361,7 +361,7 @@ func TestMergeForciblyStop(t *testing.T) {
 
 	var mp inmemoryPart
 	var bsw blockStreamWriter
-	bsw.InitFromInmemoryPart(&mp)
+	bsw.InitFromInmemoryPart(&mp, -5)
 	ch := make(chan struct{})
 	var rowsMerged, rowsDeleted uint64
 	close(ch)
@@ -384,7 +384,7 @@ func testMergeBlockStreams(t *testing.T, bsrs []*blockStreamReader, expectedBloc
 	var mp inmemoryPart
 
 	var bsw blockStreamWriter
-	bsw.InitFromInmemoryPart(&mp)
+	bsw.InitFromInmemoryPart(&mp, -5)
 
 	strg := newTestStorage()
 	var rowsMerged, rowsDeleted uint64
diff --git a/lib/storage/merge_timing_test.go b/lib/storage/merge_timing_test.go
index 5cbbe5455..cfc440c70 100644
--- a/lib/storage/merge_timing_test.go
+++ b/lib/storage/merge_timing_test.go
@@ -41,7 +41,7 @@ func benchmarkMergeBlockStreams(b *testing.B, mps []*inmemoryPart, rowsPerLoop i
 				bsrs[i].InitFromInmemoryPart(mp)
 			}
 			mpOut.Reset()
-			bsw.InitFromInmemoryPart(&mpOut)
+			bsw.InitFromInmemoryPart(&mpOut, -5)
 			if err := mergeBlockStreams(&mpOut.ph, &bsw, bsrs, nil, strg, 0, &rowsMerged, &rowsDeleted); err != nil {
 				panic(fmt.Errorf("cannot merge block streams: %w", err))
 			}
diff --git a/lib/storage/partition_search_test.go b/lib/storage/partition_search_test.go
index 9649770df..b0e39ef7d 100644
--- a/lib/storage/partition_search_test.go
+++ b/lib/storage/partition_search_test.go
@@ -185,7 +185,7 @@ func testPartitionSearchEx(t *testing.T, ptt int64, tr TimeRange, partsCount, ma
 		pt.AddRows(rows)
 
 		// Flush just added rows to a separate partition.
-		pt.flushRawRows(true)
+		pt.flushPendingRows(true)
 	}
 	testPartitionSearch(t, pt, tsids, tr, rbsExpected, -1)
 	pt.MustClose()
diff --git a/lib/storage/storage_test.go b/lib/storage/storage_test.go
index 079e7fe43..d4d330a7e 100644
--- a/lib/storage/storage_test.go
+++ b/lib/storage/storage_test.go
@@ -157,9 +157,12 @@ func TestUpdateCurrHourMetricIDs(t *testing.T) {
 	t.Run("empty_pending_metric_ids_stale_curr_hour", func(t *testing.T) {
 		s := newStorage()
 		hour := fasttime.UnixHour()
+		if hour%24 == 0 {
+			hour++
+		}
 		hmOrig := &hourMetricIDs{
 			m:    &uint64set.Set{},
-			hour: 123,
+			hour: hour - 1,
 		}
 		hmOrig.m.Add(12)
 		hmOrig.m.Add(34)
@@ -220,9 +223,12 @@ func TestUpdateCurrHourMetricIDs(t *testing.T) {
 		s.pendingHourEntries = pendingHourEntries
 
 		hour := fasttime.UnixHour()
+		if hour%24 == 0 {
+			hour++
+		}
 		hmOrig := &hourMetricIDs{
 			m:    &uint64set.Set{},
-			hour: 123,
+			hour: hour - 1,
 		}
 		hmOrig.m.Add(12)
 		hmOrig.m.Add(34)
@@ -287,6 +293,49 @@ func TestUpdateCurrHourMetricIDs(t *testing.T) {
 			t.Fatalf("unexpected s.pendingHourEntries.Len(); got %d; want %d", s.pendingHourEntries.Len(), 0)
 		}
 	})
+	t.Run("nonempty_pending_metric_ids_valid_curr_hour_start_of_day", func(t *testing.T) {
+		s := newStorage()
+		pendingHourEntries := &uint64set.Set{}
+		pendingHourEntries.Add(343)
+		pendingHourEntries.Add(32424)
+		pendingHourEntries.Add(8293432)
+		s.pendingHourEntries = pendingHourEntries
+
+		hour := fasttime.UnixHour()
+		hour -= hour % 24
+		hmOrig := &hourMetricIDs{
+			m:    &uint64set.Set{},
+			hour: hour,
+		}
+		hmOrig.m.Add(12)
+		hmOrig.m.Add(34)
+		s.currHourMetricIDs.Store(hmOrig)
+		s.updateCurrHourMetricIDs(hour)
+		hmCurr := s.currHourMetricIDs.Load().(*hourMetricIDs)
+		if hmCurr.hour != hour {
+			t.Fatalf("unexpected hmCurr.hour; got %d; want %d", hmCurr.hour, hour)
+		}
+		m := pendingHourEntries.Clone()
+		hmOrig.m.ForEach(func(part []uint64) bool {
+			for _, metricID := range part {
+				m.Add(metricID)
+			}
+			return true
+		})
+		if !hmCurr.m.Equal(m) {
+			t.Fatalf("unexpected hm.m; got %v; want %v", hmCurr.m, m)
+		}
+
+		hmPrev := s.prevHourMetricIDs.Load().(*hourMetricIDs)
+		hmEmpty := &hourMetricIDs{}
+		if !reflect.DeepEqual(hmPrev, hmEmpty) {
+			t.Fatalf("unexpected hmPrev; got %v; want %v", hmPrev, hmEmpty)
+		}
+
+		if s.pendingHourEntries.Len() != 0 {
+			t.Fatalf("unexpected s.pendingHourEntries.Len(); got %d; want %d", s.pendingHourEntries.Len(), 0)
+		}
+	})
 	t.Run("nonempty_pending_metric_ids_from_previous_hour_new_day", func(t *testing.T) {
 		s := newStorage()
 
diff --git a/lib/storage/table_search_test.go b/lib/storage/table_search_test.go
index c9b1119dc..17e1f5b86 100644
--- a/lib/storage/table_search_test.go
+++ b/lib/storage/table_search_test.go
@@ -197,7 +197,7 @@ func testTableSearchEx(t *testing.T, trData, trSearch TimeRange, partitionsCount
 		}
 
 		// Flush rows to parts.
-		tb.flushRawRows()
+		tb.flushPendingRows()
 	}
 	testTableSearch(t, tb, tsids, trSearch, rbsExpected, -1)
 	tb.MustClose()