diff --git a/pkg/cache/metrics_middleware.go b/pkg/cache/metrics_middleware.go
index 90966127655faffca96dae0e5703528dcade9ff6..42d7f3d8d37356b784d93f175c2bbc00f9b65b13 100644
--- a/pkg/cache/metrics_middleware.go
+++ b/pkg/cache/metrics_middleware.go
@@ -4,35 +4,50 @@ import (
 	"github.com/prometheus/client_golang/prometheus"
 )
 
+const badKey = "BADKEY"
+
 type metricsMiddleware struct {
-	cache       Cache
-	hitsTotal   prometheus.Counter
-	missesTotal prometheus.Counter
+	cache            Cache
+	hitsTotal        prometheus.Counter
+	missesTotal      prometheus.Counter
+	invalidatesTotal prometheus.Counter
 }
 
 // MetricsMiddleware возвращает обертку над кэшем, которая используется для отслеживания количества хитов и промахов в кэше.
 //
+// subsystem указывает подсистему, к которой принадлежат метрики.
+// Значение должно быть уникальным, совпадение разрешено только при совпадении ключей labels. Пустое значение допустимо.
+//
+// labels - список меток, где каждый элемент метки соответствует парам ключ-значение. Отсутствие допустимо.
+// Значения меток должны быть уникальными в рамках одной subsystem.
+//
 // Метрики записываются в prometheus.DefaultRegisterer
-func MetricsMiddleware(cache Cache, serviceName string) Cache {
+func MetricsMiddleware(cache Cache, subsystem string, labels ...string) Cache {
 	if cache == nil {
 		panic("cannot wrap metrics in cache, cache is nil")
 	}
 	middleware := &metricsMiddleware{
 		cache: cache,
 		hitsTotal: prometheus.NewCounter(prometheus.CounterOpts{
-			Name:        "cache_hits_total",
-			Help:        "Количество попаданий в кэш.",
-			ConstLabels: prometheus.Labels{"service_name": serviceName},
+			Subsystem: subsystem,
+			Name:      "cache_hits_total",
+			Help:      "Количество попаданий в кэш.",
 		}),
 		missesTotal: prometheus.NewCounter(prometheus.CounterOpts{
-			Name:        "cache_misses_total",
-			Help:        "Количество пропусков в кэш.",
-			ConstLabels: prometheus.Labels{"service_name": serviceName},
+			Subsystem: subsystem,
+			Name:      "cache_misses_total",
+			Help:      "Количество пропусков в кэш.",
+		}),
+		invalidatesTotal: prometheus.NewCounter(prometheus.CounterOpts{
+			Subsystem: subsystem,
+			Name:      "cache_invalidates_total",
+			Help:      "Количество инвалидаций кэша.",
 		}),
 	}
-	prometheus.MustRegister(
+	prometheus.WrapRegistererWith(argsToLabels(labels), prometheus.DefaultRegisterer).MustRegister(
 		middleware.hitsTotal,
 		middleware.missesTotal,
+		middleware.invalidatesTotal,
 	)
 	return middleware
 }
@@ -52,5 +67,25 @@ func (c *metricsMiddleware) Get(key any) (any, error) {
 }
 
 func (c *metricsMiddleware) Remove(key any) error {
+	c.invalidatesTotal.Inc()
 	return c.cache.Remove(key)
 }
+
+// argsToLabels преобразует массив строк args в метки типа prometheus.Labels.
+//
+// Функция ожидает, что каждое значение будет следовать за соответствующим ключом в массиве args,
+// и возвращает метки, соответствующие парам ключ-значение.
+func argsToLabels(args []string) prometheus.Labels {
+	labels := make(prometheus.Labels, len(args)/2)
+	for len(args) > 0 {
+		// если в массиве args остался только один элемент, он будет рассмотрен как значение с недопустимым ключом.
+		if len(args) == 1 {
+			labels[badKey] = args[0]
+			break
+		}
+
+		labels[args[0]] = args[1]
+		args = args[2:]
+	}
+	return labels
+}
diff --git a/pkg/cache/metrics_middleware_test.go b/pkg/cache/metrics_middleware_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..3d2abf739477c04a4c0a80149748324ea73e713d
--- /dev/null
+++ b/pkg/cache/metrics_middleware_test.go
@@ -0,0 +1,55 @@
+package cache
+
+import (
+	"reflect"
+	"testing"
+
+	"github.com/prometheus/client_golang/prometheus"
+	"github.com/stretchr/testify/require"
+)
+
+func TestMetricsMiddleware_argsToLabels(t *testing.T) {
+	testcases := []struct {
+		name  string
+		input []string
+		want  prometheus.Labels
+	}{
+		{
+			name:  "input is empty",
+			input: []string{},
+			want:  prometheus.Labels{},
+		},
+		{
+			name:  "input is nil",
+			input: nil,
+			want:  prometheus.Labels{},
+		},
+		{
+			name:  "valid",
+			input: []string{"key", "value"},
+			want:  prometheus.Labels{"key": "value"},
+		},
+		{
+			name:  "multi valid",
+			input: []string{"key", "value", "key1", "value1"},
+			want:  prometheus.Labels{"key": "value", "key1": "value1"},
+		},
+		{
+			name:  "bad key",
+			input: []string{"value"},
+			want:  prometheus.Labels{badKey: "value"},
+		},
+		{
+			name:  "multi bad key",
+			input: []string{"key", "value", "value1"},
+			want:  prometheus.Labels{"key": "value", badKey: "value1"},
+		},
+	}
+
+	for _, tc := range testcases {
+		t.Run(tc.name, func(t *testing.T) {
+			got := argsToLabels(tc.input)
+			require.True(t, reflect.DeepEqual(tc.want, got))
+		})
+	}
+}