You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by ib...@apache.org on 2021/05/26 08:50:36 UTC

[ignite] branch master updated: IGNITE-14774 Implement metrics for in-memory index pages (#9125)

This is an automated email from the ASF dual-hosted git repository.

ibessonov pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ignite.git


The following commit(s) were added to refs/heads/master by this push:
     new 1b6af49  IGNITE-14774 Implement metrics for in-memory index pages (#9125)
1b6af49 is described below

commit 1b6af49dcda227e4605de5ae85619524616afdb7
Author: Alexander Polovtcev <al...@gmail.com>
AuthorDate: Wed May 26 11:50:14 2021 +0300

    IGNITE-14774 Implement metrics for in-memory index pages (#9125)
---
 .../benchmarks/jmh/tree/BPlusTreeBenchmark.java    |  39 +--
 .../compress/CompressionProcessorTest.java         |   4 +-
 modules/core/pom.xml                               |   7 +
 .../org/apache/ignite/internal/IgniteKernal.java   |   4 +-
 .../ignite/internal/pagemem/PageIdAllocator.java   |  19 +-
 .../ignite/internal/pagemem/PageIdUtils.java       |  25 +-
 .../apache/ignite/internal/pagemem/PageMemory.java |   6 +
 .../pagemem/impl/PageMemoryNoStoreImpl.java        | 123 +++++----
 .../pagemem/store/IgnitePageStoreManager.java      |   6 +-
 .../wal/record/delta/InitNewPageRecord.java        |   8 +-
 .../wal/record/delta/MetaPageInitRecord.java       |   5 +-
 .../wal/record/delta/NewRootInitRecord.java        |   5 +-
 .../record/delta/PagesListInitNewPageRecord.java   |   5 +-
 .../processors/cache/CacheGroupMetricsImpl.java    |  58 ++---
 .../processors/cache/mvcc/MvccProcessorImpl.java   |   8 +-
 .../processors/cache/mvcc/txlog/TxLog.java         |  17 +-
 .../processors/cache/persistence/DataRegion.java   |  12 +-
 .../cache/persistence/DataRegionMetricsImpl.java   | 284 +++++++++++++++------
 .../persistence/DataRegionMetricsMXBeanImpl.java   |  11 +-
 .../cache/persistence/DataStructure.java           |   8 +
 .../GridCacheDatabaseSharedManager.java            |  77 +++---
 .../cache/persistence/GridCacheOffheapManager.java |  15 +-
 .../IgniteCacheDatabaseSharedManager.java          |  59 ++---
 .../persistence/defragmentation/PageStoreMap.java  |   2 +-
 .../persistence/file/FilePageStoreManager.java     |  28 +-
 .../persistence/freelist/AbstractFreeList.java     |  18 +-
 .../cache/persistence/freelist/CacheFreeList.java  |  10 +-
 .../cache/persistence/freelist/PagesList.java      |  12 +-
 .../persistence/freelist/io/PagesListMetaIO.java   |   5 +-
 .../persistence/freelist/io/PagesListNodeIO.java   |   5 +-
 .../cache/persistence/metastorage/MetaStorage.java |  29 +--
 .../cache/persistence/pagemem/PageMemoryImpl.java  | 129 +++++-----
 .../cache/persistence/pagemem/PageMetrics.java}    |  34 +--
 .../cache/persistence/pagemem/PageMetricsImpl.java | 134 ++++++++++
 .../partstorage/PartitionMetaStorageImpl.java      |  10 +-
 .../cache/persistence/tree/BPlusTree.java          |   5 +-
 .../persistence/tree/io/AbstractDataPageIO.java    |   5 +-
 .../cache/persistence/tree/io/BPlusIO.java         |  10 +-
 .../cache/persistence/tree/io/BPlusInnerIO.java    |   6 +-
 .../cache/persistence/tree/io/PageIO.java          |  61 +++--
 .../cache/persistence/tree/io/PageMetaIO.java      |   5 +-
 .../cache/persistence/tree/io/PageMetaIOV2.java    |   5 +-
 .../tree/io/PagePartitionCountersIO.java           |   5 +-
 .../persistence/tree/io/PagePartitionMetaIO.java   |   5 +-
 .../persistence/tree/io/PagePartitionMetaIOV2.java |   5 +-
 .../persistence/tree/io/PagePartitionMetaIOV3.java |   5 +-
 .../cache/persistence/tree/util/PageHandler.java   |   5 +-
 .../internal/processors/metric/MetricRegistry.java |   5 +-
 .../processors/metric/impl/LongAdderMetric.java    |  13 +-
 .../metric/impl/LongAdderWithDelegateMetric.java   |  39 ++-
 .../processors/metric/impl/MetricUtils.java        |   9 +
 .../internal/util/collection/IntHashMap.java       |  20 +-
 .../ignite/internal/util/collection/IntMap.java    |   5 +-
 .../internal/util/collection/IntRWHashMap.java     |   3 +-
 .../internal/ClusterNodeMetricsSelfTest.java       |   2 +-
 .../pagemem/impl/PageMemoryNoLoadSelfTest.java     |  12 +-
 .../eviction/paged/PageEvictionMetricTest.java     |   2 +-
 .../IgnitePdsRecoveryAfterFileCorruptionTest.java  |   2 +-
 .../persistence/IgnitePdsTaskCancelingTest.java    |   2 +-
 .../db/IgnitePdsDataRegionMetricsTest.java         |   4 +-
 ...CheckpointSimulationWithRealCpDisabledTest.java |   8 +-
 .../db/file/IgnitePdsPageReplacementTest.java      |   2 +-
 .../persistence/db/wal/WalCompactionTest.java      |   2 +-
 .../persistence/defragmentation/LinkMapTest.java   |  18 +-
 .../pagemem/BPlusTreePageMemoryImplTest.java       |   7 +-
 .../BPlusTreeReuseListPageMemoryImplTest.java      |   7 +-
 .../IgnitePageMemReplaceDelayedWriteUnitTest.java  |  45 +---
 .../pagemem/IgniteThrottlingUnitTest.java          |   6 +-
 .../pagemem/IndexStoragePageMemoryImplTest.java    |  13 +-
 .../persistence/pagemem/NoOpPageStoreManager.java  |   3 +-
 .../pagemem/PageMemoryImplNoLoadTest.java          |  20 +-
 .../persistence/pagemem/PageMemoryImplTest.java    | 138 +++++-----
 .../pagemem/PageMemoryNoStoreLeakTest.java         |   8 +-
 .../wal/memtracker/PageMemoryTracker.java          |  58 +++--
 .../persistence/wal/scanner/WalScannerTest.java    |   2 +-
 .../cache/query/CacheDataPageScanQueryTest.java    |   2 +-
 .../processors/cluster/BaselineAutoAdjustTest.java |   7 +-
 .../processors/database/BPlusTreeSelfTest.java     |   4 +-
 .../processors/database/CacheFreeListSelfTest.java |  18 +-
 .../database/DataRegionMetricsSelfTest.java        |  18 +-
 .../processors/database/IndexStorageSelfTest.java  |   5 +-
 .../internal/util/collection/IntHashMapTest.java   |  66 ++---
 modules/indexing/pom.xml                           |   7 +
 .../metric/AbstractIndexPageMetricsTest.java       | 207 +++++++++++++++
 .../ignite/internal/metric/IndexPageCounter.java   | 102 ++++++++
 .../metric/IndexPagesMetricsInMemoryTest.java      |  47 ++++
 .../IndexPagesMetricsPageDisplacementTest.java     | 194 ++++++++++++++
 .../metric/IndexPagesMetricsPersistentTest.java    |  70 +++++
 .../inlinecolumn/InlineIndexColumnTest.java        |  76 +++---
 ...teCacheWithIndexingAndPersistenceTestSuite.java |   6 +-
 .../IgniteCacheWithIndexingTestSuite.java          |   5 +-
 .../cluster-compute-example/CMakeLists.txt         |  13 +-
 .../org/apache/ignite/yardstick/cache/Loader.java  |   2 +-
 93 files changed, 1816 insertions(+), 831 deletions(-)

diff --git a/modules/benchmarks/src/main/java/org/apache/ignite/internal/benchmarks/jmh/tree/BPlusTreeBenchmark.java b/modules/benchmarks/src/main/java/org/apache/ignite/internal/benchmarks/jmh/tree/BPlusTreeBenchmark.java
index 9013f49..9d116200 100644
--- a/modules/benchmarks/src/main/java/org/apache/ignite/internal/benchmarks/jmh/tree/BPlusTreeBenchmark.java
+++ b/modules/benchmarks/src/main/java/org/apache/ignite/internal/benchmarks/jmh/tree/BPlusTreeBenchmark.java
@@ -30,6 +30,8 @@ import org.apache.ignite.internal.pagemem.PageIdAllocator;
 import org.apache.ignite.internal.pagemem.PageMemory;
 import org.apache.ignite.internal.pagemem.PageUtils;
 import org.apache.ignite.internal.pagemem.impl.PageMemoryNoStoreImpl;
+import org.apache.ignite.internal.processors.cache.persistence.DataRegionMetricsImpl;
+import org.apache.ignite.internal.processors.cache.persistence.pagemem.PageMetrics;
 import org.apache.ignite.internal.processors.cache.persistence.tree.BPlusTree;
 import org.apache.ignite.internal.processors.cache.persistence.tree.io.BPlusIO;
 import org.apache.ignite.internal.processors.cache.persistence.tree.io.BPlusInnerIO;
@@ -46,6 +48,10 @@ import org.openjdk.jmh.annotations.Setup;
 import org.openjdk.jmh.annotations.State;
 import org.openjdk.jmh.annotations.TearDown;
 
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
 /**
  *
  */
@@ -64,9 +70,6 @@ public class BPlusTreeBenchmark extends JmhAbstractBenchmark {
     private static final long MB = 1024 * 1024;
 
     /** */
-    private static final int CPUS = Runtime.getRuntime().availableProcessors();
-
-    /** */
     static int MAX_PER_PAGE = 0;
 
     /** */
@@ -89,7 +92,7 @@ public class BPlusTreeBenchmark extends JmhAbstractBenchmark {
         private final ConcurrentLinkedDeque<Long> deque = new ConcurrentLinkedDeque<>();
 
         /** {@inheritDoc} */
-        @Override public void addForRecycle(ReuseBag bag) throws IgniteCheckedException {
+        @Override public void addForRecycle(ReuseBag bag) {
             long pageId;
 
             while ((pageId = bag.pollFreePage()) != 0L)
@@ -97,19 +100,19 @@ public class BPlusTreeBenchmark extends JmhAbstractBenchmark {
         }
 
         /** {@inheritDoc} */
-        @Override public long takeRecycledPage() throws IgniteCheckedException {
+        @Override public long takeRecycledPage() {
             Long pageId = deque.pollFirst();
 
             return pageId == null ? 0L : pageId;
         }
 
         /** {@inheritDoc} */
-        @Override public long initRecycledPage(long pageId, byte flag, PageIO initIO) throws IgniteCheckedException {
+        @Override public long initRecycledPage(long pageId, byte flag, PageIO initIO) {
             return pageId;
         }
 
         /** {@inheritDoc} */
-        @Override public long recycledPagesCount() throws IgniteCheckedException {
+        @Override public long recycledPagesCount() {
             return deque.size();
         }
     }
@@ -220,22 +223,26 @@ public class BPlusTreeBenchmark extends JmhAbstractBenchmark {
 
     /**
      * @return Page memory.
-     * @throws Exception If failed.
      */
-    private PageMemory createPageMemory() throws Exception {
-        long[] sizes = new long[CPUS];
+    private PageMemory createPageMemory() {
+        DataRegionConfiguration dataRegionConfiguration = new DataRegionConfiguration().setMaxSize(1024 * MB);
+
+        DataRegionMetricsImpl dataRegionMetrics = mock(DataRegionMetricsImpl.class);
+        PageMetrics pageMetrics = mock(PageMetrics.class);
+        LongAdderMetric noOpMetric = new LongAdderMetric("foobar", null);
 
-        for (int i = 0; i < sizes.length; i++)
-            sizes[i] = 1024 * MB / CPUS;
+        when(dataRegionMetrics.cacheGrpPageMetrics(anyInt())).thenReturn(pageMetrics);
 
-        DataRegionConfiguration plcCfg = new DataRegionConfiguration().setMaxSize(1024 * MB);
+        when(pageMetrics.totalPages()).thenReturn(noOpMetric);
+        when(pageMetrics.indexPages()).thenReturn(noOpMetric);
 
-        PageMemory pageMem = new PageMemoryNoStoreImpl(new JavaLogger(),
+        PageMemory pageMem = new PageMemoryNoStoreImpl(
+            new JavaLogger(),
             new UnsafeMemoryProvider(new JavaLogger()),
             null,
             PAGE_SIZE,
-            plcCfg,
-            new LongAdderMetric("NO_OP", null),
+            dataRegionConfiguration,
+            dataRegionMetrics,
             false);
 
         pageMem.start();
diff --git a/modules/compress/src/test/java/org/apache/ignite/internal/processors/compress/CompressionProcessorTest.java b/modules/compress/src/test/java/org/apache/ignite/internal/processors/compress/CompressionProcessorTest.java
index 4a4a250..e80db68 100644
--- a/modules/compress/src/test/java/org/apache/ignite/internal/processors/compress/CompressionProcessorTest.java
+++ b/modules/compress/src/test/java/org/apache/ignite/internal/processors/compress/CompressionProcessorTest.java
@@ -804,7 +804,7 @@ public class CompressionProcessorTest extends GridCommonAbstractTest {
 
         long pageId = PageIdUtils.pageId(PageIdAllocator.INDEX_PARTITION, PageIdAllocator.FLAG_IDX, 171717);
 
-        io.initNewPage(pageAddr, pageId, pageSize);
+        io.initNewPage(pageAddr, pageId, pageSize, null);
 
         checkIo(io, page);
 
@@ -875,7 +875,7 @@ public class CompressionProcessorTest extends GridCommonAbstractTest {
 
         long pageId = PageIdUtils.pageId(PageIdAllocator.MAX_PARTITION_ID, PageIdAllocator.FLAG_DATA, 171717);
 
-        io.initNewPage(pageAddr, pageId, pageSize);
+        io.initNewPage(pageAddr, pageId, pageSize, null);
 
         checkIo(io, page);
 
diff --git a/modules/core/pom.xml b/modules/core/pom.xml
index 3a16475..f6e5a56 100644
--- a/modules/core/pom.xml
+++ b/modules/core/pom.xml
@@ -232,6 +232,13 @@
             <version>${commons.lang3.version}</version>
             <scope>test</scope>
         </dependency>
+
+        <dependency>
+            <groupId>org.hamcrest</groupId>
+            <artifactId>hamcrest-library</artifactId>
+            <version>${hamcrest.version}</version>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
     <build>
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/IgniteKernal.java b/modules/core/src/main/java/org/apache/ignite/internal/IgniteKernal.java
index 482c38f..a370b4f 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/IgniteKernal.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/IgniteKernal.java
@@ -2387,7 +2387,7 @@ public class IgniteKernal implements IgniteEx, IgniteMXBean, Externalizable {
                 long offHeapUsed = region.pageMemory().systemPageSize() * pagesCnt;
                 long offHeapInit = regCfg.getInitialSize();
                 long offHeapMax = regCfg.getMaxSize();
-                long offHeapComm = region.memoryMetrics().getOffHeapSize();
+                long offHeapComm = region.metrics().getOffHeapSize();
 
                 long offHeapUsedInMBytes = offHeapUsed / MEGABYTE;
                 long offHeapMaxInMBytes = offHeapMax / MEGABYTE;
@@ -2427,7 +2427,7 @@ public class IgniteKernal implements IgniteEx, IgniteMXBean, Externalizable {
                     .a("%, allocRam=").a(dblFmt.format(offHeapCommInMBytes)).a("MB");
 
                 if (regCfg.isPersistenceEnabled()) {
-                    long pdsUsed = region.memoryMetrics().getTotalAllocatedSize();
+                    long pdsUsed = region.metrics().getTotalAllocatedSize();
                     long pdsUsedInMBytes = pdsUsed / MEGABYTE;
 
                     pdsUsedSummary += pdsUsed;
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/pagemem/PageIdAllocator.java b/modules/core/src/main/java/org/apache/ignite/internal/pagemem/PageIdAllocator.java
index 04cdcdf..f945beb 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/pagemem/PageIdAllocator.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/pagemem/PageIdAllocator.java
@@ -16,7 +16,6 @@
  */
 
 package org.apache.ignite.internal.pagemem;
-
 import org.apache.ignite.IgniteCheckedException;
 
 import static org.apache.ignite.internal.pagemem.PageIdUtils.pageId;
@@ -26,22 +25,22 @@ import static org.apache.ignite.internal.pagemem.PageIdUtils.pageId;
  */
 public interface PageIdAllocator {
     /**
-     * Flag for Data page.
+     * Flag for a Data page.
      * Also used by partition meta and tracking pages.
-     * This type doesn't use Page ID rotation mechanizm.
+     * This type doesn't use the Page ID rotation mechanism.
      */
     public static final byte FLAG_DATA = 1;
 
     /**
-     * Flag for index page.
-     * Also used by internal structure in inmemory caches.
-     * This type uses Page ID rotation mechanizm.
+     * Flag for an index page.
+     * Also used by internal structure in in-memory caches.
+     * This type uses the Page ID rotation mechanism.
      */
     public static final byte FLAG_IDX = 2;
 
     /**
-     * Flag for internal structure page.
-     * This type uses Page ID rotation mechanizm.
+     * Flag for an internal structure page.
+     * This type uses the Page ID rotation mechanism.
      */
     public static final byte FLAG_AUX = 4;
 
@@ -66,8 +65,8 @@ public interface PageIdAllocator {
     /**
      * The given page is free now.
      *
-     * @param cacheId Cache Group ID.
+     * @param grpId Cache Group ID.
      * @param pageId Page ID.
      */
-    public boolean freePage(int cacheId, long pageId) throws IgniteCheckedException;
+    public boolean freePage(int grpId, long pageId) throws IgniteCheckedException;
 }
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/pagemem/PageIdUtils.java b/modules/core/src/main/java/org/apache/ignite/internal/pagemem/PageIdUtils.java
index 395586c..3037f8e 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/pagemem/PageIdUtils.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/pagemem/PageIdUtils.java
@@ -58,8 +58,13 @@ public final class PageIdUtils {
     /** */
     private static final long EFFECTIVE_PAGE_ID_MASK = ~(-1L << (PAGE_IDX_SIZE + PART_ID_SIZE));
 
+    /**
+     * Offset of a Rotation ID inside a Page ID.
+     */
+    private static final long ROTATION_ID_OFFSET = PAGE_IDX_SIZE + PART_ID_SIZE + FLAG_SIZE;
+
     /** */
-    private static final long PAGE_ID_MASK = ~(-1L << (PAGE_IDX_SIZE + PART_ID_SIZE + FLAG_SIZE));
+    private static final long PAGE_ID_MASK = ~(-1L << ROTATION_ID_OFFSET);
 
     /** Max itemid number. */
     public static final int MAX_ITEMID_NUM = 0xFE;
@@ -86,9 +91,9 @@ public final class PageIdUtils {
      */
     public static long link(long pageId, int itemId) {
         assert itemId >= 0 && itemId <= MAX_ITEMID_NUM : itemId;
-        assert (pageId >> (FLAG_SIZE + PART_ID_SIZE + PAGE_IDX_SIZE)) == 0 : U.hexLong(pageId);
+        assert (pageId >> ROTATION_ID_OFFSET) == 0 : U.hexLong(pageId);
 
-        return pageId | (((long)itemId) << (FLAG_SIZE + PART_ID_SIZE + PAGE_IDX_SIZE));
+        return pageId | (((long)itemId) << ROTATION_ID_OFFSET);
     }
 
     /**
@@ -134,7 +139,7 @@ public final class PageIdUtils {
      * @return Offset in 8-byte words.
      */
     public static int itemId(long link) {
-        return (int)((link >> (PAGE_IDX_SIZE + PART_ID_SIZE + FLAG_SIZE)) & OFFSET_MASK);
+        return (int)((link >> ROTATION_ID_OFFSET) & OFFSET_MASK);
     }
 
     /**
@@ -179,17 +184,23 @@ public final class PageIdUtils {
     }
 
     /**
+     * Returns the Rotation ID of a page identified by the given ID.
+     */
+    public static long rotationId(long pageId) {
+        return pageId >>> ROTATION_ID_OFFSET;
+    }
+
+    /**
      * @param pageId Page ID.
      * @return New page ID.
      */
     public static long rotatePageId(long pageId) {
-        long updatedRotationId = (pageId >>> PAGE_IDX_SIZE + PART_ID_SIZE + FLAG_SIZE) + 1;
+        long updatedRotationId = rotationId(pageId) + 1;
 
         if (updatedRotationId > MAX_ITEMID_NUM)
             updatedRotationId = 1; // We always want non-zero updatedRotationId
 
-        return (pageId & PAGE_ID_MASK) |
-            (updatedRotationId << (PAGE_IDX_SIZE + PART_ID_SIZE + FLAG_SIZE));
+        return (pageId & PAGE_ID_MASK) | (updatedRotationId << ROTATION_ID_OFFSET);
     }
 
     /**
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/pagemem/PageMemory.java b/modules/core/src/main/java/org/apache/ignite/internal/pagemem/PageMemory.java
index bd03c54..e922006 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/pagemem/PageMemory.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/pagemem/PageMemory.java
@@ -19,6 +19,7 @@ package org.apache.ignite.internal.pagemem;
 
 import java.nio.ByteBuffer;
 import org.apache.ignite.IgniteException;
+import org.apache.ignite.internal.processors.cache.persistence.DataRegionMetricsImpl;
 
 /**
  */
@@ -67,4 +68,9 @@ public interface PageMemory extends PageIdAllocator, PageSupport {
      * Number of pages used in checkpoint buffer.
      */
     public int checkpointBufferPagesCount();
+
+    /**
+     * Metrics of the data region this memory is associated with.
+     */
+    public DataRegionMetricsImpl metrics();
 }
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/pagemem/impl/PageMemoryNoStoreImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/pagemem/impl/PageMemoryNoStoreImpl.java
index dbde462..4023536 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/pagemem/impl/PageMemoryNoStoreImpl.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/pagemem/impl/PageMemoryNoStoreImpl.java
@@ -38,13 +38,15 @@ import org.apache.ignite.internal.metric.IoStatisticsHolderNoOp;
 import org.apache.ignite.internal.pagemem.PageIdUtils;
 import org.apache.ignite.internal.pagemem.PageMemory;
 import org.apache.ignite.internal.processors.cache.GridCacheSharedContext;
+import org.apache.ignite.internal.processors.cache.persistence.DataRegionMetricsImpl;
+import org.apache.ignite.internal.processors.cache.persistence.pagemem.PageMetrics;
 import org.apache.ignite.internal.processors.cache.persistence.tree.io.PageIO;
-import org.apache.ignite.internal.processors.metric.impl.LongAdderMetric;
 import org.apache.ignite.internal.util.GridUnsafe;
 import org.apache.ignite.internal.util.IgniteUtils;
 import org.apache.ignite.internal.util.OffheapReadWriteLock;
 import org.apache.ignite.internal.util.offheap.GridOffHeapOutOfMemoryException;
 import org.apache.ignite.internal.util.typedef.internal.U;
+import org.jetbrains.annotations.TestOnly;
 
 import static org.apache.ignite.IgniteSystemProperties.IGNITE_OFFHEAP_LOCK_CONCURRENCY_LEVEL;
 import static org.apache.ignite.internal.util.GridUnsafe.wrapPointer;
@@ -120,7 +122,7 @@ public class PageMemoryNoStoreImpl implements PageMemory {
     private static final int IDX_MASK = ~(-1 << IDX_BITS);
 
     /** Page size. */
-    private int sysPageSize;
+    private final int sysPageSize;
 
     /** */
     private final IgniteLogger log;
@@ -132,25 +134,25 @@ public class PageMemoryNoStoreImpl implements PageMemory {
     private final DataRegionConfiguration dataRegionCfg;
 
     /** */
-    private AtomicLong freePageListHead = new AtomicLong(INVALID_REL_PTR);
+    private final AtomicLong freePageListHead = new AtomicLong(INVALID_REL_PTR);
 
     /** Segments array. */
     private volatile Segment[] segments;
 
     /** Lock for segments changes. */
-    private Object segmentsLock = new Object();
+    private final Object segmentsLock = new Object();
 
     /** */
     private final AtomicInteger allocatedPages = new AtomicInteger();
 
     /** */
-    private final LongAdderMetric totalAllocatedPagesMetric;
+    private final DataRegionMetricsImpl dataRegionMetrics;
 
     /** */
-    private AtomicInteger selector = new AtomicInteger();
+    private final AtomicInteger selector = new AtomicInteger();
 
     /** */
-    private OffheapReadWriteLock rwLock;
+    private final OffheapReadWriteLock rwLock;
 
     /** Concurrency lvl. */
     private final int lockConcLvl = IgniteSystemProperties.getInteger(
@@ -173,32 +175,57 @@ public class PageMemoryNoStoreImpl implements PageMemory {
     private volatile boolean started;
 
     /**
-     * @param log Logger.
      * @param directMemoryProvider Memory allocator to use.
      * @param sharedCtx Cache shared context.
      * @param pageSize Page size.
      * @param dataRegionCfg Data region configuration.
-     * @param totalAllocatedPagesMetric Total allocated pages metric.
+     * @param dataRegionMetrics Data region metrics.
+     */
+    public PageMemoryNoStoreImpl(
+        GridCacheSharedContext<?, ?> sharedCtx,
+        DirectMemoryProvider directMemoryProvider,
+        int pageSize,
+        DataRegionConfiguration dataRegionCfg,
+        DataRegionMetricsImpl dataRegionMetrics
+    ) {
+        this(
+            sharedCtx.logger(PageMemoryNoStoreImpl.class),
+            directMemoryProvider,
+            sharedCtx,
+            pageSize,
+            dataRegionCfg,
+            dataRegionMetrics,
+            false
+        );
+    }
+
+    /**
+     * @param log Logger.
+     * @param directMemoryProvider Direct memory provider.
+     * @param pageSize Page size.
+     * @param dataRegionCfg Data region config.
+     * @param dataRegionMetrics Data region metrics.
      * @param trackAcquiredPages If {@code true} tracks number of allocated pages (for tests purpose only).
      */
+    @TestOnly
     public PageMemoryNoStoreImpl(
         IgniteLogger log,
         DirectMemoryProvider directMemoryProvider,
         GridCacheSharedContext<?, ?> sharedCtx,
         int pageSize,
         DataRegionConfiguration dataRegionCfg,
-        LongAdderMetric totalAllocatedPagesMetric,
+        DataRegionMetricsImpl dataRegionMetrics,
         boolean trackAcquiredPages
     ) {
-        assert log != null || sharedCtx != null;
+        assert log != null;
         assert pageSize % 8 == 0;
 
-        this.log = sharedCtx != null ? sharedCtx.logger(PageMemoryNoStoreImpl.class) : log;
+        this.log = log;
         this.directMemoryProvider = directMemoryProvider;
         this.trackAcquiredPages = trackAcquiredPages;
-        this.totalAllocatedPagesMetric = totalAllocatedPagesMetric;
         this.dataRegionCfg = dataRegionCfg;
         this.ctx = sharedCtx;
+        this.dataRegionMetrics = dataRegionMetrics;
 
         sysPageSize = pageSize + PAGE_OVERHEAD;
 
@@ -283,7 +310,7 @@ public class PageMemoryNoStoreImpl implements PageMemory {
     @Override public long allocatePage(int grpId, int partId, byte flags) {
         assert started;
 
-        long relPtr = borrowFreePage();
+        long relPtr = borrowFreePage(grpId);
         long absPtr = 0;
 
         if (relPtr != INVALID_REL_PTR) {
@@ -293,9 +320,8 @@ public class PageMemoryNoStoreImpl implements PageMemory {
 
             absPtr = seg.absolute(pageIdx);
         }
-
-        // No segments contained a free page.
-        if (relPtr == INVALID_REL_PTR) {
+        else {
+            // No segments contained a free page.
             Segment[] seg0 = segments;
             Segment allocSeg = seg0[seg0.length - 1];
 
@@ -305,6 +331,12 @@ public class PageMemoryNoStoreImpl implements PageMemory {
                 if (relPtr != INVALID_REL_PTR) {
                     absPtr = allocSeg.absolute(PageIdUtils.pageIndex(relPtr));
 
+                    allocatedPages.incrementAndGet();
+
+                    PageMetrics grpPageMetrics = dataRegionMetrics.cacheGrpPageMetrics(grpId);
+
+                    grpPageMetrics.totalPages().increment();
+
                     break;
                 }
                 else
@@ -343,10 +375,10 @@ public class PageMemoryNoStoreImpl implements PageMemory {
     }
 
     /** {@inheritDoc} */
-    @Override public boolean freePage(int cacheId, long pageId) {
+    @Override public boolean freePage(int grpId, long pageId) {
         assert started;
 
-        releaseFreePage(pageId);
+        releaseFreePage(grpId, pageId);
 
         return true;
     }
@@ -603,7 +635,7 @@ public class PageMemoryNoStoreImpl implements PageMemory {
     /**
      * @param pageId Page ID to release.
      */
-    private void releaseFreePage(long pageId) {
+    private void releaseFreePage(int grpId, long pageId) {
         int pageIdx = PageIdUtils.pageIndex(pageId);
 
         // Clear out flags and file ID.
@@ -627,7 +659,9 @@ public class PageMemoryNoStoreImpl implements PageMemory {
             if (freePageListHead.compareAndSet(freePageRelPtrMasked, relPtr)) {
                 allocatedPages.decrementAndGet();
 
-                totalAllocatedPagesMetric.decrement();
+                PageMetrics pageMetrics = dataRegionMetrics.cacheGrpPageMetrics(grpId);
+
+                pageMetrics.totalPages().decrement();
 
                 return;
             }
@@ -637,33 +671,35 @@ public class PageMemoryNoStoreImpl implements PageMemory {
     /**
      * @return Relative pointer to a free page that was borrowed from the allocated pool.
      */
-    private long borrowFreePage() {
+    private long borrowFreePage(int grpId) {
         while (true) {
             long freePageRelPtrMasked = freePageListHead.get();
 
             long freePageRelPtr = freePageRelPtrMasked & ADDRESS_MASK;
 
-            if (freePageRelPtr != INVALID_REL_PTR) {
-                int pageIdx = PageIdUtils.pageIndex(freePageRelPtr);
+            // no free pages available
+            if (freePageRelPtr == INVALID_REL_PTR)
+                return INVALID_REL_PTR;
 
-                Segment seg = segment(pageIdx);
+            int pageIdx = PageIdUtils.pageIndex(freePageRelPtr);
 
-                long freePageAbsPtr = seg.absolute(pageIdx);
-                long nextFreePageRelPtr = GridUnsafe.getLong(freePageAbsPtr) & ADDRESS_MASK;
-                long cnt = ((freePageRelPtrMasked & COUNTER_MASK) + COUNTER_INC) & COUNTER_MASK;
+            Segment seg = segment(pageIdx);
 
-                if (freePageListHead.compareAndSet(freePageRelPtrMasked, nextFreePageRelPtr | cnt)) {
-                    GridUnsafe.putLong(freePageAbsPtr, PAGE_MARKER);
+            long freePageAbsPtr = seg.absolute(pageIdx);
+            long nextFreePageRelPtr = GridUnsafe.getLong(freePageAbsPtr) & ADDRESS_MASK;
+            long cnt = ((freePageRelPtrMasked & COUNTER_MASK) + COUNTER_INC) & COUNTER_MASK;
 
-                    allocatedPages.incrementAndGet();
+            if (freePageListHead.compareAndSet(freePageRelPtrMasked, nextFreePageRelPtr | cnt)) {
+                GridUnsafe.putLong(freePageAbsPtr, PAGE_MARKER);
 
-                    totalAllocatedPagesMetric.increment();
+                allocatedPages.incrementAndGet();
 
-                    return freePageRelPtr;
-                }
+                PageMetrics grpPageMetrics = dataRegionMetrics.cacheGrpPageMetrics(grpId);
+
+                grpPageMetrics.totalPages().increment();
+
+                return freePageRelPtr;
             }
-            else
-                return INVALID_REL_PTR;
         }
     }
 
@@ -717,10 +753,10 @@ public class PageMemoryNoStoreImpl implements PageMemory {
         private static final long serialVersionUID = 0L;
 
         /** Segment index. */
-        private int idx;
+        private final int idx;
 
         /** Direct memory chunk. */
-        private DirectMemoryRegion region;
+        private final DirectMemoryRegion region;
 
         /** Last allocated page index. */
         private long lastAllocatedIdxPtr;
@@ -729,7 +765,7 @@ public class PageMemoryNoStoreImpl implements PageMemory {
         private long pagesBase;
 
         /** */
-        private int pagesInPrevSegments;
+        private final int pagesInPrevSegments;
 
         /** */
         private int maxPages;
@@ -857,10 +893,6 @@ public class PageMemoryNoStoreImpl implements PageMemory {
 
                     rwLock.init(absPtr + LOCK_OFFSET, tag);
 
-                    allocatedPages.incrementAndGet();
-
-                    totalAllocatedPagesMetric.increment();
-
                     return pageIdx;
                 }
             }
@@ -894,4 +926,9 @@ public class PageMemoryNoStoreImpl implements PageMemory {
     @Override public int checkpointBufferPagesCount() {
         return 0;
     }
+
+    /** {@inheritDoc} */
+    @Override public DataRegionMetricsImpl metrics() {
+        return dataRegionMetrics;
+    }
 }
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/pagemem/store/IgnitePageStoreManager.java b/modules/core/src/main/java/org/apache/ignite/internal/pagemem/store/IgnitePageStoreManager.java
index 30459e0..957a4a5 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/pagemem/store/IgnitePageStoreManager.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/pagemem/store/IgnitePageStoreManager.java
@@ -19,7 +19,6 @@ package org.apache.ignite.internal.pagemem.store;
 
 import java.nio.ByteBuffer;
 import java.util.Map;
-import java.util.function.LongConsumer;
 import java.util.function.Predicate;
 import org.apache.ignite.IgniteCheckedException;
 import org.apache.ignite.configuration.CacheConfiguration;
@@ -28,6 +27,7 @@ import org.apache.ignite.internal.processors.cache.CacheGroupContext;
 import org.apache.ignite.internal.processors.cache.CacheGroupDescriptor;
 import org.apache.ignite.internal.processors.cache.GridCacheSharedManager;
 import org.apache.ignite.internal.processors.cache.StoredCacheData;
+import org.apache.ignite.internal.processors.cache.persistence.pagemem.PageMetrics;
 import org.apache.ignite.internal.processors.cache.persistence.pagemem.PageReadWriteManager;
 import org.apache.ignite.internal.processors.cluster.IgniteChangeGlobalStateSupport;
 
@@ -51,10 +51,10 @@ public interface IgnitePageStoreManager extends GridCacheSharedManager, IgniteCh
      * @param cacheId Cache id.
      * @param partitions Partitions count.
      * @param workingDir Working directory.
-     * @param tracker Allocation tracker.
+     * @param pageMetrics Page metrics.
      * @throws IgniteCheckedException If failed.
      */
-    void initialize(int cacheId, int partitions, String workingDir, LongConsumer tracker)
+    public void initialize(int cacheId, int partitions, String workingDir, PageMetrics pageMetrics)
         throws IgniteCheckedException;
 
     /**
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/pagemem/wal/record/delta/InitNewPageRecord.java b/modules/core/src/main/java/org/apache/ignite/internal/pagemem/wal/record/delta/InitNewPageRecord.java
index 60a60c0..e5ffec7 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/pagemem/wal/record/delta/InitNewPageRecord.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/pagemem/wal/record/delta/InitNewPageRecord.java
@@ -21,6 +21,7 @@ import org.apache.ignite.IgniteCheckedException;
 import org.apache.ignite.IgniteLogger;
 import org.apache.ignite.internal.pagemem.PageIdUtils;
 import org.apache.ignite.internal.pagemem.PageMemory;
+import org.apache.ignite.internal.processors.cache.persistence.pagemem.PageMetrics;
 import org.apache.ignite.internal.processors.cache.persistence.tree.io.PageIO;
 import org.apache.ignite.internal.util.tostring.GridToStringExclude;
 import org.apache.ignite.internal.util.typedef.internal.S;
@@ -28,7 +29,7 @@ import org.apache.ignite.internal.util.typedef.internal.U;
 import org.jetbrains.annotations.Nullable;
 
 /**
- * Initializes new page by calling {@link PageIO#initNewPage(long, long, int)}.
+ * Initializes new page by calling {@link PageIO#initNewPage(long, long, int, PageMetrics)}.
  */
 public class InitNewPageRecord extends PageDeltaRecord {
     /** */
@@ -91,7 +92,9 @@ public class InitNewPageRecord extends PageDeltaRecord {
     @Override public void applyDelta(PageMemory pageMem, long pageAddr) throws IgniteCheckedException {
         PageIO io = PageIO.getPageIO(ioType, ioVer);
 
-        io.initNewPage(pageAddr, newPageId, pageMem.realPageSize(groupId()));
+        PageMetrics metrics = pageMem.metrics().cacheGrpPageMetrics(groupId());
+
+        io.initNewPage(pageAddr, newPageId, pageMem.realPageSize(groupId()), metrics);
     }
 
     /** {@inheritDoc} */
@@ -127,4 +130,3 @@ public class InitNewPageRecord extends PageDeltaRecord {
             "super", super.toString());
     }
 }
-
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/pagemem/wal/record/delta/MetaPageInitRecord.java b/modules/core/src/main/java/org/apache/ignite/internal/pagemem/wal/record/delta/MetaPageInitRecord.java
index 3e3bb4f..6a64960 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/pagemem/wal/record/delta/MetaPageInitRecord.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/pagemem/wal/record/delta/MetaPageInitRecord.java
@@ -20,6 +20,7 @@ package org.apache.ignite.internal.pagemem.wal.record.delta;
 import org.apache.ignite.IgniteCheckedException;
 import org.apache.ignite.IgniteLogger;
 import org.apache.ignite.internal.pagemem.PageMemory;
+import org.apache.ignite.internal.processors.cache.persistence.pagemem.PageMetrics;
 import org.apache.ignite.internal.processors.cache.persistence.tree.io.PageMetaIO;
 import org.apache.ignite.internal.util.typedef.internal.S;
 import org.jetbrains.annotations.Nullable;
@@ -96,7 +97,9 @@ public class MetaPageInitRecord extends InitNewPageRecord {
     @Override public void applyDelta(PageMemory pageMem, long pageAddr) throws IgniteCheckedException {
         PageMetaIO io = PageMetaIO.getPageIO(ioType, ioVer);
 
-        io.initNewPage(pageAddr, newPageId, pageMem.realPageSize(groupId()));
+        PageMetrics metrics = pageMem.metrics().cacheGrpPageMetrics(groupId());
+
+        io.initNewPage(pageAddr, newPageId, pageMem.realPageSize(groupId()), metrics);
 
         io.setTreeRoot(pageAddr, treeRoot);
         io.setReuseListRoot(pageAddr, reuseListRoot);
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/pagemem/wal/record/delta/NewRootInitRecord.java b/modules/core/src/main/java/org/apache/ignite/internal/pagemem/wal/record/delta/NewRootInitRecord.java
index 1d78033..66548b2 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/pagemem/wal/record/delta/NewRootInitRecord.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/pagemem/wal/record/delta/NewRootInitRecord.java
@@ -19,6 +19,7 @@ package org.apache.ignite.internal.pagemem.wal.record.delta;
 
 import org.apache.ignite.IgniteCheckedException;
 import org.apache.ignite.internal.pagemem.PageMemory;
+import org.apache.ignite.internal.processors.cache.persistence.pagemem.PageMetrics;
 import org.apache.ignite.internal.processors.cache.persistence.tree.io.BPlusInnerIO;
 import org.apache.ignite.internal.util.typedef.internal.S;
 
@@ -71,8 +72,10 @@ public class NewRootInitRecord<L> extends PageDeltaRecord {
 
     /** {@inheritDoc} */
     @Override public void applyDelta(PageMemory pageMem, long pageAddr) throws IgniteCheckedException {
+        PageMetrics metrics = pageMem.metrics().cacheGrpPageMetrics(groupId());
+
         io.initNewRoot(pageAddr, newRootId, leftChildId, null, rowBytes, rightChildId, pageMem.realPageSize(groupId()),
-            false);
+            false, metrics);
     }
 
     /** {@inheritDoc} */
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/pagemem/wal/record/delta/PagesListInitNewPageRecord.java b/modules/core/src/main/java/org/apache/ignite/internal/pagemem/wal/record/delta/PagesListInitNewPageRecord.java
index 456314a..65bb565 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/pagemem/wal/record/delta/PagesListInitNewPageRecord.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/pagemem/wal/record/delta/PagesListInitNewPageRecord.java
@@ -21,6 +21,7 @@ import org.apache.ignite.IgniteCheckedException;
 import org.apache.ignite.IgniteLogger;
 import org.apache.ignite.internal.pagemem.PageMemory;
 import org.apache.ignite.internal.processors.cache.persistence.freelist.io.PagesListNodeIO;
+import org.apache.ignite.internal.processors.cache.persistence.pagemem.PageMetrics;
 import org.apache.ignite.internal.processors.cache.persistence.tree.io.PageIO;
 import org.apache.ignite.internal.util.tostring.GridToStringExclude;
 import org.apache.ignite.internal.util.typedef.internal.S;
@@ -104,7 +105,9 @@ public class PagesListInitNewPageRecord extends InitNewPageRecord {
     @Override public void applyDelta(PageMemory pageMem, long pageAddr) throws IgniteCheckedException {
         PagesListNodeIO io = PageIO.getPageIO(PageIO.T_PAGE_LIST_NODE, ioVer);
 
-        io.initNewPage(pageAddr, pageId(), pageMem.realPageSize(groupId()));
+        PageMetrics metrics = pageMem.metrics().cacheGrpPageMetrics(groupId());
+
+        io.initNewPage(pageAddr, pageId(), pageMem.realPageSize(groupId()), metrics);
         io.setPreviousId(pageAddr, prevPageId);
 
         if (addDataPageId != 0L) {
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/CacheGroupMetricsImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/CacheGroupMetricsImpl.java
index 10d39eb..3fa3bbd 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/CacheGroupMetricsImpl.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/CacheGroupMetricsImpl.java
@@ -29,6 +29,7 @@ import org.apache.ignite.cache.CacheMode;
 import org.apache.ignite.cluster.ClusterNode;
 import org.apache.ignite.configuration.CacheConfiguration;
 import org.apache.ignite.configuration.DataStorageConfiguration;
+import org.apache.ignite.internal.GridKernalContext;
 import org.apache.ignite.internal.pagemem.store.PageStore;
 import org.apache.ignite.internal.processors.affinity.AffinityAssignment;
 import org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion;
@@ -36,14 +37,13 @@ import org.apache.ignite.internal.processors.cache.distributed.dht.preloader.Gri
 import org.apache.ignite.internal.processors.cache.distributed.dht.preloader.GridDhtPartitionMap;
 import org.apache.ignite.internal.processors.cache.distributed.dht.topology.GridDhtLocalPartition;
 import org.apache.ignite.internal.processors.cache.distributed.dht.topology.GridDhtPartitionState;
-import org.apache.ignite.internal.processors.cache.persistence.DataRegion;
-import org.apache.ignite.internal.processors.cache.persistence.DataRegionMetricsImpl;
 import org.apache.ignite.internal.processors.cache.persistence.GridCacheDatabaseSharedManager;
+import org.apache.ignite.internal.processors.cache.persistence.pagemem.PageMetrics;
 import org.apache.ignite.internal.processors.metric.MetricRegistry;
 import org.apache.ignite.internal.processors.metric.impl.AtomicLongMetric;
-import org.apache.ignite.internal.processors.metric.impl.LongAdderMetric;
 import org.apache.ignite.internal.util.typedef.internal.CU;
 import org.apache.ignite.spi.metric.LongMetric;
+import org.jetbrains.annotations.Nullable;
 
 import static org.apache.ignite.internal.processors.metric.impl.MetricUtils.cacheMetricsRegistryName;
 import static org.apache.ignite.internal.processors.metric.impl.MetricUtils.metricName;
@@ -62,16 +62,19 @@ public class CacheGroupMetricsImpl {
     private final CacheGroupContext ctx;
 
     /** */
-    private final LongAdderMetric grpPageAllocationTracker;
-
-    /** */
     private final LongMetric storageSize;
 
     /** */
     private final LongMetric sparseStorageSize;
 
     /** Number of local partitions initialized on current node. */
-    private final AtomicLongMetric initLocalPartitionsNumber;
+    private final AtomicLongMetric initLocPartitionsNum;
+
+    /**
+     * Memory page metrics. Will be {@code null} on client nodes.
+     */
+    @Nullable
+    private final PageMetrics pageMetrics;
 
     /** Interface describing a predicate of two integers. */
     private interface IntBiPredicate {
@@ -88,41 +91,36 @@ public class CacheGroupMetricsImpl {
     public CacheGroupMetricsImpl(CacheGroupContext ctx) {
         this.ctx = ctx;
 
-        CacheConfiguration cacheCfg = ctx.config();
+        CacheConfiguration<?, ?> cacheCfg = ctx.config();
 
-        DataStorageConfiguration dsCfg = ctx.shared().kernalContext().config().getDataStorageConfiguration();
+        GridKernalContext kernalCtx = ctx.shared().kernalContext();
 
-        boolean persistentEnabled = !ctx.shared().kernalContext().clientNode() && CU.isPersistentCache(cacheCfg, dsCfg);
+        DataStorageConfiguration dsCfg = kernalCtx.config().getDataStorageConfiguration();
 
-        MetricRegistry mreg = ctx.shared().kernalContext().metric().registry(metricGroupName());
+        boolean persistenceEnabled = !kernalCtx.clientNode() && CU.isPersistentCache(cacheCfg, dsCfg);
+
+        MetricRegistry mreg = kernalCtx.metric().registry(metricGroupName());
 
         mreg.register("Caches", this::getCaches, List.class, null);
 
         storageSize = mreg.register("StorageSize",
-            () -> persistentEnabled ? database().forGroupPageStores(ctx, PageStore::size) : 0,
+            () -> persistenceEnabled ? database().forGroupPageStores(ctx, PageStore::size) : 0,
             "Storage space allocated for group, in bytes.");
 
         sparseStorageSize = mreg.register("SparseStorageSize",
-            () -> persistentEnabled ? database().forGroupPageStores(ctx, PageStore::getSparseSize) : 0,
+            () -> persistenceEnabled ? database().forGroupPageStores(ctx, PageStore::getSparseSize) : 0,
             "Storage space allocated for group adjusted for possible sparsity, in bytes.");
 
         idxBuildCntPartitionsLeft = mreg.longMetric("IndexBuildCountPartitionsLeft",
             "Number of partitions need processed for finished indexes create or rebuilding.");
 
-        initLocalPartitionsNumber =
-            mreg.longMetric("InitializedLocalPartitionsNumber", "Number of local partitions initialized on current node.");
-
-        DataRegion region = ctx.dataRegion();
-
-        // On client node, region is null.
-        if (region != null) {
-            DataRegionMetricsImpl dataRegionMetrics = ctx.dataRegion().memoryMetrics();
+        initLocPartitionsNum = mreg.longMetric("InitializedLocalPartitionsNumber",
+            "Number of local partitions initialized on current node.");
 
-            grpPageAllocationTracker =
-                dataRegionMetrics.getOrAllocateGroupPageAllocationTracker(ctx.cacheOrGroupName());
-        }
-        else
-            grpPageAllocationTracker = new LongAdderMetric("NO_OP", null);
+        // disable memory page metrics for client nodes (dataRegion is null on client nodes)
+        pageMetrics = ctx.dataRegion() == null ?
+            null :
+            ctx.dataRegion().metrics().cacheGrpPageMetrics(ctx.groupId());
     }
 
     /** Callback for initializing metrics after topology was initialized. */
@@ -209,14 +207,14 @@ public class CacheGroupMetricsImpl {
      * Increments number of local partitions initialized on current node.
      */
     public void incrementInitializedLocalPartitions() {
-        initLocalPartitionsNumber.increment();
+        initLocPartitionsNum.increment();
     }
 
     /**
      * Decrements number of local partitions initialized on current node.
      */
     public void decrementInitializedLocalPartitions() {
-        initLocalPartitionsNumber.decrement();
+        initLocPartitionsNum.decrement();
     }
 
     /** */
@@ -475,9 +473,7 @@ public class CacheGroupMetricsImpl {
 
     /** */
     public long getTotalAllocatedPages() {
-        return ctx.shared().kernalContext().clientNode() ?
-            0 :
-            grpPageAllocationTracker.value();
+        return pageMetrics == null ? 0 : pageMetrics.totalPages().value();
     }
 
     /** */
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/mvcc/MvccProcessorImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/mvcc/MvccProcessorImpl.java
index 4d8b681..0e64513 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/mvcc/MvccProcessorImpl.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/mvcc/MvccProcessorImpl.java
@@ -79,9 +79,11 @@ import org.apache.ignite.internal.processors.cache.mvcc.txlog.TxKey;
 import org.apache.ignite.internal.processors.cache.mvcc.txlog.TxLog;
 import org.apache.ignite.internal.processors.cache.mvcc.txlog.TxState;
 import org.apache.ignite.internal.processors.cache.persistence.CacheDataRow;
+import org.apache.ignite.internal.processors.cache.persistence.DataRegion;
 import org.apache.ignite.internal.processors.cache.persistence.DatabaseLifecycleListener;
 import org.apache.ignite.internal.processors.cache.persistence.GridCacheDatabaseSharedManager;
 import org.apache.ignite.internal.processors.cache.persistence.IgniteCacheDatabaseSharedManager;
+import org.apache.ignite.internal.processors.cache.persistence.pagemem.PageMetrics;
 import org.apache.ignite.internal.processors.cache.tree.mvcc.data.MvccDataRow;
 import org.apache.ignite.internal.processors.cache.tree.mvcc.search.MvccLinkAwareSearchRow;
 import org.apache.ignite.internal.util.GridAtomicLong;
@@ -403,9 +405,11 @@ public class MvccProcessorImpl extends GridProcessorAdapter implements MvccProce
     private void txLogPageStoreInit(IgniteCacheDatabaseSharedManager mgr) throws IgniteCheckedException {
         assert CU.isPersistenceEnabled(ctx.config());
 
+        DataRegion dataRegion = mgr.dataRegion(TX_LOG_CACHE_NAME);
+        PageMetrics pageMetrics = dataRegion.metrics().cacheGrpPageMetrics(TX_LOG_CACHE_ID);
+
         //noinspection ConstantConditions
-        ctx.cache().context().pageStore().initialize(TX_LOG_CACHE_ID, 0,
-            TX_LOG_CACHE_NAME, mgr.dataRegion(TX_LOG_CACHE_NAME).memoryMetrics().totalAllocatedPages()::add);
+        ctx.cache().context().pageStore().initialize(TX_LOG_CACHE_ID, 0, TX_LOG_CACHE_NAME, pageMetrics);
     }
 
     /** {@inheritDoc} */
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/mvcc/txlog/TxLog.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/mvcc/txlog/TxLog.java
index 8cf61a8..8181266 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/mvcc/txlog/TxLog.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/mvcc/txlog/TxLog.java
@@ -38,6 +38,7 @@ import org.apache.ignite.internal.processors.cache.persistence.GridCacheDatabase
 import org.apache.ignite.internal.processors.cache.persistence.IgniteCacheDatabaseSharedManager;
 import org.apache.ignite.internal.processors.cache.persistence.checkpoint.CheckpointListener;
 import org.apache.ignite.internal.processors.cache.persistence.pagemem.PageMemoryEx;
+import org.apache.ignite.internal.processors.cache.persistence.pagemem.PageMetrics;
 import org.apache.ignite.internal.processors.cache.persistence.tree.BPlusTree;
 import org.apache.ignite.internal.processors.cache.persistence.tree.io.BPlusIO;
 import org.apache.ignite.internal.processors.cache.persistence.tree.io.PageIO;
@@ -130,13 +131,15 @@ public class TxLog implements CheckpointListener {
                             // Initialize new page.
                             PageMetaIO io = PageMetaIOV2.VERSIONS.latest();
 
-                            io.initNewPage(pageAddr, metaId, pageMemory.pageSize());
+                            PageMetrics metrics = txLogDataRegion.metrics().pageMetrics();
 
-                            treeRoot = pageMemory.allocatePage(TX_LOG_CACHE_ID, INDEX_PARTITION, PageMemory.FLAG_IDX);
-                            reuseListRoot = pageMemory.allocatePage(TX_LOG_CACHE_ID, INDEX_PARTITION, PageMemory.FLAG_IDX);
+                            io.initNewPage(pageAddr, metaId, pageMemory.pageSize(), metrics);
 
-                            assert PageIdUtils.flag(treeRoot) == PageMemory.FLAG_IDX;
-                            assert PageIdUtils.flag(reuseListRoot) == PageMemory.FLAG_IDX;
+                            treeRoot = pageMemory.allocatePage(TX_LOG_CACHE_ID, INDEX_PARTITION, FLAG_IDX);
+                            reuseListRoot = pageMemory.allocatePage(TX_LOG_CACHE_ID, INDEX_PARTITION, FLAG_IDX);
+
+                            assert PageIdUtils.flag(treeRoot) == FLAG_IDX;
+                            assert PageIdUtils.flag(reuseListRoot) == FLAG_IDX;
 
                             io.setTreeRoot(pageAddr, treeRoot);
                             io.setReuseListRoot(pageAddr, reuseListRoot);
@@ -161,9 +164,9 @@ public class TxLog implements CheckpointListener {
                             treeRoot = io.getTreeRoot(pageAddr);
                             reuseListRoot = io.getReuseListRoot(pageAddr);
 
-                            assert PageIdUtils.flag(treeRoot) == PageMemory.FLAG_IDX :
+                            assert PageIdUtils.flag(treeRoot) == FLAG_IDX :
                                 U.hexLong(treeRoot) + ", TX_LOG_CACHE_ID=" + TX_LOG_CACHE_ID;
-                            assert PageIdUtils.flag(reuseListRoot) == PageMemory.FLAG_IDX :
+                            assert PageIdUtils.flag(reuseListRoot) == FLAG_IDX :
                                 U.hexLong(reuseListRoot) + ", TX_LOG_CACHE_ID=" + TX_LOG_CACHE_ID;
                         }
                     }
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/DataRegion.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/DataRegion.java
index 0b0bf2b..5ff7826 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/DataRegion.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/DataRegion.java
@@ -28,7 +28,7 @@ public class DataRegion {
     private final PageMemory pageMem;
 
     /** */
-    private final DataRegionMetricsImpl memMetrics;
+    private final DataRegionMetricsImpl metrics;
 
     /** */
     private final DataRegionConfiguration cfg;
@@ -38,18 +38,18 @@ public class DataRegion {
 
     /**
      * @param pageMem PageMemory instance.
-     * @param memMetrics DataRegionMetrics instance.
+     * @param metrics DataRegionMetrics instance.
      * @param cfg Configuration of given DataRegion.
      * @param evictionTracker Eviction tracker.
      */
     public DataRegion(
         PageMemory pageMem,
         DataRegionConfiguration cfg,
-        DataRegionMetricsImpl memMetrics,
+        DataRegionMetricsImpl metrics,
         PageEvictionTracker evictionTracker
     ) {
         this.pageMem = pageMem;
-        this.memMetrics = memMetrics;
+        this.metrics = metrics;
         this.cfg = cfg;
         this.evictionTracker = evictionTracker;
     }
@@ -71,8 +71,8 @@ public class DataRegion {
     /**
      * @return Memory Metrics.
      */
-    public DataRegionMetricsImpl memoryMetrics() {
-        return memMetrics;
+    public DataRegionMetricsImpl metrics() {
+        return metrics;
     }
 
     /**
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/DataRegionMetricsImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/DataRegionMetricsImpl.java
index 7621ea0..1530d54 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/DataRegionMetricsImpl.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/DataRegionMetricsImpl.java
@@ -16,44 +16,89 @@
  */
 package org.apache.ignite.internal.processors.cache.persistence;
 
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
+import java.util.Optional;
 import org.apache.ignite.DataRegionMetrics;
 import org.apache.ignite.DataRegionMetricsProvider;
 import org.apache.ignite.configuration.DataRegionConfiguration;
+import org.apache.ignite.internal.GridKernalContext;
 import org.apache.ignite.internal.pagemem.PageMemory;
-import org.apache.ignite.internal.processors.metric.GridMetricManager;
+import org.apache.ignite.internal.processors.cache.persistence.pagemem.PageMetrics;
+import org.apache.ignite.internal.processors.cache.persistence.pagemem.PageMetricsImpl;
 import org.apache.ignite.internal.processors.metric.MetricRegistry;
 import org.apache.ignite.internal.processors.metric.impl.AtomicLongMetric;
 import org.apache.ignite.internal.processors.metric.impl.HitRateMetric;
 import org.apache.ignite.internal.processors.metric.impl.LongAdderMetric;
-import org.apache.ignite.internal.processors.performancestatistics.PerformanceStatisticsProcessor;
+import org.apache.ignite.internal.processors.metric.impl.LongAdderWithDelegateMetric;
+import org.apache.ignite.internal.processors.metric.impl.MetricUtils;
+import org.apache.ignite.internal.util.collection.IntHashMap;
+import org.apache.ignite.internal.util.collection.IntMap;
 import org.apache.ignite.internal.util.typedef.internal.U;
 import org.apache.ignite.mxbean.MetricsMxBean;
-import org.apache.ignite.spi.metric.Metric;
-
-import static org.apache.ignite.internal.processors.cache.CacheGroupMetricsImpl.CACHE_GROUP_METRICS_PREFIX;
-import static org.apache.ignite.internal.processors.metric.impl.MetricUtils.metricName;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.annotations.TestOnly;
 
 /**
  *
  */
 public class DataRegionMetricsImpl implements DataRegionMetrics {
     /**
+     * {@link LongAdderWithDelegateMetric.Delegate} that forwards all calls to another {@link LongAdderMetric}.
+     */
+    private static final class LongAdderMetricDelegate implements LongAdderWithDelegateMetric.Delegate {
+        /** */
+        private final LongAdderMetric delegate;
+
+        /** */
+        LongAdderMetricDelegate(LongAdderMetric delegate) {
+            this.delegate = delegate;
+        }
+
+        /** {@inheritDoc} */
+        @Override public void increment() {
+            delegate.increment();
+        }
+
+        /** {@inheritDoc} */
+        @Override public void decrement() {
+            delegate.decrement();
+        }
+
+        /** {@inheritDoc} */
+        @Override public void add(long x) {
+            delegate.add(x);
+        }
+    }
+
+    /**
+     * Factory method for {@link LongAdderMetricDelegate}.
+     */
+    private static LongAdderMetricDelegate delegate(LongAdderMetric delegate) {
+        return new LongAdderMetricDelegate(delegate);
+    }
+
+    /**
      * Data region metrics prefix.
      * Full name will contain {@link DataRegionConfiguration#getName()} also.
      * {@code "io.dataregion.default"}, for example.
      */
-    public static final String DATAREGION_METRICS_PREFIX = metricName("io", "dataregion");
+    public static final String DATAREGION_METRICS_PREFIX = MetricUtils.metricName("io", "dataregion");
 
     /** */
     private final DataRegionMetricsProvider dataRegionMetricsProvider;
 
-    /** */
-    private final LongAdderMetric totalAllocatedPages;
+    /**
+     * Cache group ID -> Cache group memory page metrics.
+     * <p>
+     * This is effectively a copy-on-write hash map, all write operations should create a new copy under the
+     * {@link #cacheGrpMetricsLock} lock.
+     */
+    private volatile IntMap<PageMetrics> cacheGrpMetrics = new IntHashMap<>();
 
-    /** */
-    private final ConcurrentMap<String, LongAdderMetric> grpAllocationTrackers = new ConcurrentHashMap<>();
+    /** Lock for the {@link #cacheGrpMetrics} field. */
+    private final Object cacheGrpMetricsLock = new Object();
+
+    /** Memory page metrics for the whole region. */
+    private final PageMetrics dataRegionPageMetrics;
 
     /**
      * Counter for number of pages occupied by large entries (one entry is larger than one page).
@@ -76,7 +121,7 @@ public class DataRegionMetricsImpl implements DataRegionMetrics {
     private final AtomicLongMetric offHeapSize;
 
     /** */
-    private final AtomicLongMetric checkpointBufferSize;
+    private final AtomicLongMetric checkpointBufSize;
 
     /** */
     private volatile boolean metricsEnabled;
@@ -85,7 +130,7 @@ public class DataRegionMetricsImpl implements DataRegionMetrics {
     private boolean persistenceEnabled;
 
     /** */
-    private volatile int subInts;
+    private final int subInts;
 
     /** Allocation rate calculator. */
     private final HitRateMetric allocRate;
@@ -103,54 +148,64 @@ public class DataRegionMetricsImpl implements DataRegionMetrics {
     private final LongAdderMetric totalThrottlingTime;
 
     /** */
-    private final DataRegionConfiguration memPlcCfg;
+    private final DataRegionConfiguration dataRegionCfg;
 
     /** */
+    @Nullable
     private PageMemory pageMem;
 
     /** */
-    private final GridMetricManager mmgr;
-
-    /** Performance statistics processor. */
-    private final PerformanceStatisticsProcessor psproc;
+    private final GridKernalContext kernalCtx;
 
     /** Time interval (in milliseconds) when allocations/evictions are counted to calculate rate. */
     private volatile long rateTimeInterval;
 
     /**
-     * @param memPlcCfg DataRegionConfiguration.
-     * @param mmgr Metrics manager.
-     * @param psproc Performance statistics processor.
+     * Same as {@link #DataRegionMetricsImpl(DataRegionConfiguration, GridKernalContext, DataRegionMetricsProvider)}
+     * but uses a no-op implementation for the {@link DataRegionMetricsProvider}.
+     */
+    @TestOnly
+    public DataRegionMetricsImpl(DataRegionConfiguration dataRegionCfg, GridKernalContext kernalCtx) {
+        this(dataRegionCfg, kernalCtx, new DataRegionMetricsProvider() {
+            @Override public long partiallyFilledPagesFreeSpace() {
+                return 0;
+            }
+
+            @Override public long emptyDataPages() {
+                return 0;
+            }
+        });
+    }
+
+    /**
+     * @param dataRegionCfg DataRegionConfiguration.
+     * @param kernalCtx Kernal context.
      * @param dataRegionMetricsProvider Data region metrics provider.
      */
-    public DataRegionMetricsImpl(DataRegionConfiguration memPlcCfg,
-        GridMetricManager mmgr,
-        PerformanceStatisticsProcessor psproc,
-        DataRegionMetricsProvider dataRegionMetricsProvider) {
-        this.memPlcCfg = memPlcCfg;
+    public DataRegionMetricsImpl(
+        DataRegionConfiguration dataRegionCfg,
+        GridKernalContext kernalCtx,
+        DataRegionMetricsProvider dataRegionMetricsProvider
+    ) {
+        this.dataRegionCfg = dataRegionCfg;
         this.dataRegionMetricsProvider = dataRegionMetricsProvider;
-        this.mmgr = mmgr;
-        this.psproc = psproc;
+        this.kernalCtx = kernalCtx;
 
-        metricsEnabled = memPlcCfg.isMetricsEnabled();
+        metricsEnabled = dataRegionCfg.isMetricsEnabled();
 
-        persistenceEnabled = memPlcCfg.isPersistenceEnabled();
+        persistenceEnabled = dataRegionCfg.isPersistenceEnabled();
 
-        rateTimeInterval = memPlcCfg.getMetricsRateTimeInterval();
+        rateTimeInterval = dataRegionCfg.getMetricsRateTimeInterval();
 
-        subInts = memPlcCfg.getMetricsSubIntervalCount();
+        subInts = dataRegionCfg.getMetricsSubIntervalCount();
 
-        MetricRegistry mreg = mmgr.registry(metricName(DATAREGION_METRICS_PREFIX, memPlcCfg.getName()));
+        MetricRegistry mreg = metricRegistry();
 
         allocRate = mreg.hitRateMetric("AllocationRate",
             "Allocation rate (pages per second) averaged across rateTimeInternal.",
             60_000,
             5);
 
-        totalAllocatedPages = mreg.longAdderMetric("TotalAllocatedPages",
-            this::updateAllocRate,
-            "Total number of allocated pages.");
-
         evictRate = mreg.hitRateMetric("EvictionRate",
             "Eviction rate (pages per second).",
             60_000,
@@ -184,7 +239,7 @@ public class DataRegionMetricsImpl implements DataRegionMetrics {
         offHeapSize = mreg.longMetric("OffHeapSize",
             "Offheap size in bytes.");
 
-        checkpointBufferSize = mreg.longMetric("CheckpointBufferSize",
+        checkpointBufSize = mreg.longMetric("CheckpointBufferSize",
             "Checkpoint buffer size in bytes.");
 
         mreg.register("EmptyDataPages",
@@ -197,20 +252,44 @@ public class DataRegionMetricsImpl implements DataRegionMetrics {
                 "dirty pages during the ongoing checkpoint.");
 
         mreg.longMetric("InitialSize", "Initial memory region size in bytes defined by its data region.")
-            .value(memPlcCfg.getInitialSize());
+            .value(dataRegionCfg.getInitialSize());
 
         mreg.longMetric("MaxSize", "Maximum memory region size in bytes defined by its data region.")
-            .value(memPlcCfg.getMaxSize());
+            .value(dataRegionCfg.getMaxSize());
+
+        dataRegionPageMetrics = PageMetricsImpl.builder(mreg)
+            .totalPagesCallback(new LongAdderWithDelegateMetric.Delegate() {
+                @Override public void increment() {
+                    add(1);
+                }
+
+                @Override public void add(long x) {
+                    if (metricsEnabled && x > 0)
+                        allocRate.add(x);
+                }
+
+                @Override public void decrement() {
+                }
+            })
+            .build();
+    }
+
+    /**
+     * Retrieves the {@link MetricRegistry} for this data region.
+     */
+    private MetricRegistry metricRegistry() {
+        String registryName = MetricUtils.metricName(DATAREGION_METRICS_PREFIX, dataRegionCfg.getName());
+        return kernalCtx.metric().registry(registryName);
     }
 
     /** {@inheritDoc} */
     @Override public String getName() {
-        return U.maskName(memPlcCfg.getName());
+        return U.maskName(dataRegionCfg.getName());
     }
 
     /** {@inheritDoc} */
     @Override public long getTotalAllocatedPages() {
-        return totalAllocatedPages.value();
+        return dataRegionPageMetrics.totalPages().value();
     }
 
     /** {@inheritDoc} */
@@ -244,7 +323,8 @@ public class DataRegionMetricsImpl implements DataRegionMetrics {
         if (!metricsEnabled)
             return 0;
 
-        return totalAllocatedPages.value() != 0 ? (float)largeEntriesPages.value() / totalAllocatedPages.value() : 0;
+        long totalAllocatedPages = getTotalAllocatedPages();
+        return totalAllocatedPages != 0 ? (float)largeEntriesPages.value() / totalAllocatedPages : 0;
     }
 
     /** {@inheritDoc} */
@@ -254,7 +334,7 @@ public class DataRegionMetricsImpl implements DataRegionMetrics {
 
         long freeSpace = dataRegionMetricsProvider.partiallyFilledPagesFreeSpace();
 
-        long totalAllocated = getPageSize() * totalAllocatedPages.value();
+        long totalAllocated = getPageSize() * getTotalAllocatedPages();
 
         return totalAllocated != 0 ? (float)(totalAllocated - freeSpace) / totalAllocated : 0f;
     }
@@ -319,7 +399,7 @@ public class DataRegionMetricsImpl implements DataRegionMetrics {
         if (!metricsEnabled || !persistenceEnabled)
             return 0;
 
-        return checkpointBufferSize.value();
+        return checkpointBufSize.value();
     }
 
     /** {@inheritDoc} */
@@ -378,7 +458,14 @@ public class DataRegionMetricsImpl implements DataRegionMetrics {
      * @param size Checkpoint buffer size.
      */
     public void updateCheckpointBufferSize(long size) {
-        this.checkpointBufferSize.add(size);
+        this.checkpointBufSize.add(size);
+    }
+
+    /**
+     * Memory page metrics associated with the data region.
+     */
+    public PageMetrics pageMetrics() {
+        return dataRegionPageMetrics;
     }
 
     /**
@@ -434,23 +521,73 @@ public class DataRegionMetricsImpl implements DataRegionMetrics {
             dirtyPages.reset();
     }
 
-    /** */
-    public LongAdderMetric totalAllocatedPages() {
-        return totalAllocatedPages;
+    /**
+     * Returns memory page metrics associated with the given cache group.
+     */
+    public PageMetrics cacheGrpPageMetrics(int cacheGrpId) {
+        PageMetrics pageMetrics = cacheGrpMetrics.get(cacheGrpId);
+
+        if (pageMetrics != null)
+            return pageMetrics;
+
+        synchronized (cacheGrpMetricsLock) {
+            IntMap<PageMetrics> localCacheGrpMetrics = cacheGrpMetrics;
+
+            // double check
+            PageMetrics doubleCheckPageMetrics = localCacheGrpMetrics.get(cacheGrpId);
+            if (doubleCheckPageMetrics != null)
+                return doubleCheckPageMetrics;
+
+            IntMap<PageMetrics> copy = new IntHashMap<>(localCacheGrpMetrics);
+
+            PageMetrics newMetrics = Optional.of(kernalCtx)
+                // both cache and group descriptor can be null
+                .map(GridKernalContext::cache)
+                .map(cache -> cache.cacheGroupDescriptors().get(cacheGrpId))
+                .map(decs -> createCacheGrpPageMetrics(decs.cacheOrGroupName()))
+                // return region-wide metrics for a non-existent cache group ID. This is needed in some scenarios
+                // when per-group data structures are used on a per-region basis and in tests.
+                .orElse(dataRegionPageMetrics);
+
+            copy.put(cacheGrpId, newMetrics);
+
+            cacheGrpMetrics = copy;
+
+            return newMetrics;
+        }
     }
 
     /**
-     * Get or allocate group allocation tracker.
-     *
-     * @param grpName Group name.
-     * @return Group allocation tracker.
+     * Creates memory page metrics container associated with the given cache group.
      */
-    public LongAdderMetric getOrAllocateGroupPageAllocationTracker(String grpName) {
-        return grpAllocationTrackers.computeIfAbsent(grpName,
-            id -> mmgr.registry(metricName(CACHE_GROUP_METRICS_PREFIX, grpName)).longAdderMetric(
-                "TotalAllocatedPages",
-                totalAllocatedPages::add,
-                "Cache group total allocated pages."));
+    private PageMetrics createCacheGrpPageMetrics(String cacheGrpName) {
+        String registryName = MetricUtils.cacheGroupMetricsRegistryName(cacheGrpName);
+        MetricRegistry registry = kernalCtx.metric().registry(registryName);
+
+        return PageMetricsImpl.builder(registry)
+            .totalPagesCallback(delegate(dataRegionPageMetrics.totalPages()))
+            .indexPagesCallback(delegate(dataRegionPageMetrics.indexPages()))
+            .build();
+    }
+
+    /**
+     * Removes all memory page metrics associated with the given cache group.
+     */
+    public void removeCacheGrpPageMetrics(Integer grpId) {
+        PageMetrics rmvMetrics;
+
+        synchronized (cacheGrpMetricsLock) {
+            IntMap<PageMetrics> copy = new IntHashMap<>(cacheGrpMetrics);
+
+            rmvMetrics = copy.remove(grpId);
+
+            cacheGrpMetrics = copy;
+        }
+
+        // we don't decrease the total pages counter for historical reasons
+        // (it hasn't been done in previous implementations)
+        if (rmvMetrics != null)
+            dataRegionPageMetrics.indexPages().add(-rmvMetrics.indexPages().value());
     }
 
     /**
@@ -504,7 +641,7 @@ public class DataRegionMetricsImpl implements DataRegionMetrics {
     public void pageMemory(PageMemory pageMem) {
         this.pageMem = pageMem;
 
-        MetricRegistry mreg = mmgr.registry(metricName(DATAREGION_METRICS_PREFIX, memPlcCfg.getName()));
+        MetricRegistry mreg = metricRegistry();
 
         mreg.register("PagesFillFactor",
             this::getPagesFillFactor,
@@ -571,37 +708,30 @@ public class DataRegionMetricsImpl implements DataRegionMetrics {
      * Clear metrics.
      */
     public void clear() {
-        totalAllocatedPages.reset();
-        grpAllocationTrackers.values().forEach(Metric::reset);
         largeEntriesPages.reset();
         dirtyPages.reset();
         readPages.reset();
         writtenPages.reset();
         replacedPages.reset();
         offHeapSize.reset();
-        checkpointBufferSize.reset();
+        checkpointBufSize.reset();
         allocRate.reset();
         evictRate.reset();
         pageReplaceRate.reset();
         pageReplaceAge.reset();
+
+        dataRegionPageMetrics.reset();
+
+        for (PageMetrics metrics : cacheGrpMetrics.values())
+            metrics.reset();
     }
 
     /** @param time Time to add to {@code totalThrottlingTime} metric in milliseconds. */
     public void addThrottlingTime(long time) {
-        if (psproc.enabled())
-            psproc.pagesWriteThrottle(U.currentTimeMillis(), time);
+        if (kernalCtx.performanceStatistics().enabled())
+            kernalCtx.performanceStatistics().pagesWriteThrottle(U.currentTimeMillis(), time);
 
         if (metricsEnabled)
             totalThrottlingTime.add(time);
     }
-
-    /**
-     * Updates allocation rate metric.
-     *
-     * @param delta Delta.
-     */
-    private void updateAllocRate(long delta) {
-        if (metricsEnabled && delta > 0)
-            allocRate.add(delta);
-    }
 }
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/DataRegionMetricsMXBeanImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/DataRegionMetricsMXBeanImpl.java
index e470a88..cd5d000 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/DataRegionMetricsMXBeanImpl.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/DataRegionMetricsMXBeanImpl.java
@@ -43,14 +43,11 @@ class DataRegionMetricsMXBeanImpl implements DataRegionMetricsMXBean {
     private final DataRegionConfiguration dataRegCfg;
 
     /**
-     * @param memMetrics DataRegionMetrics instance to expose through JMX interface.
-     * @param dataRegCfg Configuration of data region this MX Bean is created for.
+     * @param dataRegion Data region which metrics will be exposed through this JMX bean.
      */
-    DataRegionMetricsMXBeanImpl(DataRegionMetricsImpl memMetrics,
-        DataRegionConfiguration dataRegCfg
-    ) {
-        this.memMetrics = memMetrics;
-        this.dataRegCfg = dataRegCfg;
+    DataRegionMetricsMXBeanImpl(DataRegion dataRegion) {
+        this.memMetrics = dataRegion.metrics();
+        this.dataRegCfg = dataRegion.config();
     }
 
     /** {@inheritDoc} */
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/DataStructure.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/DataStructure.java
index 8814d18..7d0ea75 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/DataStructure.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/DataStructure.java
@@ -28,6 +28,7 @@ import org.apache.ignite.internal.pagemem.PageMemory;
 import org.apache.ignite.internal.pagemem.wal.IgniteWriteAheadLogManager;
 import org.apache.ignite.internal.pagemem.wal.record.delta.RecycleRecord;
 import org.apache.ignite.internal.pagemem.wal.record.delta.RotatedIdPartRecord;
+import org.apache.ignite.internal.processors.cache.persistence.pagemem.PageMetrics;
 import org.apache.ignite.internal.processors.cache.persistence.tree.io.PageIO;
 import org.apache.ignite.internal.processors.cache.persistence.tree.io.PageIoResolver;
 import org.apache.ignite.internal.processors.cache.persistence.tree.reuse.ReuseBag;
@@ -73,6 +74,9 @@ public abstract class DataStructure {
     /** */
     protected final byte pageFlag;
 
+    /** */
+    protected final PageMetrics metrics;
+
     /**
      * @param cacheGrpId Cache group ID.
      * @param grpName Cache group name.
@@ -100,6 +104,7 @@ public abstract class DataStructure {
         this.lockLsnr = lockLsnr == null ? NOOP_LSNR : lockLsnr;
         this.pageIoRslvr = pageIoRslvr;
         this.pageFlag = pageFlag;
+        this.metrics = pageMem.metrics().cacheGrpPageMetrics(cacheGrpId);
     }
 
     /**
@@ -442,6 +447,9 @@ public abstract class DataStructure {
         if (needWalDeltaRecord)
             wal.log(new RecycleRecord(grpId, pageId, recycled));
 
+        if (PageIO.isIndexPage(PageIO.getType(pageAddr)))
+            metrics.indexPages().decrement();
+
         return recycled;
     }
 
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/GridCacheDatabaseSharedManager.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/GridCacheDatabaseSharedManager.java
index 33b2467..67f21e6 100755
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/GridCacheDatabaseSharedManager.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/GridCacheDatabaseSharedManager.java
@@ -52,6 +52,7 @@ import java.util.function.Predicate;
 import java.util.function.ToLongFunction;
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
+import org.apache.ignite.DataRegionMetrics;
 import org.apache.ignite.DataRegionMetricsProvider;
 import org.apache.ignite.DataStorageMetrics;
 import org.apache.ignite.IgniteCheckedException;
@@ -320,7 +321,7 @@ public class GridCacheDatabaseSharedManager extends IgniteCacheDatabaseSharedMan
     private IgniteCacheSnapshotManager snapshotMgr;
 
     /** */
-    private DataStorageMetricsImpl persStoreMetrics;
+    private final DataStorageMetricsImpl persStoreMetrics;
 
     /**
      * MetaStorage instance. Value {@code null} means storage not initialized yet.
@@ -335,10 +336,10 @@ public class GridCacheDatabaseSharedManager extends IgniteCacheDatabaseSharedMan
     private List<MetastorageLifecycleListener> metastorageLifecycleLsnrs;
 
     /** Initially disabled cache groups. */
-    private Collection<Integer> initiallyGlobalWalDisabledGrps = new HashSet<>();
+    private final Collection<Integer> initiallyGlobalWalDisabledGrps = new HashSet<>();
 
     /** Initially local wal disabled groups. */
-    private Collection<Integer> initiallyLocalWalDisabledGrps = new HashSet<>();
+    private final Collection<Integer> initiallyLocWalDisabledGrps = new HashSet<>();
 
     /** Flag allows to log additional information about partitions during recovery phases. */
     private final boolean recoveryVerboseLogging =
@@ -348,7 +349,7 @@ public class GridCacheDatabaseSharedManager extends IgniteCacheDatabaseSharedMan
     private final Map<String, AtomicLong> pageListCacheLimits = new ConcurrentHashMap<>();
 
     /** Lock for releasing history for preloading. */
-    private ReentrantLock releaseHistForPreloadingLock = new ReentrantLock();
+    private final ReentrantLock releaseHistForPreloadingLock = new ReentrantLock();
 
     /** */
     private CachePartitionDefragmentationManager defrgMgr;
@@ -458,13 +459,12 @@ public class GridCacheDatabaseSharedManager extends IgniteCacheDatabaseSharedMan
     @Override protected void initDataRegions0(DataStorageConfiguration memCfg) throws IgniteCheckedException {
         super.initDataRegions0(memCfg);
 
-        addDataRegion(
-            memCfg,
-            createMetastoreDataRegionConfig(memCfg),
-            false
-        );
+        addDataRegion(memCfg, createMetastoreDataRegionConfig(memCfg), false);
 
-        persStoreMetrics.regionMetrics(memMetricsMap.values());
+        List<DataRegionMetrics> regionMetrics = dataRegionMap.values().stream()
+            .map(DataRegion::metrics)
+            .collect(Collectors.toList());
+        persStoreMetrics.regionMetrics(regionMetrics);
     }
 
     /**
@@ -1051,12 +1051,7 @@ public class GridCacheDatabaseSharedManager extends IgniteCacheDatabaseSharedMan
     private MetaStorage createMetastorage(boolean readOnly) throws IgniteCheckedException {
         cctx.pageStore().initializeForMetastorage();
 
-        MetaStorage storage = new MetaStorage(
-            cctx,
-            dataRegion(METASTORE_DATA_REGION_NAME),
-            (DataRegionMetricsImpl) memMetricsMap.get(METASTORE_DATA_REGION_NAME),
-            readOnly
-        );
+        MetaStorage storage = new MetaStorage(cctx, dataRegion(METASTORE_DATA_REGION_NAME), readOnly);
 
         storage.init(this);
 
@@ -1242,11 +1237,7 @@ public class GridCacheDatabaseSharedManager extends IgniteCacheDatabaseSharedMan
             this,
             memMetrics,
             resolveThrottlingPolicy(),
-            new IgniteOutClosure<CheckpointProgress>() {
-                @Override public CheckpointProgress apply() {
-                    return getCheckpointer().currentProgress();
-                }
-            }
+            () -> getCheckpointer().currentProgress()
         );
 
         memMetrics.pageMemory(pageMem);
@@ -1264,7 +1255,7 @@ public class GridCacheDatabaseSharedManager extends IgniteCacheDatabaseSharedMan
         final DataRegionMetricsImpl memMetrics
     ) {
         return new DirectMemoryProvider() {
-            private AtomicInteger checkPointBufferIdxCnt = new AtomicInteger();
+            private final AtomicInteger checkPointBufferIdxCnt = new AtomicInteger();
 
             private final DirectMemoryProvider memProvider = memoryProvider0;
 
@@ -1529,9 +1520,7 @@ public class GridCacheDatabaseSharedManager extends IgniteCacheDatabaseSharedMan
     }
 
     /** {@inheritDoc} */
-    @Override public void onCacheGroupsStopped(
-        Collection<IgniteBiTuple<CacheGroupContext, Boolean>> stoppedGrps
-    ) {
+    @Override public void onCacheGroupsStopped(Collection<IgniteBiTuple<CacheGroupContext, Boolean>> stoppedGrps) {
         Map<PageMemoryEx, Collection<Integer>> destroyed = new HashMap<>();
 
         List<Integer> stoppedGrpIds = stoppedGrps.stream()
@@ -1541,30 +1530,38 @@ public class GridCacheDatabaseSharedManager extends IgniteCacheDatabaseSharedMan
 
         cctx.snapshotMgr().onCacheGroupsStopped(stoppedGrpIds);
 
-        initiallyLocalWalDisabledGrps.removeAll(stoppedGrpIds);
+        initiallyLocWalDisabledGrps.removeAll(stoppedGrpIds);
         initiallyGlobalWalDisabledGrps.removeAll(stoppedGrpIds);
 
         for (IgniteBiTuple<CacheGroupContext, Boolean> tup : stoppedGrps) {
             CacheGroupContext gctx = tup.get1();
+            boolean destroy = tup.get2();
+
+            int grpId = gctx.groupId();
+
+            DataRegion dataRegion = gctx.dataRegion();
+
+            if (dataRegion != null)
+                dataRegion.metrics().removeCacheGrpPageMetrics(grpId);
 
             if (!gctx.persistenceEnabled())
                 continue;
 
-            snapshotMgr.onCacheGroupStop(gctx, tup.get2());
+            snapshotMgr.onCacheGroupStop(gctx, destroy);
 
-            PageMemoryEx pageMem = (PageMemoryEx)gctx.dataRegion().pageMemory();
+            PageMemoryEx pageMem = (PageMemoryEx) dataRegion.pageMemory();
 
             Collection<Integer> grpIds = destroyed.computeIfAbsent(pageMem, k -> new HashSet<>());
 
-            grpIds.add(tup.get1().groupId());
+            grpIds.add(grpId);
 
             if (gctx.config().isEncryptionEnabled())
-                cctx.kernalContext().encryption().onCacheGroupStop(gctx.groupId());
+                cctx.kernalContext().encryption().onCacheGroupStop(grpId);
 
-            pageMem.onCacheGroupDestroyed(tup.get1().groupId());
+            pageMem.onCacheGroupDestroyed(grpId);
 
-            if (tup.get2())
-                cctx.kernalContext().encryption().onCacheGroupDestroyed(gctx.groupId());
+            if (destroy)
+                cctx.kernalContext().encryption().onCacheGroupDestroyed(grpId);
         }
 
         Collection<IgniteInternalFuture<Void>> clearFuts = new ArrayList<>(destroyed.size());
@@ -3013,19 +3010,19 @@ public class GridCacheDatabaseSharedManager extends IgniteCacheDatabaseSharedMan
         private static final String lockFileName = "lock";
 
         /** File. */
-        private File file;
+        private final File file;
 
         /** Channel. */
-        private RandomAccessFile lockFile;
+        private final RandomAccessFile lockFile;
 
         /** Lock. */
         private volatile FileLock lock;
 
         /** Kernal context to generate Id of locked node in file. */
-        @NotNull private GridKernalContext ctx;
+        @NotNull private final GridKernalContext ctx;
 
         /** Logger. */
-        private IgniteLogger log;
+        private final IgniteLogger log;
 
         /**
          * @param path Path.
@@ -3229,7 +3226,7 @@ public class GridCacheDatabaseSharedManager extends IgniteCacheDatabaseSharedMan
     /** {@inheritDoc} */
     @Override public boolean walEnabled(int grpId, boolean local) {
         if (local)
-            return !initiallyLocalWalDisabledGrps.contains(grpId);
+            return !initiallyLocWalDisabledGrps.contains(grpId);
         else
             return !initiallyGlobalWalDisabledGrps.contains(grpId);
     }
@@ -3305,7 +3302,7 @@ public class GridCacheDatabaseSharedManager extends IgniteCacheDatabaseSharedMan
 
                 if (t2 != null) {
                     if (t2.get2())
-                        initiallyLocalWalDisabledGrps.add(t2.get1());
+                        initiallyLocWalDisabledGrps.add(t2.get1());
                     else
                         initiallyGlobalWalDisabledGrps.add(t2.get1());
                 }
@@ -3479,7 +3476,7 @@ public class GridCacheDatabaseSharedManager extends IgniteCacheDatabaseSharedMan
      */
     private IgnitePredicate<Integer> groupsWithEnabledWal() {
         return groupId -> !initiallyGlobalWalDisabledGrps.contains(groupId)
-            && !initiallyLocalWalDisabledGrps.contains(groupId);
+            && !initiallyLocWalDisabledGrps.contains(groupId);
     }
 
     /**
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/GridCacheOffheapManager.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/GridCacheOffheapManager.java
index 66f2629..8f2b12a 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/GridCacheOffheapManager.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/GridCacheOffheapManager.java
@@ -87,6 +87,7 @@ import org.apache.ignite.internal.processors.cache.persistence.freelist.CacheFre
 import org.apache.ignite.internal.processors.cache.persistence.freelist.SimpleDataRow;
 import org.apache.ignite.internal.processors.cache.persistence.migration.UpgradePendingTreeToPerPartitionTask;
 import org.apache.ignite.internal.processors.cache.persistence.pagemem.PageMemoryEx;
+import org.apache.ignite.internal.processors.cache.persistence.pagemem.PageMetrics;
 import org.apache.ignite.internal.processors.cache.persistence.partstate.GroupPartitionId;
 import org.apache.ignite.internal.processors.cache.persistence.partstate.PagesAllocationRange;
 import org.apache.ignite.internal.processors.cache.persistence.partstate.PartitionAllocationMap;
@@ -855,6 +856,8 @@ public class GridCacheOffheapManager extends IgniteCacheOffheapManagerImpl imple
         long nextId = cntrsPageId;
         int written = 0;
 
+        PageMetrics metrics = pageMem.metrics().cacheGrpPageMetrics(grpId);
+
         while (written != items) {
             final long curId = nextId;
             final long curPage = pageMem.acquirePage(grpId, curId);
@@ -870,7 +873,7 @@ public class GridCacheOffheapManager extends IgniteCacheOffheapManagerImpl imple
                     if (init) {
                         partCntrIo = PagePartitionCountersIO.VERSIONS.latest();
 
-                        partCntrIo.initNewPage(curAddr, curId, pageMem.realPageSize(grpId));
+                        partCntrIo.initNewPage(curAddr, curId, pageMem.realPageSize(grpId), metrics);
                     }
                     else
                         partCntrIo = PageIO.getPageIO(curAddr);
@@ -1107,7 +1110,9 @@ public class GridCacheOffheapManager extends IgniteCacheOffheapManagerImpl imple
                 PageMetaIOV2 io = (PageMetaIOV2)PageMetaIO.VERSIONS.latest();
 
                 if (PageIO.getType(pageAddr) != PageIO.T_META) {
-                    io.initNewPage(pageAddr, metaId, pageMem.realPageSize(grpId));
+                    PageMetrics metrics = pageMem.metrics().cacheGrpPageMetrics(grpId);
+
+                    io.initNewPage(pageAddr, metaId, pageMem.realPageSize(grpId), metrics);
 
                     metastoreRoot = pageMem.allocatePage(grpId, PageIdAllocator.INDEX_PARTITION, PageMemory.FLAG_IDX);
                     reuseListRoot = pageMem.allocatePage(grpId, PageIdAllocator.INDEX_PARTITION, PageMemory.FLAG_IDX);
@@ -2027,7 +2032,6 @@ public class GridCacheOffheapManager extends IgniteCacheOffheapManagerImpl imple
                     freeList = new CacheFreeList(
                         grp.groupId(),
                         freeListName,
-                        grp.dataRegion().memoryMetrics(),
                         grp.dataRegion(),
                         ctx.wal(),
                         reuseRoot.pageId().pageId(),
@@ -2052,7 +2056,6 @@ public class GridCacheOffheapManager extends IgniteCacheOffheapManagerImpl imple
                     partStorage = new PartitionMetaStorageImpl<SimpleDataRow>(
                         grp.groupId(),
                         partMetastoreName,
-                        grp.dataRegion().memoryMetrics(),
                         grp.dataRegion(),
                         freeList,
                         ctx.wal(),
@@ -2270,7 +2273,9 @@ public class GridCacheOffheapManager extends IgniteCacheOffheapManagerImpl imple
 
                     // Initialize new page.
                     if (PageIO.getType(pageAddr) != PageIO.T_PART_META) {
-                        io.initNewPage(pageAddr, partMetaId, pageMem.realPageSize(grpId));
+                        PageMetrics metrics = pageMem.metrics().cacheGrpPageMetrics(grpId);
+
+                        io.initNewPage(pageAddr, partMetaId, pageMem.realPageSize(grpId), metrics);
 
                         treeRoot = pageMem.allocatePage(grpId, partId, PageMemory.FLAG_AUX);
                         reuseListRoot = pageMem.allocatePage(grpId, partId, PageMemory.FLAG_AUX);
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/IgniteCacheDatabaseSharedManager.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/IgniteCacheDatabaseSharedManager.java
index ceddf8a..b0ea184 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/IgniteCacheDatabaseSharedManager.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/IgniteCacheDatabaseSharedManager.java
@@ -18,7 +18,6 @@
 package org.apache.ignite.internal.processors.cache.persistence;
 
 import java.io.File;
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
@@ -83,7 +82,6 @@ import org.apache.ignite.internal.processors.cache.persistence.wal.WALPointer;
 import org.apache.ignite.internal.processors.cache.warmup.WarmUpStrategy;
 import org.apache.ignite.internal.processors.cluster.IgniteChangeGlobalStateSupport;
 import org.apache.ignite.internal.util.TimeBag;
-import org.apache.ignite.internal.util.typedef.F;
 import org.apache.ignite.internal.util.typedef.T2;
 import org.apache.ignite.internal.util.typedef.internal.CU;
 import org.apache.ignite.internal.util.typedef.internal.LT;
@@ -143,9 +141,6 @@ public class IgniteCacheDatabaseSharedManager extends GridCacheSharedManagerAdap
     private static final String MBEAN_GROUP_NAME = "DataRegionMetrics";
 
     /** */
-    protected final Map<String, DataRegionMetrics> memMetricsMap = new ConcurrentHashMap<>();
-
-    /** */
     protected volatile boolean dataRegionsInitialized;
 
     /** */
@@ -272,14 +267,12 @@ public class IgniteCacheDatabaseSharedManager extends GridCacheSharedManagerAdap
 
         assert cfg != null;
 
-        for (DataRegionMetrics memMetrics : memMetricsMap.values()) {
-            DataRegionConfiguration memPlcCfg = dataRegionMap.get(memMetrics.getName()).config();
-
+        for (DataRegion dataRegion : dataRegionMap.values()) {
             registerMetricsMBean(
                 cfg,
                 MBEAN_GROUP_NAME,
-                memPlcCfg.getName(),
-                new DataRegionMetricsMXBeanImpl((DataRegionMetricsImpl)memMetrics, memPlcCfg),
+                dataRegion.config().getName(),
+                new DataRegionMetricsMXBeanImpl(dataRegion),
                 DataRegionMetricsMXBean.class
             );
         }
@@ -297,8 +290,6 @@ public class IgniteCacheDatabaseSharedManager extends GridCacheSharedManagerAdap
         for (DataRegion memPlc : dataRegionMap.values()) {
             DataRegionConfiguration memPlcCfg = memPlc.config();
 
-            DataRegionMetricsImpl memMetrics = (DataRegionMetricsImpl)memMetricsMap.get(memPlcCfg.getName());
-
             boolean persistenceEnabled = memPlcCfg.isPersistenceEnabled();
 
             String freeListName = memPlcCfg.getName() + "##FreeList";
@@ -308,7 +299,6 @@ public class IgniteCacheDatabaseSharedManager extends GridCacheSharedManagerAdap
             CacheFreeList freeList = new CacheFreeList(
                 0,
                 freeListName,
-                memMetrics,
                 memPlc,
                 persistenceEnabled ? cctx.wal() : null,
                 0L,
@@ -444,16 +434,13 @@ public class IgniteCacheDatabaseSharedManager extends GridCacheSharedManagerAdap
 
         DataRegionMetricsImpl memMetrics = new DataRegionMetricsImpl(
             dataRegionCfg,
-            cctx.kernalContext().metric(),
-            cctx.kernalContext().performanceStatistics(),
+            cctx.kernalContext(),
             dataRegionMetricsProvider(dataRegionCfg));
 
         DataRegion region = initMemory(dataStorageCfg, dataRegionCfg, memMetrics, trackable, pmPageMgr);
 
         dataRegionMap.put(dataRegionName, region);
 
-        memMetricsMap.put(dataRegionName, memMetrics);
-
         if (dataRegionName.equals(dfltMemPlcName))
             dfltDataRegion = region;
         else if (dataRegionName.equals(DFLT_DATA_REG_DEFAULT_NAME))
@@ -864,17 +851,10 @@ public class IgniteCacheDatabaseSharedManager extends GridCacheSharedManagerAdap
      * @return DataRegionMetrics for all MemoryPolicies configured in Ignite instance.
      */
     public Collection<DataRegionMetrics> memoryMetrics() {
-        if (!F.isEmpty(memMetricsMap)) {
-            // Intentionally return a collection copy to make it explicitly serializable.
-            Collection<DataRegionMetrics> res = new ArrayList<>(memMetricsMap.size());
-
-            for (DataRegionMetrics metrics : memMetricsMap.values())
-                res.add(new DataRegionMetricsSnapshot(metrics));
-
-            return res;
-        }
-        else
-            return Collections.emptyList();
+        return dataRegionMap.values().stream()
+            .map(DataRegion::metrics)
+            .map(DataRegionMetricsSnapshot::new)
+            .collect(Collectors.toList());
     }
 
     /**
@@ -885,18 +865,13 @@ public class IgniteCacheDatabaseSharedManager extends GridCacheSharedManagerAdap
     }
 
     /**
-     * @param memPlcName Name of {@link DataRegion} to obtain {@link DataRegionMetrics} for.
+     * @param dataRegionName Name of {@link DataRegion} to obtain {@link DataRegionMetrics} for.
      * @return {@link DataRegionMetrics} snapshot for specified {@link DataRegion} or {@code null} if
      * no {@link DataRegion} is configured for specified name.
      */
-    public @Nullable DataRegionMetrics memoryMetrics(String memPlcName) {
-        if (!F.isEmpty(memMetricsMap)) {
-            DataRegionMetrics memMetrics = memMetricsMap.get(memPlcName);
-
-            return memMetrics == null ? null : new DataRegionMetricsSnapshot(memMetrics);
-        }
-        else
-            return null;
+    public @Nullable DataRegionMetrics memoryMetrics(String dataRegionName) {
+        DataRegion dataRegion = dataRegionMap.get(dataRegionName);
+        return dataRegion == null ? null : new DataRegionMetricsSnapshot(dataRegion.metrics());
     }
 
     /**
@@ -1229,7 +1204,7 @@ public class IgniteCacheDatabaseSharedManager extends GridCacheSharedManagerAdap
 
             memPlc.evictionTracker().evictDataPage();
 
-            memPlc.memoryMetrics().updateEvictionRate();
+            memPlc.metrics().updateEvictionRate();
         }
     }
 
@@ -1362,13 +1337,11 @@ public class IgniteCacheDatabaseSharedManager extends GridCacheSharedManagerAdap
         memMetrics.persistenceEnabled(false);
 
         PageMemory pageMem = new PageMemoryNoStoreImpl(
-            log,
-            wrapMetricsMemoryProvider(memProvider, memMetrics),
             cctx,
+            wrapMetricsMemoryProvider(memProvider, memMetrics),
             memCfg.getPageSize(),
             memPlcCfg,
-            memMetrics.totalAllocatedPages(),
-            false
+            memMetrics
         );
 
         memMetrics.pageMemory(pageMem);
@@ -1498,7 +1471,7 @@ public class IgniteCacheDatabaseSharedManager extends GridCacheSharedManagerAdap
             unregisterMetricsMBean(
                 cctx.gridConfig(),
                 MBEAN_GROUP_NAME,
-                region.memoryMetrics().getName()
+                region.metrics().getName()
             );
         }
 
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/defragmentation/PageStoreMap.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/defragmentation/PageStoreMap.java
index 3939c87..f0210bb 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/defragmentation/PageStoreMap.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/defragmentation/PageStoreMap.java
@@ -103,6 +103,6 @@ class PageStoreMap implements PageStoreCollection {
             ));
         }
 
-        return Arrays.asList(partPageStoresMap.values());
+        return partPageStoresMap.values();
     }
 }
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/file/FilePageStoreManager.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/file/FilePageStoreManager.java
index 5a92f1c6e..dd87d35 100755
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/file/FilePageStoreManager.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/file/FilePageStoreManager.java
@@ -49,7 +49,6 @@ import java.util.concurrent.locks.ReentrantReadWriteLock;
 import java.util.function.BiConsumer;
 import java.util.function.BiFunction;
 import java.util.function.Function;
-import java.util.function.LongConsumer;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
 import org.apache.ignite.IgniteCheckedException;
@@ -72,16 +71,15 @@ import org.apache.ignite.internal.processors.cache.CacheGroupDescriptor;
 import org.apache.ignite.internal.processors.cache.GridCacheSharedManagerAdapter;
 import org.apache.ignite.internal.processors.cache.StoredCacheData;
 import org.apache.ignite.internal.processors.cache.persistence.DataRegion;
-import org.apache.ignite.internal.processors.cache.persistence.DataRegionMetricsImpl;
 import org.apache.ignite.internal.processors.cache.persistence.GridCacheDatabaseSharedManager;
 import org.apache.ignite.internal.processors.cache.persistence.StorageException;
 import org.apache.ignite.internal.processors.cache.persistence.defragmentation.DefragmentationFileUtils;
 import org.apache.ignite.internal.processors.cache.persistence.filename.PdsFolderSettings;
 import org.apache.ignite.internal.processors.cache.persistence.metastorage.MetaStorage;
+import org.apache.ignite.internal.processors.cache.persistence.pagemem.PageMetrics;
 import org.apache.ignite.internal.processors.cache.persistence.pagemem.PageReadWriteManager;
 import org.apache.ignite.internal.processors.cache.persistence.pagemem.PageReadWriteManagerImpl;
 import org.apache.ignite.internal.processors.cache.persistence.snapshot.IgniteCacheSnapshotManager;
-import org.apache.ignite.internal.processors.metric.impl.LongAdderMetric;
 import org.apache.ignite.internal.util.GridStripedReadWriteLock;
 import org.apache.ignite.internal.util.typedef.X;
 import org.apache.ignite.internal.util.typedef.internal.CU;
@@ -451,7 +449,7 @@ public class FilePageStoreManager extends GridCacheSharedManagerAdapter implemen
     }
 
     /** {@inheritDoc} */
-    @Override public void initialize(int cacheId, int partitions, String workingDir, LongConsumer tracker)
+    @Override public void initialize(int cacheId, int partitions, String workingDir, PageMetrics pageMetrics)
         throws IgniteCheckedException {
         assert storeWorkDir != null;
 
@@ -460,7 +458,7 @@ public class FilePageStoreManager extends GridCacheSharedManagerAdapter implemen
                 new File(storeWorkDir, workingDir),
                 cacheId,
                 partitions,
-                tracker,
+                pageMetrics,
                 cctx.cacheContext(cacheId) != null && cctx.cacheContext(cacheId).config().isEncryptionEnabled()
             );
 
@@ -493,12 +491,13 @@ public class FilePageStoreManager extends GridCacheSharedManagerAdapter implemen
 
         if (!idxCacheStores.containsKey(grpId)) {
             DataRegion dataRegion = cctx.database().dataRegion(GridCacheDatabaseSharedManager.METASTORE_DATA_REGION_NAME);
+            PageMetrics pageMetrics = dataRegion.metrics().cacheGrpPageMetrics(grpId);
 
             CacheStoreHolder holder = initDir(
                 new File(storeWorkDir, MetaStorage.METASTORAGE_DIR_NAME),
                 grpId,
                 MetaStorage.METASTORAGE_PARTITIONS.size(),
-                dataRegion.memoryMetrics().totalAllocatedPages()::add,
+                pageMetrics,
                 false);
 
             CacheStoreHolder old = idxCacheStores.put(grpId, holder);
@@ -663,17 +662,14 @@ public class FilePageStoreManager extends GridCacheSharedManagerAdapter implemen
         File cacheWorkDir = cacheWorkDir(ccfg);
 
         String dataRegionName = grpDesc.config().getDataRegionName();
-
-        DataRegionMetricsImpl regionMetrics = cctx.database().dataRegion(dataRegionName).memoryMetrics();
-
-        LongAdderMetric allocatedTracker =
-            regionMetrics.getOrAllocateGroupPageAllocationTracker(grpDesc.cacheOrGroupName());
+        DataRegion dataRegion = cctx.database().dataRegion(dataRegionName);
+        PageMetrics pageMetrics = dataRegion.metrics().cacheGrpPageMetrics(grpDesc.groupId());
 
         return initDir(
             cacheWorkDir,
             grpDesc.groupId(),
             grpDesc.config().getAffinity().partitions(),
-            allocatedTracker::add,
+            pageMetrics,
             ccfg.isEncryptionEnabled()
         );
     }
@@ -723,7 +719,7 @@ public class FilePageStoreManager extends GridCacheSharedManagerAdapter implemen
      * @param cacheWorkDir Work directory.
      * @param grpId Group ID.
      * @param partitions Number of partitions.
-     * @param allocatedTracker Metrics updater.
+     * @param pageMetrics Page metrics.
      * @param encrypted {@code True} if this cache encrypted.
      * @return Cache store holder.
      * @throws IgniteCheckedException If failed.
@@ -731,7 +727,7 @@ public class FilePageStoreManager extends GridCacheSharedManagerAdapter implemen
     private CacheStoreHolder initDir(File cacheWorkDir,
         int grpId,
         int partitions,
-        LongConsumer allocatedTracker,
+        PageMetrics pageMetrics,
         boolean encrypted) throws IgniteCheckedException {
         try {
             boolean dirExisted = checkAndInitCacheWorkDir(cacheWorkDir);
@@ -754,7 +750,7 @@ public class FilePageStoreManager extends GridCacheSharedManagerAdapter implemen
                 pageStoreFactory.createPageStore(
                     PageStore.TYPE_IDX,
                     idxFile,
-                    allocatedTracker);
+                    pageMetrics.totalPages()::add);
 
             PageStore[] partStores = new PageStore[partitions];
 
@@ -765,7 +761,7 @@ public class FilePageStoreManager extends GridCacheSharedManagerAdapter implemen
                     pageStoreFactory.createPageStore(
                         PageStore.TYPE_DATA,
                         () -> getPartitionFilePath(cacheWorkDir, p),
-                        allocatedTracker);
+                        pageMetrics.totalPages()::add);
 
                     partStores[partId] = partStore;
             }
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/freelist/AbstractFreeList.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/freelist/AbstractFreeList.java
index 7ccaf37..3fb0d23 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/freelist/AbstractFreeList.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/freelist/AbstractFreeList.java
@@ -419,10 +419,9 @@ public abstract class AbstractFreeList<T extends Storable> extends PagesList imp
     }
 
     /**
-     * @param cacheId Cache ID.
+     * @param cacheGrpId Cache group ID.
      * @param name Name (for debug purpose).
-     * @param memMetrics Memory metrics.
-     * @param memPlc Data region.
+     * @param dataRegion Data region.
      * @param reuseList Reuse list or {@code null} if this free list will be a reuse list for itself.
      * @param wal Write ahead log manager.
      * @param metaPageId Metadata page ID.
@@ -431,10 +430,9 @@ public abstract class AbstractFreeList<T extends Storable> extends PagesList imp
      * @throws IgniteCheckedException If failed.
      */
     public AbstractFreeList(
-        int cacheId,
+        int cacheGrpId,
         String name,
-        DataRegionMetricsImpl memMetrics,
-        DataRegion memPlc,
+        DataRegion dataRegion,
         ReuseList reuseList,
         IgniteWriteAheadLogManager wal,
         long metaPageId,
@@ -444,11 +442,11 @@ public abstract class AbstractFreeList<T extends Storable> extends PagesList imp
         AtomicLong pageListCacheLimit,
         byte pageFlag
     ) throws IgniteCheckedException {
-        super(cacheId, name, memPlc.pageMemory(), BUCKETS, wal, metaPageId, lockLsnr, ctx, pageFlag);
+        super(cacheGrpId, name, dataRegion.pageMemory(), BUCKETS, wal, metaPageId, lockLsnr, ctx, pageFlag);
 
-        rmvRow = new RemoveRowHandler(cacheId == 0);
+        rmvRow = new RemoveRowHandler(cacheGrpId == 0);
 
-        this.evictionTracker = memPlc.evictionTracker();
+        this.evictionTracker = dataRegion.evictionTracker();
         this.reuseList = reuseList == null ? this : reuseList;
         int pageSize = pageMem.pageSize();
 
@@ -469,7 +467,7 @@ public abstract class AbstractFreeList<T extends Storable> extends PagesList imp
 
         this.shift = shift;
 
-        this.memMetrics = memMetrics;
+        this.memMetrics = dataRegion.metrics();
 
         this.pageListCacheLimit = pageListCacheLimit;
 
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/freelist/CacheFreeList.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/freelist/CacheFreeList.java
index fdf50c9..020b379 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/freelist/CacheFreeList.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/freelist/CacheFreeList.java
@@ -25,7 +25,6 @@ import org.apache.ignite.internal.pagemem.PageIdUtils;
 import org.apache.ignite.internal.pagemem.wal.IgniteWriteAheadLogManager;
 import org.apache.ignite.internal.processors.cache.persistence.CacheDataRow;
 import org.apache.ignite.internal.processors.cache.persistence.DataRegion;
-import org.apache.ignite.internal.processors.cache.persistence.DataRegionMetricsImpl;
 import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageLockListener;
 import org.apache.ignite.internal.util.typedef.internal.U;
 
@@ -34,9 +33,8 @@ import org.apache.ignite.internal.util.typedef.internal.U;
  */
 public class CacheFreeList extends AbstractFreeList<CacheDataRow> {
     /**
-     * @param cacheId Cache id.
+     * @param cacheGrpId Cache group id.
      * @param name Name.
-     * @param regionMetrics Region metrics.
      * @param dataRegion Data region.
      * @param wal Wal.
      * @param metaPageId Meta page id.
@@ -44,9 +42,8 @@ public class CacheFreeList extends AbstractFreeList<CacheDataRow> {
      * @param pageFlag Default flag value for allocated pages.
      */
     public CacheFreeList(
-        int cacheId,
+        int cacheGrpId,
         String name,
-        DataRegionMetricsImpl regionMetrics,
         DataRegion dataRegion,
         IgniteWriteAheadLogManager wal,
         long metaPageId,
@@ -57,9 +54,8 @@ public class CacheFreeList extends AbstractFreeList<CacheDataRow> {
         byte pageFlag
     ) throws IgniteCheckedException {
         super(
-            cacheId,
+            cacheGrpId,
             name,
-            regionMetrics,
             dataRegion,
             null,
             wal,
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/freelist/PagesList.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/freelist/PagesList.java
index 06ccdda..a706d8d 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/freelist/PagesList.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/freelist/PagesList.java
@@ -205,7 +205,7 @@ public abstract class PagesList extends DataStructure {
     }
 
     /**
-     * @param cacheId Cache ID.
+     * @param cacheGrpId Cache group ID.
      * @param name Name (for debug purpose).
      * @param pageMem Page memory.
      * @param buckets Number of buckets.
@@ -214,7 +214,7 @@ public abstract class PagesList extends DataStructure {
      * @param pageFlag Default flag value for allocated pages.
      */
     protected PagesList(
-        int cacheId,
+        int cacheGrpId,
         String name,
         PageMemory pageMem,
         int buckets,
@@ -224,7 +224,7 @@ public abstract class PagesList extends DataStructure {
         GridKernalContext ctx,
         byte pageFlag
     ) {
-        super(cacheId, null, pageMem, wal, lockLsnr, DEFAULT_PAGE_IO_RESOLVER, pageFlag);
+        super(cacheGrpId, null, pageMem, wal, lockLsnr, DEFAULT_PAGE_IO_RESOLVER, pageFlag);
 
         this.name = name;
         this.buckets = buckets;
@@ -478,7 +478,7 @@ public abstract class PagesList extends DataStructure {
 
                                 curIo = PagesListMetaIO.VERSIONS.latest();
 
-                                curIo.initNewPage(curAddr, curId, pageSize());
+                                curIo.initNewPage(curAddr, curId, pageSize(), metrics);
                             }
                             else {
                                 releaseAndClose(curId, curPage, curAddr);
@@ -601,7 +601,7 @@ public abstract class PagesList extends DataStructure {
     private void setupNextPage(PagesListNodeIO io, long prevId, long prev, long nextId, long next) {
         assert io.getNextId(prev) == 0L;
 
-        io.initNewPage(next, nextId, pageSize());
+        io.initNewPage(next, nextId, pageSize(), metrics);
         io.setPreviousId(next, prevId);
 
         io.setNextId(prev, nextId);
@@ -1561,7 +1561,7 @@ public abstract class PagesList extends DataStructure {
         boolean needWalDeltaRecord = needWalDeltaRecord(reusedPageId, reusedPage, null);
 
         if (initIo != null) {
-            initIo.initNewPage(reusedPageAddr, newPageId, pageSize());
+            initIo.initNewPage(reusedPageAddr, newPageId, pageSize(), metrics);
 
             if (needWalDeltaRecord) {
                 assert PageIdUtils.partId(reusedPageId) == PageIdUtils.partId(newPageId) :
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/freelist/io/PagesListMetaIO.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/freelist/io/PagesListMetaIO.java
index 3c33241..c9c0e56 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/freelist/io/PagesListMetaIO.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/freelist/io/PagesListMetaIO.java
@@ -22,6 +22,7 @@ import java.util.Map;
 import org.apache.ignite.IgniteCheckedException;
 import org.apache.ignite.internal.pagemem.PageUtils;
 import org.apache.ignite.internal.processors.cache.persistence.freelist.PagesList;
+import org.apache.ignite.internal.processors.cache.persistence.pagemem.PageMetrics;
 import org.apache.ignite.internal.processors.cache.persistence.tree.io.IOVersions;
 import org.apache.ignite.internal.processors.cache.persistence.tree.io.PageIO;
 import org.apache.ignite.internal.util.GridLongList;
@@ -56,8 +57,8 @@ public class PagesListMetaIO extends PageIO {
     }
 
     /** {@inheritDoc} */
-    @Override public void initNewPage(long pageAddr, long pageId, int pageSize) {
-        super.initNewPage(pageAddr, pageId, pageSize);
+    @Override public void initNewPage(long pageAddr, long pageId, int pageSize, PageMetrics metrics) {
+        super.initNewPage(pageAddr, pageId, pageSize, metrics);
 
         setCount(pageAddr, 0);
         setNextMetaPageId(pageAddr, 0L);
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/freelist/io/PagesListNodeIO.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/freelist/io/PagesListNodeIO.java
index 2186d29..202961e 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/freelist/io/PagesListNodeIO.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/freelist/io/PagesListNodeIO.java
@@ -21,6 +21,7 @@ import java.nio.ByteBuffer;
 import org.apache.ignite.IgniteCheckedException;
 import org.apache.ignite.internal.pagemem.PageIdUtils;
 import org.apache.ignite.internal.pagemem.PageUtils;
+import org.apache.ignite.internal.processors.cache.persistence.pagemem.PageMetrics;
 import org.apache.ignite.internal.processors.cache.persistence.tree.io.CompactablePageIO;
 import org.apache.ignite.internal.processors.cache.persistence.tree.io.IOVersions;
 import org.apache.ignite.internal.processors.cache.persistence.tree.io.PageIO;
@@ -58,8 +59,8 @@ public class PagesListNodeIO extends PageIO implements CompactablePageIO {
     }
 
     /** {@inheritDoc} */
-    @Override public void initNewPage(long pageAddr, long pageId, int pageSize) {
-        super.initNewPage(pageAddr, pageId, pageSize);
+    @Override public void initNewPage(long pageAddr, long pageId, int pageSize, PageMetrics metrics) {
+        super.initNewPage(pageAddr, pageId, pageSize, metrics);
 
         setEmpty(pageAddr);
 
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/metastorage/MetaStorage.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/metastorage/MetaStorage.java
index 2e098bf..990a088 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/metastorage/MetaStorage.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/metastorage/MetaStorage.java
@@ -53,7 +53,6 @@ import org.apache.ignite.internal.pagemem.wal.record.delta.MetaPageInitRecord;
 import org.apache.ignite.internal.processors.cache.CacheDiagnosticManager;
 import org.apache.ignite.internal.processors.cache.GridCacheSharedContext;
 import org.apache.ignite.internal.processors.cache.persistence.DataRegion;
-import org.apache.ignite.internal.processors.cache.persistence.DataRegionMetricsImpl;
 import org.apache.ignite.internal.processors.cache.persistence.GridCacheDatabaseSharedManager;
 import org.apache.ignite.internal.processors.cache.persistence.IgniteCacheDatabaseSharedManager;
 import org.apache.ignite.internal.processors.cache.persistence.RootPage;
@@ -61,6 +60,7 @@ import org.apache.ignite.internal.processors.cache.persistence.StorageException;
 import org.apache.ignite.internal.processors.cache.persistence.checkpoint.CheckpointListener;
 import org.apache.ignite.internal.processors.cache.persistence.file.FilePageStoreManager;
 import org.apache.ignite.internal.processors.cache.persistence.pagemem.PageMemoryEx;
+import org.apache.ignite.internal.processors.cache.persistence.pagemem.PageMetrics;
 import org.apache.ignite.internal.processors.cache.persistence.partstorage.PartitionMetaStorageImpl;
 import org.apache.ignite.internal.processors.cache.persistence.tree.io.PageIO;
 import org.apache.ignite.internal.processors.cache.persistence.tree.io.PagePartitionMetaIO;
@@ -76,6 +76,7 @@ import org.apache.ignite.marshaller.jdk.JdkMarshaller;
 import org.jetbrains.annotations.NotNull;
 
 import static org.apache.ignite.internal.pagemem.PageIdAllocator.FLAG_AUX;
+import static org.apache.ignite.internal.pagemem.PageIdAllocator.FLAG_DATA;
 
 /**
  * General purpose key-value local-only storage.
@@ -128,9 +129,6 @@ public class MetaStorage implements CheckpointListener, ReadWriteMetastorage {
     private AtomicLong rmvId = new AtomicLong();
 
     /** */
-    private DataRegionMetricsImpl regionMetrics;
-
-    /** */
     private final boolean readOnly;
 
     /** */
@@ -164,13 +162,11 @@ public class MetaStorage implements CheckpointListener, ReadWriteMetastorage {
     public MetaStorage(
         GridCacheSharedContext<?, ?> cctx,
         DataRegion dataRegion,
-        DataRegionMetricsImpl regionMetrics,
         boolean readOnly
     ) {
         this.cctx = cctx;
         wal = cctx.wal();
         this.dataRegion = dataRegion;
-        this.regionMetrics = regionMetrics;
         this.readOnly = readOnly;
         log = cctx.logger(getClass());
         this.failureProcessor = cctx.kernalContext().failure();
@@ -178,7 +174,7 @@ public class MetaStorage implements CheckpointListener, ReadWriteMetastorage {
 
     /** */
     public void init(GridCacheDatabaseSharedManager db) throws IgniteCheckedException {
-        regionMetrics.clear();
+        dataRegion.metrics().clear();
         initInternal(db);
 
         if (!PRESERVE_LEGACY_METASTORAGE_PARTITION_ID) {
@@ -275,7 +271,6 @@ public class MetaStorage implements CheckpointListener, ReadWriteMetastorage {
             partStorage = new PartitionMetaStorageImpl<MetastorageRowStoreEntry>(
                 METASTORAGE_CACHE_ID,
                 freeListName,
-                regionMetrics,
                 dataRegion,
                 null,
                 wal,
@@ -505,13 +500,11 @@ public class MetaStorage implements CheckpointListener, ReadWriteMetastorage {
 
     /** */
     private void checkRootsPageIdFlag(long treeRoot, long reuseListRoot) throws StorageException {
-        if (PageIdUtils.flag(treeRoot) != PageMemory.FLAG_AUX &&
-            PageIdUtils.flag(treeRoot) != PageMemory.FLAG_DATA)
+        if (PageIdUtils.flag(treeRoot) != FLAG_AUX && PageIdUtils.flag(treeRoot) != FLAG_DATA)
             throw new StorageException("Wrong tree root page id flag: treeRoot="
                 + U.hexLong(treeRoot) + ", METASTORAGE_CACHE_ID=" + METASTORAGE_CACHE_ID);
 
-        if (PageIdUtils.flag(treeRoot) != PageMemory.FLAG_AUX &&
-            PageIdUtils.flag(treeRoot) != PageMemory.FLAG_DATA)
+        if (PageIdUtils.flag(reuseListRoot) != FLAG_AUX && PageIdUtils.flag(reuseListRoot) != FLAG_DATA)
             throw new StorageException("Wrong reuse list root page id flag: reuseListRoot="
                 + U.hexLong(reuseListRoot) + ", METASTORAGE_CACHE_ID=" + METASTORAGE_CACHE_ID);
     }
@@ -567,14 +560,16 @@ public class MetaStorage implements CheckpointListener, ReadWriteMetastorage {
                         // Initialize new page.
                         PagePartitionMetaIO io = PagePartitionMetaIO.VERSIONS.latest();
 
+                        PageMetrics metrics = pageMem.metrics().cacheGrpPageMetrics(METASTORAGE_CACHE_ID);
+
                         //MetaStorage never encrypted so realPageSize == pageSize.
-                        io.initNewPage(pageAddr, partMetaId, pageMem.pageSize());
+                        io.initNewPage(pageAddr, partMetaId, pageMem.pageSize(), metrics);
 
-                        treeRoot = pageMem.allocatePage(METASTORAGE_CACHE_ID, partId, PageMemory.FLAG_AUX);
-                        reuseListRoot = pageMem.allocatePage(METASTORAGE_CACHE_ID, partId, PageMemory.FLAG_AUX);
+                        treeRoot = pageMem.allocatePage(METASTORAGE_CACHE_ID, partId, FLAG_AUX);
+                        reuseListRoot = pageMem.allocatePage(METASTORAGE_CACHE_ID, partId, FLAG_AUX);
 
-                        assert PageIdUtils.flag(treeRoot) == PageMemory.FLAG_AUX;
-                        assert PageIdUtils.flag(reuseListRoot) == PageMemory.FLAG_AUX;
+                        assert PageIdUtils.flag(treeRoot) == FLAG_AUX;
+                        assert PageIdUtils.flag(reuseListRoot) == FLAG_AUX;
 
                         io.setTreeRoot(pageAddr, treeRoot);
                         io.setReuseListRoot(pageAddr, reuseListRoot);
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/PageMemoryImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/PageMemoryImpl.java
index f9c84c0..ba26086 100755
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/PageMemoryImpl.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/PageMemoryImpl.java
@@ -196,7 +196,7 @@ public class PageMemoryImpl implements PageMemoryEx {
     private final PageReadWriteManager pmPageMgr;
 
     /** */
-    private IgniteWriteAheadLogManager walMgr;
+    private final IgniteWriteAheadLogManager walMgr;
 
     /** */
     private final GridEncryptionManager encMgr;
@@ -223,7 +223,7 @@ public class PageMemoryImpl implements PageMemoryEx {
     private PagePool checkpointPool;
 
     /** */
-    private OffheapReadWriteLock rwLock;
+    private final OffheapReadWriteLock rwLock;
 
     /** Flush dirty page closure. When possible, will be called by evictPage(). */
     private final PageStoreWriter flushDirtyPage;
@@ -245,7 +245,7 @@ public class PageMemoryImpl implements PageMemoryEx {
     private PagesWriteThrottlePolicy writeThrottle;
 
     /** Write throttle type. */
-    private ThrottlingPolicy throttlingPlc;
+    private final ThrottlingPolicy throttlingPlc;
 
     /** Checkpoint progress provider. Null disables throttling. */
     @Nullable private final IgniteOutClosure<CheckpointProgress> cpProgressProvider;
@@ -258,10 +258,10 @@ public class PageMemoryImpl implements PageMemoryEx {
     private volatile int pageReplacementWarned;
 
     /** */
-    private long[] sizes;
+    private final long[] sizes;
 
     /** Memory metrics to track dirty pages count and page replace rate. */
-    private final DataRegionMetricsImpl memMetrics;
+    private final DataRegionMetricsImpl dataRegionMetrics;
 
     /**
      * {@code False} if memory was not started or already stopped and is not supposed for any usage.
@@ -277,7 +277,7 @@ public class PageMemoryImpl implements PageMemoryEx {
      * @param flushDirtyPage write callback invoked when a dirty page is removed for replacement.
      * @param changeTracker Callback invoked to track changes in pages.
      * @param stateChecker Checkpoint lock state provider. Used to ensure lock is held by thread, which modify pages.
-     * @param memMetrics Memory metrics to track dirty pages count and page replace rate.
+     * @param dataRegionMetrics Memory metrics to track dirty pages count and page replace rate.
      * @param throttlingPlc Write throttle enabled and its type. Null equal to none.
      * @param cpProgressProvider checkpoint progress, base for throttling. Null disables throttling.
      */
@@ -290,13 +290,13 @@ public class PageMemoryImpl implements PageMemoryEx {
         PageStoreWriter flushDirtyPage,
         @Nullable GridInClosure3X<Long, FullPageId, PageMemoryEx> changeTracker,
         CheckpointLockStateChecker stateChecker,
-        DataRegionMetricsImpl memMetrics,
+        DataRegionMetricsImpl dataRegionMetrics,
         @Nullable ThrottlingPolicy throttlingPlc,
         IgniteOutClosure<CheckpointProgress> cpProgressProvider
     ) {
         assert ctx != null;
         assert pageSize > 0;
-        assert memMetrics != null;
+        assert dataRegionMetrics != null;
 
         log = ctx.logger(PageMemoryImpl.class);
 
@@ -328,7 +328,7 @@ public class PageMemoryImpl implements PageMemoryEx {
 
         rwLock = new OffheapReadWriteLock(128);
 
-        this.memMetrics = memMetrics;
+        this.dataRegionMetrics = dataRegionMetrics;
 
         asyncRunner = new ThreadPoolExecutor(
             0,
@@ -608,7 +608,9 @@ public class PageMemoryImpl implements PageMemoryEx {
                 // We are inside segment write lock, so no other thread can pin this tracking page yet.
                 // We can modify page buffer directly.
                 if (PageIO.getType(pageAddr) == 0) {
-                    trackingIO.initNewPage(pageAddr, pageId, realPageSize(grpId));
+                    PageMetrics metrics = dataRegionMetrics.cacheGrpPageMetrics(grpId);
+
+                    trackingIO.initNewPage(pageAddr, pageId, realPageSize(grpId), metrics);
 
                     if (!ctx.wal().disabled(fullId.groupId())) {
                         if (!ctx.wal().isAlwaysWriteFullPages())
@@ -670,7 +672,7 @@ public class PageMemoryImpl implements PageMemoryEx {
 
         assert memCfg != null;
 
-        String dataRegionName = memMetrics.getName();
+        String dataRegionName = dataRegionMetrics.getName();
 
         if (memCfg.getDefaultDataRegionConfiguration().getName().equals(dataRegionName))
             return memCfg.getDefaultDataRegionConfiguration();
@@ -914,7 +916,10 @@ public class PageMemoryImpl implements PageMemoryEx {
 
                     actualPageId = PageIO.getPageId(buf);
 
-                    memMetrics.onPageRead();
+                    dataRegionMetrics.onPageRead();
+
+                    if (PageIO.isIndexPage(PageIO.getType(buf)))
+                        dataRegionMetrics.cacheGrpPageMetrics(grpId).indexPages().increment();
                 }
                 catch (IgniteDataIntegrityViolationException e) {
                     U.warn(log, "Failed to read page (data integrity violation encountered, will try to " +
@@ -926,7 +931,7 @@ public class PageMemoryImpl implements PageMemoryEx {
 
                     statHolder.trackPhysicalAndLogicalRead(pageAddr);
 
-                    memMetrics.onPageRead();
+                    dataRegionMetrics.onPageRead();
                 }
                 finally {
                     rwLock.writeUnlock(lockedPageAbsPtr + PAGE_LOCK_OFFSET,
@@ -1134,7 +1139,7 @@ public class PageMemoryImpl implements PageMemoryEx {
 
         safeToUpdate.set(true);
 
-        memMetrics.resetDirtyPages();
+        dataRegionMetrics.resetDirtyPages();
 
         if (throttlingPlc != ThrottlingPolicy.DISABLED)
             writeThrottle.onBeginCheckpoint();
@@ -1337,7 +1342,7 @@ public class PageMemoryImpl implements PageMemoryEx {
 
                 pageStoreWriter.writePage(fullId, buf, tag);
 
-                memMetrics.onPageWritten();
+                dataRegionMetrics.onPageWritten();
 
                 buf.rewind();
             }
@@ -1636,7 +1641,7 @@ public class PageMemoryImpl implements PageMemoryEx {
             if (tmpRelPtr == INVALID_REL_PTR) {
                 rwLock.writeUnlock(absPtr + PAGE_LOCK_OFFSET, OffheapReadWriteLock.TAG_LOCK_ALWAYS);
 
-                throw new IgniteException(CHECKPOINT_POOL_OVERFLOW_ERROR_MSG + ": " + memMetrics.getName());
+                throw new IgniteException(CHECKPOINT_POOL_OVERFLOW_ERROR_MSG + ": " + dataRegionMetrics.getName());
             }
 
             // Pin the page until checkpoint is not finished.
@@ -1844,7 +1849,7 @@ public class PageMemoryImpl implements PageMemoryEx {
                     if (dirtyPagesCnt >= seg.maxDirtyPages)
                         safeToUpdate.set(false);
 
-                    memMetrics.incrementDirtyPages();
+                    dataRegionMetrics.incrementDirtyPages();
                 }
             }
         }
@@ -1854,7 +1859,7 @@ public class PageMemoryImpl implements PageMemoryEx {
             if (seg.dirtyPages.remove(pageId)) {
                 seg.dirtyPagesCntr.decrementAndGet();
 
-                memMetrics.decrementDirtyPages();
+                dataRegionMetrics.decrementDirtyPages();
             }
         }
     }
@@ -1894,16 +1899,16 @@ public class PageMemoryImpl implements PageMemoryEx {
         return U.safeAbs(hash) % segments;
     }
 
-    /** @return Data region metrics. */
-    public DataRegionMetricsImpl metrics() {
-        return memMetrics;
-    }
-
     /** {@inheritDoc} */
     @Override public boolean shouldThrottle() {
         return writeThrottle.shouldThrottle();
     }
 
+    /** {@inheritDoc} */
+    @Override public DataRegionMetricsImpl metrics() {
+        return dataRegionMetrics;
+    }
+
     /**
      * Get arbitrary page from cp buffer.
      */
@@ -1968,16 +1973,16 @@ public class PageMemoryImpl implements PageMemoryEx {
         private final LoadedPagesMap loadedPages;
 
         /** Pointer to acquired pages integer counter. */
-        private long acquiredPagesPtr;
+        private final long acquiredPagesPtr;
 
         /** */
-        private PagePool pool;
+        private final PagePool pool;
 
         /** */
         private final PageReplacementPolicy pageReplacementPolicy;
 
         /** Bytes required to store {@link #loadedPages}. */
-        private long memPerTbl;
+        private final long memPerTbl;
 
         /** Bytes required to store {@link #pageReplacementPolicy} service data. */
         private long memPerRepl;
@@ -2157,44 +2162,40 @@ public class PageMemoryImpl implements PageMemoryEx {
 
             if (isDirty(absPtr)) {
                 CheckpointPages checkpointPages = this.checkpointPages;
-                // Can evict a dirty page only if should be written by a checkpoint.
-                // These pages does not have tmp buffer.
-                if (checkpointPages != null && checkpointPages.allowToSave(fullPageId)) {
-                    assert pmPageMgr != null;
-
-                    memMetrics.updatePageReplaceRate(U.currentTimeMillis() - PageHeader.readTimestamp(absPtr));
-
-                    PageStoreWriter saveDirtyPage = delayedPageReplacementTracker != null
-                        ? delayedPageReplacementTracker.delayedPageWrite() : flushDirtyPage;
-
-                    saveDirtyPage.writePage(
-                        fullPageId,
-                        wrapPointer(absPtr + PAGE_OVERHEAD, pageSize()),
-                        partGeneration(
-                            fullPageId.groupId(),
-                            PageIdUtils.partId(fullPageId.pageId())
-                        )
-                    );
+                // Can evict a dirty page only if it has been written by a checkpoint.
+                // These pages do not have a tmp buffer.
+                if (checkpointPages == null || !checkpointPages.allowToSave(fullPageId))
+                    return false;
+
+                assert pmPageMgr != null;
+
+                PageStoreWriter saveDirtyPage = delayedPageReplacementTracker != null
+                    ? delayedPageReplacementTracker.delayedPageWrite() : flushDirtyPage;
+
+                saveDirtyPage.writePage(
+                    fullPageId,
+                    wrapPointer(absPtr + PAGE_OVERHEAD, pageSize()),
+                    partGeneration(
+                        fullPageId.groupId(),
+                        PageIdUtils.partId(fullPageId.pageId())
+                    )
+                );
 
-                    setDirty(fullPageId, absPtr, false, true);
+                setDirty(fullPageId, absPtr, false, true);
 
-                    checkpointPages.markAsSaved(fullPageId);
+                checkpointPages.markAsSaved(fullPageId);
+            }
 
-                    loadedPages.remove(fullPageId.groupId(), fullPageId.effectivePageId());
+            dataRegionMetrics.updatePageReplaceRate(U.currentTimeMillis() - PageHeader.readTimestamp(absPtr));
 
-                    return true;
-                }
+            loadedPages.remove(fullPageId.groupId(), fullPageId.effectivePageId());
 
-                return false;
+            if (PageIO.isIndexPage(PageIO.getType(absPtr + PAGE_OVERHEAD))) {
+                int grpId = fullPageId.groupId();
+                dataRegionMetrics.cacheGrpPageMetrics(grpId).indexPages().decrement();
             }
-            else {
-                memMetrics.updatePageReplaceRate(U.currentTimeMillis() - PageHeader.readTimestamp(absPtr));
-
-                loadedPages.remove(fullPageId.groupId(), fullPageId.effectivePageId());
 
-                // Page was not modified, ok to evict.
-                return true;
-            }
+            return true;
         }
 
         /**
@@ -2214,7 +2215,7 @@ public class PageMemoryImpl implements PageMemoryEx {
                 if (PageIO.getType(pageAddr) != PageIO.T_DATA)
                     return;
 
-                final GridQueryRowCacheCleaner cleaner = ctx.kernalContext().indexProcessor()
+                GridQueryRowCacheCleaner cleaner = ctx.kernalContext().indexProcessor()
                     .rowCacheCleaner(fullPageId.groupId());
 
                 if (cleaner == null)
@@ -2298,7 +2299,7 @@ public class PageMemoryImpl implements PageMemoryEx {
                 if (pageReplacementWarnedFieldUpdater.compareAndSet(PageMemoryImpl.this, 0, 1)) {
                     String msg = "Page replacements started, pages will be rotated with disk, this will affect " +
                         "storage performance (consider increasing DataRegionConfiguration#setMaxSize for " +
-                        "data region): " + memMetrics.getName();
+                        "data region): " + dataRegionMetrics.getName();
 
                     U.warn(log, msg);
 
@@ -2308,7 +2309,7 @@ public class PageMemoryImpl implements PageMemoryEx {
                                 ctx.gridEvents().record(new PageReplacementStartedEvent(
                                     ctx.localNode(),
                                     msg,
-                                    memMetrics.getName()));
+                                    dataRegionMetrics.getName()));
                             }
                         });
                     }
@@ -2489,19 +2490,19 @@ public class PageMemoryImpl implements PageMemoryEx {
      */
     private static class ClearSegmentRunnable implements Runnable {
         /** */
-        private Segment seg;
+        private final Segment seg;
 
         /** Clear element filter for (cache group ID, page ID). */
         LoadedPagesMap.KeyPredicate clearPred;
 
         /** */
-        private CountDownFuture doneFut;
+        private final CountDownFuture doneFut;
 
         /** */
-        private int pageSize;
+        private final int pageSize;
 
         /** */
-        private boolean rmvDirty;
+        private final boolean rmvDirty;
 
         /**
          * @param seg Segment.
diff --git a/modules/indexing/src/test/java/org/apache/ignite/testsuites/IgniteCacheWithIndexingAndPersistenceTestSuite.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/PageMetrics.java
similarity index 51%
copy from modules/indexing/src/test/java/org/apache/ignite/testsuites/IgniteCacheWithIndexingAndPersistenceTestSuite.java
copy to modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/PageMetrics.java
index 060759d8..d5c6c1f 100644
--- a/modules/indexing/src/test/java/org/apache/ignite/testsuites/IgniteCacheWithIndexingAndPersistenceTestSuite.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/PageMetrics.java
@@ -1,6 +1,6 @@
 /*
  * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
+ * contributor license agreements. See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
  * The ASF licenses this file to You under the Apache License, Version 2.0
  * (the "License"); you may not use this file except in compliance with
@@ -15,22 +15,26 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.testsuites;
+package org.apache.ignite.internal.processors.cache.persistence.pagemem;
 
-import org.apache.ignite.internal.processors.cache.StartCachesInParallelTest;
-import org.apache.ignite.internal.processors.cache.index.IoStatisticsBasicIndexSelfTest;
-import org.apache.ignite.internal.processors.query.CleanupIndexTreeCheckpointFailoverTest;
-import org.junit.runner.RunWith;
-import org.junit.runners.Suite;
+import org.apache.ignite.internal.processors.metric.impl.LongAdderMetric;
 
 /**
- * Cache tests using indexing.
+ * Container for different memory page-related metrics.
  */
-@RunWith(Suite.class)
-@Suite.SuiteClasses({
-    StartCachesInParallelTest.class,
-    IoStatisticsBasicIndexSelfTest.class,
-    CleanupIndexTreeCheckpointFailoverTest.class
-})
-public class IgniteCacheWithIndexingAndPersistenceTestSuite {
+public interface PageMetrics {
+    /**
+     * Total number of allocated pages.
+     */
+    public LongAdderMetric totalPages();
+
+    /**
+     * Number of index pages loaded into memory.
+     */
+    public LongAdderMetric indexPages();
+
+    /**
+     * Resets all metric counters.
+     */
+    public void reset();
 }
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/PageMetricsImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/PageMetricsImpl.java
new file mode 100644
index 0000000..84661fb
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/PageMetricsImpl.java
@@ -0,0 +1,134 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.cache.persistence.pagemem;
+
+import org.apache.ignite.internal.processors.metric.MetricRegistry;
+import org.apache.ignite.internal.processors.metric.impl.LongAdderMetric;
+import org.apache.ignite.internal.processors.metric.impl.LongAdderWithDelegateMetric;
+import org.jetbrains.annotations.Nullable;
+
+/** */
+public class PageMetricsImpl implements PageMetrics {
+    /** Total pages. */
+    private final LongAdderMetric totalPages;
+
+    /** Index pages in memory. */
+    private final LongAdderMetric idxPages;
+
+    /** */
+    private PageMetricsImpl(
+        MetricRegistry metricRegistry,
+        @Nullable LongAdderWithDelegateMetric.Delegate totalPagesCb,
+        @Nullable LongAdderWithDelegateMetric.Delegate idxPagesCb
+    ) {
+        totalPages = createMetricWithOptionalDelegate(
+            metricRegistry, "TotalAllocatedPages", "Total allocated pages.", totalPagesCb
+        );
+
+        idxPages = createMetricWithOptionalDelegate(
+            metricRegistry, "InMemoryIndexPages", "Amount of index pages loaded into memory.", idxPagesCb
+        );
+    }
+
+    /**
+     * Builder for {@link PageMetricsImpl} instances.
+     */
+    public static final class Builder {
+        /** Metric registry. */
+        private final MetricRegistry metricRegistry;
+
+        /** Total pages callback. */
+        private LongAdderWithDelegateMetric.Delegate totalPagesCb;
+
+        /** Index pages callback. */
+        private LongAdderWithDelegateMetric.Delegate idxPagesCb;
+
+        /**
+         * @param metricRegistry Metric registry.
+         */
+        Builder(MetricRegistry metricRegistry) {
+            this.metricRegistry = metricRegistry;
+        }
+
+        /**
+         * @param cb Callback.
+         */
+        public Builder totalPagesCallback(LongAdderWithDelegateMetric.Delegate cb) {
+            totalPagesCb = cb;
+            return this;
+        }
+
+        /**
+         * @param cb Callback.
+         */
+        public Builder indexPagesCallback(LongAdderWithDelegateMetric.Delegate cb) {
+            idxPagesCb = cb;
+            return this;
+        }
+
+        /** */
+        public PageMetricsImpl build() {
+            return new PageMetricsImpl(
+                metricRegistry,
+                totalPagesCb,
+                idxPagesCb
+            );
+        }
+    }
+
+    /**
+     * @param metricRegistry Metric registry.
+     */
+    public static Builder builder(MetricRegistry metricRegistry) {
+        return new Builder(metricRegistry);
+    }
+
+    /**
+     * @param metricRegistry Metric registry.
+     * @param name Name.
+     * @param desc Description.
+     * @param delegate Delegate.
+     */
+    private static LongAdderMetric createMetricWithOptionalDelegate(
+        MetricRegistry metricRegistry,
+        String name,
+        String desc,
+        @Nullable LongAdderWithDelegateMetric.Delegate delegate
+    ) {
+        return delegate == null ?
+            metricRegistry.longAdderMetric(name, desc) :
+            metricRegistry.longAdderMetric(name, delegate, desc);
+    }
+
+    /** {@inheritDoc} */
+    @Override public LongAdderMetric totalPages() {
+        return totalPages;
+    }
+
+
+    /** {@inheritDoc} */
+    @Override public LongAdderMetric indexPages() {
+        return idxPages;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void reset() {
+        totalPages.reset();
+        idxPages.reset();
+    }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/partstorage/PartitionMetaStorageImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/partstorage/PartitionMetaStorageImpl.java
index acf8334..dd0228a 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/partstorage/PartitionMetaStorageImpl.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/partstorage/PartitionMetaStorageImpl.java
@@ -25,7 +25,6 @@ import org.apache.ignite.internal.pagemem.PageUtils;
 import org.apache.ignite.internal.pagemem.wal.IgniteWriteAheadLogManager;
 import org.apache.ignite.internal.processors.cache.IncompleteObject;
 import org.apache.ignite.internal.processors.cache.persistence.DataRegion;
-import org.apache.ignite.internal.processors.cache.persistence.DataRegionMetricsImpl;
 import org.apache.ignite.internal.processors.cache.persistence.Storable;
 import org.apache.ignite.internal.processors.cache.persistence.freelist.AbstractFreeList;
 import org.apache.ignite.internal.processors.cache.persistence.tree.io.AbstractDataPageIO;
@@ -41,9 +40,8 @@ import static org.apache.ignite.internal.pagemem.PageIdUtils.pageId;
  */
 public class PartitionMetaStorageImpl<T extends Storable> extends AbstractFreeList<T> implements PartitionMetaStorage<T> {
     /**
-     * @param cacheId Cache id.
+     * @param cacheGrpId Cache group id.
      * @param name Name.
-     * @param memMetrics Mem metrics.
      * @param memPlc Mem policy.
      * @param reuseList Reuse list.
      * @param wal Wal.
@@ -52,8 +50,8 @@ public class PartitionMetaStorageImpl<T extends Storable> extends AbstractFreeLi
      * @param pageFlag Default flag value for allocated pages.
      */
     public PartitionMetaStorageImpl(
-        int cacheId, String name,
-        DataRegionMetricsImpl memMetrics,
+        int cacheGrpId,
+        String name,
         DataRegion memPlc,
         ReuseList reuseList,
         IgniteWriteAheadLogManager wal,
@@ -64,7 +62,7 @@ public class PartitionMetaStorageImpl<T extends Storable> extends AbstractFreeLi
         AtomicLong pageListCacheLimit,
         byte pageFlag
     ) throws IgniteCheckedException {
-        super(cacheId, name, memMetrics, memPlc, reuseList, wal, metaPageId, initNew, lsnr, ctx, pageListCacheLimit, pageFlag);
+        super(cacheGrpId, name, memPlc, reuseList, wal, metaPageId, initNew, lsnr, ctx, pageListCacheLimit, pageFlag);
     }
 
     /**
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/BPlusTree.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/BPlusTree.java
index a44378d..8a6e377 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/BPlusTree.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/BPlusTree.java
@@ -2734,7 +2734,7 @@ public abstract class BPlusTree<L, T extends L> extends DataStructure implements
         }
 
         // Update forward page.
-        io.splitForwardPage(pageAddr, fwdId, fwdBuf, mid, cnt, pageSize());
+        io.splitForwardPage(pageAddr, fwdId, fwdBuf, mid, cnt, pageSize(), metrics);
 
         // Update existing page.
         io.splitExistingPage(pageAddr, mid, fwdId);
@@ -3797,7 +3797,8 @@ public abstract class BPlusTree<L, T extends L> extends DataStructure implements
                                     null,
                                     fwdId,
                                     pageSize(),
-                                    needWal);
+                                    needWal,
+                                    metrics);
 
                                 if (needWal)
                                     wal.log(new NewRootInitRecord<>(grpId, newRootId, newRootId,
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/io/AbstractDataPageIO.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/io/AbstractDataPageIO.java
index 0c976fd..82e565f 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/io/AbstractDataPageIO.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/io/AbstractDataPageIO.java
@@ -28,6 +28,7 @@ import org.apache.ignite.internal.pagemem.PageIdUtils;
 import org.apache.ignite.internal.pagemem.PageMemory;
 import org.apache.ignite.internal.pagemem.PageUtils;
 import org.apache.ignite.internal.processors.cache.persistence.Storable;
+import org.apache.ignite.internal.processors.cache.persistence.pagemem.PageMetrics;
 import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageHandler;
 import org.apache.ignite.internal.util.GridStringBuilder;
 import org.apache.ignite.internal.util.typedef.internal.SB;
@@ -214,8 +215,8 @@ public abstract class AbstractDataPageIO<T extends Storable> extends PageIO impl
     }
 
     /** {@inheritDoc} */
-    @Override public void initNewPage(long pageAddr, long pageId, int pageSize) {
-        super.initNewPage(pageAddr, pageId, pageSize);
+    @Override public void initNewPage(long pageAddr, long pageId, int pageSize, PageMetrics metrics) {
+        super.initNewPage(pageAddr, pageId, pageSize, metrics);
 
         setEmptyPage(pageAddr, pageSize);
         setFreeListPageId(pageAddr, 0L);
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/io/BPlusIO.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/io/BPlusIO.java
index 55a5d45..1f72342 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/io/BPlusIO.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/io/BPlusIO.java
@@ -20,6 +20,7 @@ package org.apache.ignite.internal.processors.cache.persistence.tree.io;
 import java.nio.ByteBuffer;
 import org.apache.ignite.IgniteCheckedException;
 import org.apache.ignite.internal.pagemem.PageUtils;
+import org.apache.ignite.internal.processors.cache.persistence.pagemem.PageMetrics;
 import org.apache.ignite.internal.processors.cache.persistence.tree.BPlusTree;
 import org.apache.ignite.internal.util.GridStringBuilder;
 import org.apache.ignite.internal.util.GridUnsafe;
@@ -75,8 +76,8 @@ public abstract class BPlusIO<L> extends PageIO implements CompactablePageIO {
     }
 
     /** {@inheritDoc} */
-    @Override public void initNewPage(long pageAddr, long pageId, int pageSize) {
-        super.initNewPage(pageAddr, pageId, pageSize);
+    @Override public void initNewPage(long pageAddr, long pageId, int pageSize, PageMetrics metrics) {
+        super.initNewPage(pageAddr, pageId, pageSize, metrics);
 
         setCount(pageAddr, 0);
         setForward(pageAddr, 0);
@@ -287,9 +288,10 @@ public abstract class BPlusIO<L> extends PageIO implements CompactablePageIO {
         long fwdPageAddr,
         int mid,
         int cnt,
-        int pageSize
+        int pageSize,
+        PageMetrics metrics
     ) throws IgniteCheckedException {
-        initNewPage(fwdPageAddr, fwdId, pageSize);
+        initNewPage(fwdPageAddr, fwdId, pageSize, metrics);
 
         cnt -= mid;
 
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/io/BPlusInnerIO.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/io/BPlusInnerIO.java
index dabee7c..5e5bc1c 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/io/BPlusInnerIO.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/io/BPlusInnerIO.java
@@ -19,6 +19,7 @@ package org.apache.ignite.internal.processors.cache.persistence.tree.io;
 
 import org.apache.ignite.IgniteCheckedException;
 import org.apache.ignite.internal.pagemem.PageUtils;
+import org.apache.ignite.internal.processors.cache.persistence.pagemem.PageMetrics;
 import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageHandler;
 
 /**
@@ -166,9 +167,10 @@ public abstract class BPlusInnerIO<L> extends BPlusIO<L> {
         byte[] rowBytes,
         long rightChildId,
         int pageSize,
-        boolean needRowBytes
+        boolean needRowBytes,
+        PageMetrics metrics
     ) throws IgniteCheckedException {
-        initNewPage(newRootPageAddr, newRootId, pageSize);
+        initNewPage(newRootPageAddr, newRootId, pageSize, metrics);
 
         setCount(newRootPageAddr, 1);
         setLeft(newRootPageAddr, 0, leftChildId);
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/io/PageIO.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/io/PageIO.java
index 6d88643..ac75fa0 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/io/PageIO.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/io/PageIO.java
@@ -35,6 +35,7 @@ import org.apache.ignite.internal.processors.cache.persistence.freelist.io.Pages
 import org.apache.ignite.internal.processors.cache.persistence.freelist.io.PagesListNodeIO;
 import org.apache.ignite.internal.processors.cache.persistence.metastorage.MetastorageBPlusIO;
 import org.apache.ignite.internal.processors.cache.persistence.metastorage.MetastoreDataPageIO;
+import org.apache.ignite.internal.processors.cache.persistence.pagemem.PageMetrics;
 import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageHandler;
 import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageLockListener;
 import org.apache.ignite.internal.processors.cache.tree.CacheIdAwareDataInnerIO;
@@ -51,6 +52,7 @@ import org.apache.ignite.internal.processors.cache.tree.mvcc.data.MvccDataInnerI
 import org.apache.ignite.internal.processors.cache.tree.mvcc.data.MvccDataLeafIO;
 import org.apache.ignite.internal.util.GridStringBuilder;
 import org.apache.ignite.spi.encryption.EncryptionSpi;
+import org.jetbrains.annotations.Nullable;
 
 /**
  * Base format for all the page types.
@@ -63,7 +65,7 @@ import org.apache.ignite.spi.encryption.EncryptionSpi;
  *    static methods (like {@code {@link #getPageId(long)}}) intentionally:
  *    this base format can not be changed between versions.
  *
- * 2. IO must correctly override {@link #initNewPage(long, long, int)} method and call super.
+ * 2. IO must correctly override {@link #initNewPage(long, long, int, PageMetrics)} method and call super.
  *    We have logic that relies on this behavior.
  *
  * 3. Page IO type ID constant must be declared in this class to have a list of all the
@@ -608,10 +610,11 @@ public abstract class PageIO {
      * @param pageAddr Page address.
      * @param pageId Page ID.
      * @param pageSize Page size.
+     * @param metrics Page metrics for tracking page allocation. Can be {@code null} if no tracking is required.
      *
      * @see EncryptionSpi#encryptedSize(int)
      */
-    public void initNewPage(long pageAddr, long pageId, int pageSize) {
+    public void initNewPage(long pageAddr, long pageId, int pageSize, @Nullable PageMetrics metrics) {
         setType(pageAddr, getType());
         setVersion(pageAddr, getVersion());
         setPageId(pageAddr, pageId);
@@ -621,6 +624,28 @@ public abstract class PageIO {
         PageUtils.putLong(pageAddr, ROTATED_ID_PART_OFF, 0L);
         PageUtils.putLong(pageAddr, RESERVED_2_OFF, 0L);
         PageUtils.putLong(pageAddr, RESERVED_3_OFF, 0L);
+
+        if (metrics != null && isIndexPage(getType()))
+            metrics.indexPages().increment();
+    }
+
+    /**
+     * Returns {@code true} if the given page type is related to SQL index data pages.
+     *
+     * @param pageType Page type (can be obtained by {@link #getType(long)} or {@link #getType(ByteBuffer)} methods).
+     */
+    public static boolean isIndexPage(int pageType) {
+        if ((T_H2_EX_REF_LEAF_START <= pageType && pageType <= T_H2_EX_REF_LEAF_END) ||
+            (T_H2_EX_REF_MVCC_LEAF_START <= pageType && pageType <= T_H2_EX_REF_MVCC_LEAF_END)
+        )
+            return true;
+
+        if ((T_H2_EX_REF_INNER_START <= pageType && pageType <= T_H2_EX_REF_INNER_END) ||
+            (T_H2_EX_REF_MVCC_INNER_START <= pageType && pageType <= T_H2_EX_REF_MVCC_INNER_END)
+        )
+            return true;
+
+        return false;
     }
 
     /** {@inheritDoc} */
@@ -832,30 +857,30 @@ public abstract class PageIO {
     public static IndexPageType deriveIndexPageType(long pageAddr) {
         int pageIoType = PageIO.getType(pageAddr);
         switch (pageIoType) {
-            case PageIO.T_DATA_REF_INNER:
-            case PageIO.T_DATA_REF_MVCC_INNER:
-            case PageIO.T_H2_REF_INNER:
-            case PageIO.T_H2_MVCC_REF_INNER:
-            case PageIO.T_CACHE_ID_AWARE_DATA_REF_INNER:
-            case PageIO.T_CACHE_ID_DATA_REF_MVCC_INNER:
+            case T_DATA_REF_INNER:
+            case T_DATA_REF_MVCC_INNER:
+            case T_H2_REF_INNER:
+            case T_H2_MVCC_REF_INNER:
+            case T_CACHE_ID_AWARE_DATA_REF_INNER:
+            case T_CACHE_ID_DATA_REF_MVCC_INNER:
                 return IndexPageType.INNER;
 
-            case PageIO.T_DATA_REF_LEAF:
-            case PageIO.T_DATA_REF_MVCC_LEAF:
-            case PageIO.T_H2_REF_LEAF:
-            case PageIO.T_H2_MVCC_REF_LEAF:
-            case PageIO.T_CACHE_ID_AWARE_DATA_REF_LEAF:
-            case PageIO.T_CACHE_ID_DATA_REF_MVCC_LEAF:
+            case T_DATA_REF_LEAF:
+            case T_DATA_REF_MVCC_LEAF:
+            case T_H2_REF_LEAF:
+            case T_H2_MVCC_REF_LEAF:
+            case T_CACHE_ID_AWARE_DATA_REF_LEAF:
+            case T_CACHE_ID_DATA_REF_MVCC_LEAF:
                 return IndexPageType.LEAF;
 
             default:
-                if ((PageIO.T_H2_EX_REF_LEAF_START <= pageIoType && pageIoType <= PageIO.T_H2_EX_REF_LEAF_END) ||
-                    (PageIO.T_H2_EX_REF_MVCC_LEAF_START <= pageIoType && pageIoType <= PageIO.T_H2_EX_REF_MVCC_LEAF_END)
+                if ((T_H2_EX_REF_LEAF_START <= pageIoType && pageIoType <= T_H2_EX_REF_LEAF_END) ||
+                    (T_H2_EX_REF_MVCC_LEAF_START <= pageIoType && pageIoType <= T_H2_EX_REF_MVCC_LEAF_END)
                 )
                     return IndexPageType.LEAF;
 
-                if ((PageIO.T_H2_EX_REF_INNER_START <= pageIoType && pageIoType <= PageIO.T_H2_EX_REF_INNER_END) ||
-                    (PageIO.T_H2_EX_REF_MVCC_INNER_START <= pageIoType && pageIoType <= PageIO.T_H2_EX_REF_MVCC_INNER_END)
+                if ((T_H2_EX_REF_INNER_START <= pageIoType && pageIoType <= T_H2_EX_REF_INNER_END) ||
+                    (T_H2_EX_REF_MVCC_INNER_START <= pageIoType && pageIoType <= T_H2_EX_REF_MVCC_INNER_END)
                 )
                     return IndexPageType.INNER;
         }
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/io/PageMetaIO.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/io/PageMetaIO.java
index 84735c2..d12dd42 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/io/PageMetaIO.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/io/PageMetaIO.java
@@ -20,6 +20,7 @@ package org.apache.ignite.internal.processors.cache.persistence.tree.io;
 import java.nio.ByteBuffer;
 import org.apache.ignite.IgniteCheckedException;
 import org.apache.ignite.internal.pagemem.PageUtils;
+import org.apache.ignite.internal.processors.cache.persistence.pagemem.PageMetrics;
 import org.apache.ignite.internal.util.GridStringBuilder;
 import org.jetbrains.annotations.NotNull;
 
@@ -76,8 +77,8 @@ public class PageMetaIO extends PageIO {
     }
 
     /** {@inheritDoc} */
-    @Override public void initNewPage(long pageAddr, long pageId, int pageSize) {
-        super.initNewPage(pageAddr, pageId, pageSize);
+    @Override public void initNewPage(long pageAddr, long pageId, int pageSize, PageMetrics metrics) {
+        super.initNewPage(pageAddr, pageId, pageSize, metrics);
 
         setTreeRoot(pageAddr, 0);
         setReuseListRoot(pageAddr, 0);
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/io/PageMetaIOV2.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/io/PageMetaIOV2.java
index f9f956d..6927c3e 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/io/PageMetaIOV2.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/io/PageMetaIOV2.java
@@ -18,6 +18,7 @@
 package org.apache.ignite.internal.processors.cache.persistence.tree.io;
 
 import org.apache.ignite.internal.pagemem.PageUtils;
+import org.apache.ignite.internal.processors.cache.persistence.pagemem.PageMetrics;
 
 /**
  * IO for index partition metadata page.
@@ -83,8 +84,8 @@ public class PageMetaIOV2 extends PageMetaIO {
     }
 
     /** {@inheritDoc} */
-    @Override public void initNewPage(long pageAddr, long pageId, int pageSize) {
-        super.initNewPage(pageAddr, pageId, pageSize);
+    @Override public void initNewPage(long pageAddr, long pageId, int pageSize, PageMetrics metrics) {
+        super.initNewPage(pageAddr, pageId, pageSize, metrics);
 
         setEncryptedPageCount(pageAddr, 0);
         setEncryptedPageIndex(pageAddr, 0);
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/io/PagePartitionCountersIO.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/io/PagePartitionCountersIO.java
index a3e92cf..d16c5f8 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/io/PagePartitionCountersIO.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/io/PagePartitionCountersIO.java
@@ -22,6 +22,7 @@ import java.util.HashMap;
 import java.util.Map;
 import org.apache.ignite.IgniteCheckedException;
 import org.apache.ignite.internal.pagemem.PageUtils;
+import org.apache.ignite.internal.processors.cache.persistence.pagemem.PageMetrics;
 import org.apache.ignite.internal.util.GridStringBuilder;
 import org.apache.ignite.internal.util.GridUnsafe;
 
@@ -83,8 +84,8 @@ public class PagePartitionCountersIO extends PageIO {
     }
 
     /** {@inheritDoc} */
-    @Override public void initNewPage(long pageAddr, long pageId, int pageSize) {
-        super.initNewPage(pageAddr, pageId, pageSize);
+    @Override public void initNewPage(long pageAddr, long pageId, int pageSize, PageMetrics metrics) {
+        super.initNewPage(pageAddr, pageId, pageSize, metrics);
 
         setCount(pageAddr, 0);
         setNextCountersPageId(pageAddr, 0);
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/io/PagePartitionMetaIO.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/io/PagePartitionMetaIO.java
index 16653f1..d2bb31c 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/io/PagePartitionMetaIO.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/io/PagePartitionMetaIO.java
@@ -21,6 +21,7 @@ package org.apache.ignite.internal.processors.cache.persistence.tree.io;
 import org.apache.ignite.IgniteCheckedException;
 import org.apache.ignite.internal.pagemem.PageUtils;
 import org.apache.ignite.internal.processors.cache.distributed.dht.topology.GridDhtPartitionState;
+import org.apache.ignite.internal.processors.cache.persistence.pagemem.PageMetrics;
 import org.apache.ignite.internal.util.GridStringBuilder;
 
 /**
@@ -53,8 +54,8 @@ public class PagePartitionMetaIO extends PageMetaIO {
     );
 
     /** {@inheritDoc} */
-    @Override public void initNewPage(long pageAddr, long pageId, int pageSize) {
-        super.initNewPage(pageAddr, pageId, pageSize);
+    @Override public void initNewPage(long pageAddr, long pageId, int pageSize, PageMetrics metrics) {
+        super.initNewPage(pageAddr, pageId, pageSize, metrics);
 
         setSize(pageAddr, 0);
         setUpdateCounter(pageAddr, 0);
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/io/PagePartitionMetaIOV2.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/io/PagePartitionMetaIOV2.java
index bfe5bf2..d4eeacb 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/io/PagePartitionMetaIOV2.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/io/PagePartitionMetaIOV2.java
@@ -19,6 +19,7 @@
 package org.apache.ignite.internal.processors.cache.persistence.tree.io;
 
 import org.apache.ignite.internal.pagemem.PageUtils;
+import org.apache.ignite.internal.processors.cache.persistence.pagemem.PageMetrics;
 import org.apache.ignite.internal.util.GridStringBuilder;
 
 /**
@@ -43,8 +44,8 @@ public class PagePartitionMetaIOV2 extends PagePartitionMetaIO {
     }
 
     /** {@inheritDoc} */
-    @Override public void initNewPage(long pageAddr, long pageId, int pageSize) {
-        super.initNewPage(pageAddr, pageId, pageSize);
+    @Override public void initNewPage(long pageAddr, long pageId, int pageSize, PageMetrics metrics) {
+        super.initNewPage(pageAddr, pageId, pageSize, metrics);
 
         setPendingTreeRoot(pageAddr, 0L);
         setPartitionMetaStoreReuseListRoot(pageAddr, 0L);
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/io/PagePartitionMetaIOV3.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/io/PagePartitionMetaIOV3.java
index 8543006..c0c009f 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/io/PagePartitionMetaIOV3.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/io/PagePartitionMetaIOV3.java
@@ -19,6 +19,7 @@
 package org.apache.ignite.internal.processors.cache.persistence.tree.io;
 
 import org.apache.ignite.internal.pagemem.PageUtils;
+import org.apache.ignite.internal.processors.cache.persistence.pagemem.PageMetrics;
 import org.apache.ignite.internal.util.GridStringBuilder;
 
 /**
@@ -39,8 +40,8 @@ public class PagePartitionMetaIOV3 extends PagePartitionMetaIOV2 {
     }
 
     /** {@inheritDoc} */
-    @Override public void initNewPage(long pageAddr, long pageId, int pageSize) {
-        super.initNewPage(pageAddr, pageId, pageSize);
+    @Override public void initNewPage(long pageAddr, long pageId, int pageSize, PageMetrics metrics) {
+        super.initNewPage(pageAddr, pageId, pageSize, metrics);
 
         setEncryptedPageIndex(pageAddr, 0);
         setEncryptedPageCount(pageAddr, 0);
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/util/PageHandler.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/util/PageHandler.java
index 02d8238..0899c63 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/util/PageHandler.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/util/PageHandler.java
@@ -24,6 +24,7 @@ import org.apache.ignite.internal.pagemem.PageMemory;
 import org.apache.ignite.internal.pagemem.PageSupport;
 import org.apache.ignite.internal.pagemem.wal.IgniteWriteAheadLogManager;
 import org.apache.ignite.internal.pagemem.wal.record.delta.InitNewPageRecord;
+import org.apache.ignite.internal.processors.cache.persistence.pagemem.PageMetrics;
 import org.apache.ignite.internal.processors.cache.persistence.tree.io.PageIO;
 import org.apache.ignite.internal.processors.cache.persistence.tree.io.PageIoResolver;
 import org.apache.ignite.internal.util.GridUnsafe;
@@ -461,7 +462,9 @@ public abstract class PageHandler<X, R> {
 
         assert PageIO.getCrc(pageAddr) == 0; //TODO GG-11480
 
-        init.initNewPage(pageAddr, pageId, pageMem.realPageSize(grpId));
+        PageMetrics metrics = pageMem.metrics().cacheGrpPageMetrics(grpId);
+
+        init.initNewPage(pageAddr, pageId, pageMem.realPageSize(grpId), metrics);
 
         // Here we should never write full page, because it is known to be new.
         if (isWalDeltaRecordNeeded(pageMem, grpId, pageId, page, wal, FALSE))
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/metric/MetricRegistry.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/metric/MetricRegistry.java
index 36b3a1d..0ace7e5 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/metric/MetricRegistry.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/metric/MetricRegistry.java
@@ -23,7 +23,6 @@ import java.util.function.BooleanSupplier;
 import java.util.function.DoubleSupplier;
 import java.util.function.Function;
 import java.util.function.IntSupplier;
-import java.util.function.LongConsumer;
 import java.util.function.LongSupplier;
 import java.util.function.Supplier;
 import org.apache.ignite.IgniteLogger;
@@ -251,7 +250,9 @@ public class MetricRegistry implements ReadOnlyMetricRegistry {
      * @param desc Description.
      * @return {@link LongAdderWithDelegateMetric}.
      */
-    public LongAdderMetric longAdderMetric(String name, LongConsumer delegate, @Nullable String desc) {
+    public LongAdderWithDelegateMetric longAdderMetric(
+        String name, LongAdderWithDelegateMetric.Delegate delegate, @Nullable String desc
+    ) {
         return addMetric(name, new LongAdderWithDelegateMetric(metricName(regName, name), delegate, desc));
     }
 
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/metric/impl/LongAdderMetric.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/metric/impl/LongAdderMetric.java
index d0497b9..9dd1362 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/metric/impl/LongAdderMetric.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/metric/impl/LongAdderMetric.java
@@ -43,17 +43,24 @@ public class LongAdderMetric extends AbstractMetric implements LongMetric {
      * @param x Value to be added.
      */
     public void add(long x) {
-        val.add(x);
+        add0(x);
     }
 
     /** Adds 1 to the metric. */
     public void increment() {
-        add(1);
+        add0(1);
     }
 
     /** Adds -1 to the metric. */
     public void decrement() {
-        add(-1);
+        add0(-1);
+    }
+
+    /**
+     * Implementation method needed to avoid being overriden.
+     */
+    private void add0(long x) {
+        val.add(x);
     }
 
     /** {@inheritDoc} */
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/metric/impl/LongAdderWithDelegateMetric.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/metric/impl/LongAdderWithDelegateMetric.java
index 25ee7da..628b735 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/metric/impl/LongAdderWithDelegateMetric.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/metric/impl/LongAdderWithDelegateMetric.java
@@ -18,31 +18,62 @@
 package org.apache.ignite.internal.processors.metric.impl;
 
 import java.util.concurrent.atomic.LongAdder;
-import java.util.function.LongConsumer;
 import org.jetbrains.annotations.Nullable;
 
 /**
  * Long metric implementation based on {@link LongAdder} with {@link #delegate}.
  */
 public class LongAdderWithDelegateMetric extends LongAdderMetric {
+    /** */
+    public interface Delegate {
+        /**
+         * Called when the parent metric's {@code increment} method is called.
+         */
+        public void increment();
+
+        /**
+         * Called when the parent metric's {@code decrement} method is called.
+         */
+        public void decrement();
+
+        /**
+         * Called when the parent metric's {@code add} method is called.
+         */
+        public void add(long x);
+    }
+
     /** Delegate. */
-    private LongConsumer delegate;
+    private final Delegate delegate;
 
     /**
      * @param name Name.
      * @param delegate Delegate to which all updates from new metric will be delegated to.
      * @param descr Description.
      */
-    public LongAdderWithDelegateMetric(String name, LongConsumer delegate, @Nullable String descr) {
+    public LongAdderWithDelegateMetric(String name, Delegate delegate, @Nullable String descr) {
         super(name, descr);
 
         this.delegate = delegate;
     }
 
     /** {@inheritDoc} */
+    @Override public void increment() {
+        super.increment();
+
+        delegate.increment();
+    }
+
+    /** {@inheritDoc} */
+    @Override public void decrement() {
+        super.decrement();
+
+        delegate.decrement();
+    }
+
+    /** {@inheritDoc} */
     @Override public void add(long x) {
         super.add(x);
 
-        delegate.accept(x);
+        delegate.add(x);
     }
 }
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/metric/impl/MetricUtils.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/metric/impl/MetricUtils.java
index 0ec8d6b..cf62dc5 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/metric/impl/MetricUtils.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/metric/impl/MetricUtils.java
@@ -22,6 +22,7 @@ import org.apache.ignite.internal.processors.metric.MetricRegistry;
 import org.apache.ignite.internal.util.typedef.T2;
 import org.apache.ignite.spi.metric.HistogramMetric;
 
+import static org.apache.ignite.internal.processors.cache.CacheGroupMetricsImpl.CACHE_GROUP_METRICS_PREFIX;
 import static org.apache.ignite.internal.processors.cache.CacheMetricsImpl.CACHE_METRICS;
 
 /**
@@ -82,6 +83,14 @@ public class MetricUtils {
     }
 
     /**
+     * @param cacheOrGroupName Cache or group name, depending whether group is implicit or not.
+     * @return Cache metrics registry name.
+     */
+    public static String cacheGroupMetricsRegistryName(String cacheOrGroupName) {
+        return metricName(CACHE_GROUP_METRICS_PREFIX, cacheOrGroupName);
+    }
+
+    /**
      * Atomically sets the value to the given updated value
      * if the current value {@code ==} the expected value.
      *
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/util/collection/IntHashMap.java b/modules/core/src/main/java/org/apache/ignite/internal/util/collection/IntHashMap.java
index 2160590..cf23153 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/util/collection/IntHashMap.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/util/collection/IntHashMap.java
@@ -17,7 +17,10 @@
 
 package org.apache.ignite.internal.util.collection;
 
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
 import java.util.Objects;
 import java.util.stream.Collectors;
 
@@ -104,6 +107,15 @@ public class IntHashMap<V> implements IntMap<V> {
         entries = (Entry<V>[])new Entry[entriesSize];
     }
 
+    /**
+     * Copy constructor.
+     */
+    public IntHashMap(IntMap<? extends V> other) {
+        this(other.size());
+
+        other.forEach(this::put);
+    }
+
     /** {@inheritDoc} */
     @Override public V get(int key) {
         int idx = find(key);
@@ -194,14 +206,12 @@ public class IntHashMap<V> implements IntMap<V> {
     }
 
     /** {@inheritDoc} */
-    @Override public V[] values() {
-        V[] vals = (V[])new Object[size];
-
-        int idx = 0;
+    @Override public Collection<V> values() {
+        List<V> vals = new ArrayList<>(size);
 
         for (Entry<V> entry : entries)
             if (entry != null)
-                vals[idx++] = entry.val;
+                vals.add(entry.val);
 
         return vals;
     }
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/util/collection/IntMap.java b/modules/core/src/main/java/org/apache/ignite/internal/util/collection/IntMap.java
index 12d447e..0034b25 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/util/collection/IntMap.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/util/collection/IntMap.java
@@ -17,6 +17,7 @@
 
 package org.apache.ignite.internal.util.collection;
 
+import java.util.Collection;
 import java.util.function.IntFunction;
 import org.apache.ignite.internal.util.typedef.internal.A;
 import org.jetbrains.annotations.Nullable;
@@ -90,8 +91,8 @@ public interface IntMap<V> {
     /** Returns array of keys. */
     int[] keys();
 
-    /** Return array of values. */
-    V[] values();
+    /** Returns a collection of values. */
+    Collection<V> values();
 
     /**
      * If the specified key is not already associated with a value (or is mapped
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/util/collection/IntRWHashMap.java b/modules/core/src/main/java/org/apache/ignite/internal/util/collection/IntRWHashMap.java
index 52cffaa..c366f51 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/util/collection/IntRWHashMap.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/util/collection/IntRWHashMap.java
@@ -17,6 +17,7 @@
 
 package org.apache.ignite.internal.util.collection;
 
+import java.util.Collection;
 import java.util.concurrent.locks.ReadWriteLock;
 import java.util.concurrent.locks.ReentrantReadWriteLock;
 
@@ -118,7 +119,7 @@ public class IntRWHashMap<V> implements IntMap<V> {
     }
 
     /** {@inheritDoc} */
-    @Override public V[] values() {
+    @Override public Collection<V> values() {
         lock.readLock().lock();
         try {
             return delegate.values();
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/ClusterNodeMetricsSelfTest.java b/modules/core/src/test/java/org/apache/ignite/internal/ClusterNodeMetricsSelfTest.java
index 14a44e3..f4d3f71 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/ClusterNodeMetricsSelfTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/ClusterNodeMetricsSelfTest.java
@@ -115,7 +115,7 @@ public class ClusterNodeMetricsSelfTest extends GridCommonAbstractTest {
 
         DataRegion dataRegion = getDefaultDataRegion(ignite);
 
-        DataRegionMetricsImpl memMetrics = dataRegion.memoryMetrics();
+        DataRegionMetricsImpl memMetrics = dataRegion.metrics();
 
         memMetrics.enableMetrics();
 
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/pagemem/impl/PageMemoryNoLoadSelfTest.java b/modules/core/src/test/java/org/apache/ignite/internal/pagemem/impl/PageMemoryNoLoadSelfTest.java
index 61a4e2e..3fa24ad 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/pagemem/impl/PageMemoryNoLoadSelfTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/pagemem/impl/PageMemoryNoLoadSelfTest.java
@@ -32,10 +32,11 @@ import org.apache.ignite.internal.pagemem.PageIdAllocator;
 import org.apache.ignite.internal.pagemem.PageIdUtils;
 import org.apache.ignite.internal.pagemem.PageMemory;
 import org.apache.ignite.internal.pagemem.PageUtils;
+import org.apache.ignite.internal.processors.cache.persistence.DataRegionMetricsImpl;
 import org.apache.ignite.internal.processors.cache.persistence.DummyPageIO;
 import org.apache.ignite.internal.processors.cache.persistence.tree.io.PageIO;
-import org.apache.ignite.internal.processors.metric.impl.LongAdderMetric;
 import org.apache.ignite.internal.util.typedef.internal.U;
+import org.apache.ignite.testframework.junits.GridTestKernalContext;
 import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
 import org.junit.Test;
 
@@ -236,7 +237,7 @@ public class PageMemoryNoLoadSelfTest extends GridCommonAbstractTest {
                     assertNotNull(pageAddr);
 
                     try {
-                        PAGE_IO.initNewPage(pageAddr, id.pageId(), mem.realPageSize(id.groupId()));
+                        PAGE_IO.initNewPage(pageAddr, id.pageId(), mem.realPageSize(id.groupId()), null);
 
                         long updId = PageIdUtils.rotatePageId(id.pageId());
 
@@ -332,8 +333,9 @@ public class PageMemoryNoLoadSelfTest extends GridCommonAbstractTest {
             null,
             PAGE_SIZE,
             plcCfg,
-            new LongAdderMetric("NO_OP", null),
-            true);
+            new DataRegionMetricsImpl(plcCfg, new GridTestKernalContext(log())),
+            true
+        );
     }
 
     /**
@@ -346,7 +348,7 @@ public class PageMemoryNoLoadSelfTest extends GridCommonAbstractTest {
         long pageAddr = mem.writeLock(-1, fullId.pageId(), page);
 
         try {
-            PAGE_IO.initNewPage(pageAddr, fullId.pageId(), mem.realPageSize(fullId.groupId()));
+            PAGE_IO.initNewPage(pageAddr, fullId.pageId(), mem.realPageSize(fullId.groupId()), null);
 
             for (int i = PageIO.COMMON_HEADER_END; i < PAGE_SIZE; i++)
                 PageUtils.putByte(pageAddr, i, (byte)val);
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/eviction/paged/PageEvictionMetricTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/eviction/paged/PageEvictionMetricTest.java
index 9c2facc..5299cda 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/eviction/paged/PageEvictionMetricTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/eviction/paged/PageEvictionMetricTest.java
@@ -66,7 +66,7 @@ public class PageEvictionMetricTest extends PageEvictionAbstractTest {
         IgniteEx ignite = startGrid(0);
 
         DataRegionMetricsImpl metrics =
-            ignite.context().cache().context().database().dataRegion(null).memoryMetrics();
+            ignite.context().cache().context().database().dataRegion(null).metrics();
 
         metrics.enableMetrics();
 
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/IgnitePdsRecoveryAfterFileCorruptionTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/IgnitePdsRecoveryAfterFileCorruptionTest.java
index 82dfb71..aebf7f3 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/IgnitePdsRecoveryAfterFileCorruptionTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/IgnitePdsRecoveryAfterFileCorruptionTest.java
@@ -202,7 +202,7 @@ public class IgnitePdsRecoveryAfterFileCorruptionTest extends GridCommonAbstract
             final long pageAddr = mem.writeLock(fullId.groupId(), fullId.pageId(), page);
 
             try {
-                pageIO.initNewPage(pageAddr, fullId.pageId(), mem.realPageSize(fullId.groupId()));
+                pageIO.initNewPage(pageAddr, fullId.pageId(), mem.realPageSize(fullId.groupId()), null);
             }
             finally {
                 mem.writeUnlock(fullId.groupId(), fullId.pageId(), page, null, true);
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/IgnitePdsTaskCancelingTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/IgnitePdsTaskCancelingTest.java
index ba702c6..a762821 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/IgnitePdsTaskCancelingTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/IgnitePdsTaskCancelingTest.java
@@ -216,7 +216,7 @@ public class IgnitePdsTaskCancelingTest extends GridCommonAbstractTest {
 
                 long pageAdr = ptr + i * pageSize;
 
-                pageIO.initNewPage(pageAdr, pageId, pageSize);
+                pageIO.initNewPage(pageAdr, pageId, pageSize, null);
 
                 ByteBuffer buf = GridUnsafe.wrapPointer(pageAdr, pageSize);
 
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/db/IgnitePdsDataRegionMetricsTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/db/IgnitePdsDataRegionMetricsTest.java
index 5f65cf6..c12b40f 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/db/IgnitePdsDataRegionMetricsTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/db/IgnitePdsDataRegionMetricsTest.java
@@ -247,7 +247,7 @@ public class IgnitePdsDataRegionMetricsTest extends GridCommonAbstractTest {
         ig.cluster().active(true);
 
         DataRegionMetricsImpl regionMetrics = ig.cachex(DEFAULT_CACHE_NAME)
-            .context().group().dataRegion().memoryMetrics();
+            .context().group().dataRegion().metrics();
 
         Assert.assertTrue(regionMetrics.getCheckpointBufferSize() != 0);
         Assert.assertTrue(regionMetrics.getCheckpointBufferSize() <= MAX_REGION_SIZE);
@@ -265,7 +265,7 @@ public class IgnitePdsDataRegionMetricsTest extends GridCommonAbstractTest {
         ig.cluster().active(true);
 
         final DataRegionMetricsImpl regionMetrics = ig.cachex(DEFAULT_CACHE_NAME)
-            .context().group().dataRegion().memoryMetrics();
+            .context().group().dataRegion().metrics();
 
         Assert.assertEquals(0, regionMetrics.getUsedCheckpointBufferPages());
         Assert.assertEquals(0, regionMetrics.getUsedCheckpointBufferSize());
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/db/file/IgnitePdsCheckpointSimulationWithRealCpDisabledTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/db/file/IgnitePdsCheckpointSimulationWithRealCpDisabledTest.java
index a5d89eb..3fa1611 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/db/file/IgnitePdsCheckpointSimulationWithRealCpDisabledTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/db/file/IgnitePdsCheckpointSimulationWithRealCpDisabledTest.java
@@ -243,7 +243,7 @@ public class IgnitePdsCheckpointSimulationWithRealCpDisabledTest extends GridCom
 
                     try {
                         DataPageIO.VERSIONS.latest().initNewPage(pageAddr, fullId.pageId(),
-                            mem.realPageSize(fullId.groupId()));
+                            mem.realPageSize(fullId.groupId()), null);
 
                         for (int i = PageIO.COMMON_HEADER_END + DataPageIO.ITEMS_OFF; i < mem.pageSize(); i++)
                             PageUtils.putByte(pageAddr, i, (byte)0xAB);
@@ -584,7 +584,7 @@ public class IgnitePdsCheckpointSimulationWithRealCpDisabledTest extends GridCom
                     long pageAddr = mem.writeLock(fullId.groupId(), fullId.pageId(), page);
 
                     try {
-                        pageIO.initNewPage(pageAddr, fullId.pageId(), mem.realPageSize(fullId.groupId()));
+                        pageIO.initNewPage(pageAddr, fullId.pageId(), mem.realPageSize(fullId.groupId()), null);
 
                         assertTrue(mem.isDirty(fullId.groupId(), fullId.pageId(), page));
                     }
@@ -673,7 +673,7 @@ public class IgnitePdsCheckpointSimulationWithRealCpDisabledTest extends GridCom
             long pageAddr = mem.writeLock(fullId.groupId(), fullId.pageId(), page);
 
             try {
-                DummyPageIO.VERSIONS.latest().initNewPage(pageAddr, fullId.pageId(), mem.realPageSize(fullId.groupId()));
+                DummyPageIO.VERSIONS.latest().initNewPage(pageAddr, fullId.pageId(), mem.realPageSize(fullId.groupId()), null);
 
                 ThreadLocalRandom rnd = ThreadLocalRandom.current();
 
@@ -1098,7 +1098,7 @@ public class IgnitePdsCheckpointSimulationWithRealCpDisabledTest extends GridCom
             final long pageAddr = mem.writeLock(fullId.groupId(), fullId.pageId(), page);
 
             try {
-                pageIO.initNewPage(pageAddr, fullId.pageId(), mem.realPageSize(fullId.groupId()));
+                pageIO.initNewPage(pageAddr, fullId.pageId(), mem.realPageSize(fullId.groupId()), null);
             }
             finally {
                 mem.writeUnlock(fullId.groupId(), fullId.pageId(), page, null, true);
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/db/file/IgnitePdsPageReplacementTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/db/file/IgnitePdsPageReplacementTest.java
index 9b848c6..2193f543 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/db/file/IgnitePdsPageReplacementTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/db/file/IgnitePdsPageReplacementTest.java
@@ -203,7 +203,7 @@ public class IgnitePdsPageReplacementTest extends GridCommonAbstractTest {
             final long pageAddr = mem.writeLock(fullId.groupId(), fullId.pageId(), page);
 
             try {
-                pageIO.initNewPage(pageAddr, fullId.pageId(), mem.realPageSize(fullId.groupId()));
+                pageIO.initNewPage(pageAddr, fullId.pageId(), mem.realPageSize(fullId.groupId()), null);
             }
             finally {
                 mem.writeUnlock(fullId.groupId(), fullId.pageId(), page, null, true);
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/db/wal/WalCompactionTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/db/wal/WalCompactionTest.java
index a3e8e97..682ac0b 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/db/wal/WalCompactionTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/db/wal/WalCompactionTest.java
@@ -575,7 +575,7 @@ public class WalCompactionTest extends GridCommonAbstractTest {
     private static byte[] dummyPage(int pageSize) {
         ByteBuffer pageBuf = ByteBuffer.allocateDirect(pageSize);
 
-        DummyPageIO.VERSIONS.latest().initNewPage(GridUnsafe.bufferAddress(pageBuf), -1, pageSize);
+        DummyPageIO.VERSIONS.latest().initNewPage(GridUnsafe.bufferAddress(pageBuf), -1, pageSize, null);
 
         byte[] pageData = new byte[pageSize];
 
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/defragmentation/LinkMapTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/defragmentation/LinkMapTest.java
index ee2d436..966db78 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/defragmentation/LinkMapTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/defragmentation/LinkMapTest.java
@@ -23,7 +23,8 @@ import org.apache.ignite.internal.pagemem.FullPageId;
 import org.apache.ignite.internal.pagemem.PageIdAllocator;
 import org.apache.ignite.internal.pagemem.PageMemory;
 import org.apache.ignite.internal.pagemem.impl.PageMemoryNoStoreImpl;
-import org.apache.ignite.internal.processors.metric.impl.LongAdderMetric;
+import org.apache.ignite.internal.processors.cache.persistence.DataRegionMetricsImpl;
+import org.apache.ignite.testframework.junits.GridTestKernalContext;
 import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
 import org.junit.Test;
 
@@ -68,13 +69,14 @@ public class LinkMapTest extends GridCommonAbstractTest {
                 .setInitialSize(2 * MB)
                 .setMaxSize(2 * MB);
 
-        PageMemory pageMem = new PageMemoryNoStoreImpl(log,
-                new UnsafeMemoryProvider(log),
-                null,
-                PAGE_SIZE,
-                plcCfg,
-                new LongAdderMetric("NO_OP", null),
-                true);
+        PageMemory pageMem = new PageMemoryNoStoreImpl(
+            log,
+            new UnsafeMemoryProvider(log),
+            null,
+            PAGE_SIZE,
+            plcCfg,
+            new DataRegionMetricsImpl(plcCfg, new GridTestKernalContext(log())),
+            true);
 
         pageMem.start();
 
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/BPlusTreePageMemoryImplTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/BPlusTreePageMemoryImplTest.java
index c44a903..82ecd66 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/BPlusTreePageMemoryImplTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/BPlusTreePageMemoryImplTest.java
@@ -46,8 +46,6 @@ import org.apache.ignite.spi.metric.noop.NoopMetricExporterSpi;
 import org.apache.ignite.testframework.junits.GridTestKernalContext;
 import org.mockito.Mockito;
 
-import static org.apache.ignite.internal.processors.database.DataRegionMetricsSelfTest.NO_OP_METRICS;
-
 /**
  *
  */
@@ -122,10 +120,7 @@ public class BPlusTreePageMemoryImplTest extends BPlusTreeSelfTest {
                 }
             },
             () -> true,
-            new DataRegionMetricsImpl(new DataRegionConfiguration(),
-                cctx.metric(),
-                cctx.performanceStatistics(),
-                NO_OP_METRICS),
+            new DataRegionMetricsImpl(new DataRegionConfiguration(), cctx),
             PageMemoryImpl.ThrottlingPolicy.DISABLED,
             clo
         );
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/BPlusTreeReuseListPageMemoryImplTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/BPlusTreeReuseListPageMemoryImplTest.java
index 024b2ad..a9e3800 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/BPlusTreeReuseListPageMemoryImplTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/BPlusTreeReuseListPageMemoryImplTest.java
@@ -45,8 +45,6 @@ import org.apache.ignite.spi.metric.noop.NoopMetricExporterSpi;
 import org.apache.ignite.testframework.junits.GridTestKernalContext;
 import org.mockito.Mockito;
 
-import static org.apache.ignite.internal.processors.database.DataRegionMetricsSelfTest.NO_OP_METRICS;
-
 /**
  *
  */
@@ -121,10 +119,7 @@ public class BPlusTreeReuseListPageMemoryImplTest extends BPlusTreeReuseSelfTest
                 }
             },
             () -> true,
-            new DataRegionMetricsImpl(new DataRegionConfiguration(),
-                cctx.metric(),
-                cctx.performanceStatistics(),
-                NO_OP_METRICS),
+            new DataRegionMetricsImpl(new DataRegionConfiguration(), cctx),
             PageMemoryImpl.ThrottlingPolicy.DISABLED,
             clo
         );
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/IgnitePageMemReplaceDelayedWriteUnitTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/IgnitePageMemReplaceDelayedWriteUnitTest.java
index 362044f..bc54e41 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/IgnitePageMemReplaceDelayedWriteUnitTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/IgnitePageMemReplaceDelayedWriteUnitTest.java
@@ -61,10 +61,7 @@ import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.Timeout;
 import org.mockito.Mockito;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
 
-import static org.apache.ignite.internal.processors.database.DataRegionMetricsSelfTest.NO_OP_METRICS;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
@@ -238,52 +235,24 @@ public class IgnitePageMemReplaceDelayedWriteUnitTest {
 
         when(kernalCtx.config()).thenReturn(cfg);
         when(kernalCtx.log(any(Class.class))).thenReturn(log);
-        when(kernalCtx.internalSubscriptionProcessor()).thenAnswer(new Answer<Object>() {
-            @Override public Object answer(InvocationOnMock mock) throws Throwable {
-                return new GridInternalSubscriptionProcessor(kernalCtx);
-            }
-        });
-        when(kernalCtx.encryption()).thenAnswer(new Answer<Object>() {
-            @Override public Object answer(InvocationOnMock mock) throws Throwable {
-                return new GridEncryptionManager(kernalCtx);
-            }
-        });
-        when(kernalCtx.metric()).thenAnswer(new Answer<Object>() {
-            @Override public Object answer(InvocationOnMock mock) throws Throwable {
-                return new GridMetricManager(kernalCtx);
-            }
-        });
-
-        when(kernalCtx.performanceStatistics()).thenAnswer(new Answer<Object>() {
-            @Override public Object answer(InvocationOnMock mock) throws Throwable {
-                return new PerformanceStatisticsProcessor(kernalCtx);
-            }
-        });
+        when(kernalCtx.internalSubscriptionProcessor()).thenAnswer(mock -> new GridInternalSubscriptionProcessor(kernalCtx));
+        when(kernalCtx.encryption()).thenAnswer(mock -> new GridEncryptionManager(kernalCtx));
+        when(kernalCtx.metric()).thenAnswer(mock -> new GridMetricManager(kernalCtx));
+        when(kernalCtx.performanceStatistics()).thenAnswer(mock -> new PerformanceStatisticsProcessor(kernalCtx));
 
         when(sctx.kernalContext()).thenReturn(kernalCtx);
 
-        when(sctx.gridEvents()).thenAnswer(new Answer<Object>() {
-            @Override public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
-                return new GridEventStorageManager(kernalCtx);
-            }
-        });
+        when(sctx.gridEvents()).thenAnswer(invocationOnMock -> new GridEventStorageManager(kernalCtx));
 
         DataRegionConfiguration regCfg = cfg.getDataStorageConfiguration().getDefaultDataRegionConfiguration();
 
-        DataRegionMetricsImpl memMetrics = new DataRegionMetricsImpl(regCfg,
-            kernalCtx.metric(),
-            kernalCtx.performanceStatistics(),
-            NO_OP_METRICS);
+        DataRegionMetricsImpl memMetrics = new DataRegionMetricsImpl(regCfg, kernalCtx);
 
         long[] sizes = prepareSegmentSizes(regCfg.getMaxSize());
 
         DirectMemoryProvider provider = new UnsafeMemoryProvider(log);
 
-        IgniteOutClosure<CheckpointProgress> clo = new IgniteOutClosure<CheckpointProgress>() {
-            @Override public CheckpointProgress apply() {
-                return Mockito.mock(CheckpointProgressImpl.class);
-            }
-        };
+        IgniteOutClosure<CheckpointProgress> clo = () -> Mockito.mock(CheckpointProgressImpl.class);
 
         PageMemoryImpl memory = new PageMemoryImpl(provider, sizes, sctx, sctx.pageStore(), pageSize,
             pageWriter, null, () -> true, memMetrics, PageMemoryImpl.ThrottlingPolicy.DISABLED,
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/IgniteThrottlingUnitTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/IgniteThrottlingUnitTest.java
index 2fcb627..ebbb0f1 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/IgniteThrottlingUnitTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/IgniteThrottlingUnitTest.java
@@ -45,7 +45,6 @@ import org.junit.rules.Timeout;
 import org.mockito.Mockito;
 
 import static java.lang.Thread.State.TIMED_WAITING;
-import static org.apache.ignite.internal.processors.database.DataRegionMetricsSelfTest.NO_OP_METRICS;
 import static org.apache.ignite.testframework.GridTestUtils.waitForCondition;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
@@ -82,10 +81,7 @@ public class IgniteThrottlingUnitTest {
         ctx.add(new GridMetricManager(ctx));
         ctx.add(new PerformanceStatisticsProcessor(ctx));
 
-        DataRegionMetricsImpl metrics = new DataRegionMetricsImpl(new DataRegionConfiguration(),
-            ctx.metric(),
-            ctx.performanceStatistics(),
-            NO_OP_METRICS);
+        DataRegionMetricsImpl metrics = new DataRegionMetricsImpl(new DataRegionConfiguration(), ctx);
 
         when(pageMemory2g.metrics()).thenReturn(metrics);
     }
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/IndexStoragePageMemoryImplTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/IndexStoragePageMemoryImplTest.java
index eb22cce..efccb29 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/IndexStoragePageMemoryImplTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/IndexStoragePageMemoryImplTest.java
@@ -48,8 +48,6 @@ import org.apache.ignite.spi.metric.noop.NoopMetricExporterSpi;
 import org.apache.ignite.testframework.junits.GridTestKernalContext;
 import org.mockito.Mockito;
 
-import static org.apache.ignite.internal.processors.database.DataRegionMetricsSelfTest.NO_OP_METRICS;
-
 /**
  *
  */
@@ -118,11 +116,7 @@ public class IndexStoragePageMemoryImplTest extends IndexStorageSelfTest {
             new CacheDiagnosticManager()
         );
 
-        IgniteOutClosure<CheckpointProgress> clo = new IgniteOutClosure<CheckpointProgress>() {
-            @Override public CheckpointProgress apply() {
-                return Mockito.mock(CheckpointProgressImpl.class);
-            }
-        };
+        IgniteOutClosure<CheckpointProgress> clo = () -> Mockito.mock(CheckpointProgressImpl.class);
 
         return new PageMemoryImpl(
             provider, sizes,
@@ -137,10 +131,7 @@ public class IndexStoragePageMemoryImplTest extends IndexStorageSelfTest {
                 }
             },
             () -> true,
-            new DataRegionMetricsImpl(new DataRegionConfiguration(),
-                cctx.metric(),
-                cctx.performanceStatistics(),
-                NO_OP_METRICS),
+            new DataRegionMetricsImpl(new DataRegionConfiguration(), cctx),
             PageMemoryImpl.ThrottlingPolicy.DISABLED,
             clo
         );
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/NoOpPageStoreManager.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/NoOpPageStoreManager.java
index 05e9235..c53845f 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/NoOpPageStoreManager.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/NoOpPageStoreManager.java
@@ -23,7 +23,6 @@ import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.atomic.AtomicInteger;
-import java.util.function.LongConsumer;
 import java.util.function.Predicate;
 import org.apache.ignite.IgniteCheckedException;
 import org.apache.ignite.configuration.CacheConfiguration;
@@ -58,7 +57,7 @@ public class NoOpPageStoreManager implements IgnitePageStoreManager {
 
     /** {@inheritDoc} */
     @Override public void initialize(int cacheId, int partitions, String workingDir,
-        LongConsumer tracker) throws IgniteCheckedException {
+        PageMetrics pageMetrics) throws IgniteCheckedException {
         // No-op.
     }
 
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/PageMemoryImplNoLoadTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/PageMemoryImplNoLoadTest.java
index 6edd85a..afd127c 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/PageMemoryImplNoLoadTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/PageMemoryImplNoLoadTest.java
@@ -31,7 +31,6 @@ import org.apache.ignite.internal.pagemem.FullPageId;
 import org.apache.ignite.internal.pagemem.PageMemory;
 import org.apache.ignite.internal.pagemem.impl.PageMemoryNoLoadSelfTest;
 import org.apache.ignite.internal.processors.cache.GridCacheSharedContext;
-import org.apache.ignite.internal.processors.cache.persistence.CheckpointLockStateChecker;
 import org.apache.ignite.internal.processors.cache.persistence.DataRegionMetricsImpl;
 import org.apache.ignite.internal.processors.cache.persistence.IgniteCacheDatabaseSharedManager;
 import org.apache.ignite.internal.processors.cache.persistence.checkpoint.CheckpointProgress;
@@ -49,8 +48,6 @@ import org.apache.ignite.testframework.junits.GridTestKernalContext;
 import org.junit.Test;
 import org.mockito.Mockito;
 
-import static org.apache.ignite.internal.processors.database.DataRegionMetricsSelfTest.NO_OP_METRICS;
-
 /**
  *
  */
@@ -108,11 +105,7 @@ public class PageMemoryImplNoLoadTest extends PageMemoryNoLoadSelfTest {
             null
         );
 
-        IgniteOutClosure<CheckpointProgress> clo = new IgniteOutClosure<CheckpointProgress>() {
-            @Override public CheckpointProgress apply() {
-                return Mockito.mock(CheckpointProgressImpl.class);
-            }
-        };
+        IgniteOutClosure<CheckpointProgress> clo = () -> Mockito.mock(CheckpointProgressImpl.class);
 
         return new PageMemoryImpl(
             provider,
@@ -127,15 +120,8 @@ public class PageMemoryImplNoLoadTest extends PageMemoryNoLoadSelfTest {
                 @Override public void applyx(Long page, FullPageId fullId, PageMemoryEx pageMem) {
                 }
             },
-            new CheckpointLockStateChecker() {
-                @Override public boolean checkpointLockIsHeldByThread() {
-                    return true;
-                }
-            },
-            new DataRegionMetricsImpl(new DataRegionConfiguration(),
-                cctx.metric(),
-                cctx.performanceStatistics(),
-                NO_OP_METRICS),
+            () -> true,
+            new DataRegionMetricsImpl(new DataRegionConfiguration(), cctx),
             PageMemoryImpl.ThrottlingPolicy.DISABLED,
             clo
         );
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/PageMemoryImplTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/PageMemoryImplTest.java
index b85b96d..a9a5322 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/PageMemoryImplTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/PageMemoryImplTest.java
@@ -45,7 +45,6 @@ import org.apache.ignite.internal.pagemem.PageUtils;
 import org.apache.ignite.internal.pagemem.store.IgnitePageStoreManager;
 import org.apache.ignite.internal.pagemem.store.PageStore;
 import org.apache.ignite.internal.processors.cache.GridCacheSharedContext;
-import org.apache.ignite.internal.processors.cache.persistence.CheckpointLockStateChecker;
 import org.apache.ignite.internal.processors.cache.persistence.DataRegionMetricsImpl;
 import org.apache.ignite.internal.processors.cache.persistence.DummyPageIO;
 import org.apache.ignite.internal.processors.cache.persistence.IgniteCacheDatabaseSharedManager;
@@ -80,7 +79,6 @@ import org.mockito.Mockito;
 import static org.apache.ignite.internal.pagemem.PageIdAllocator.FLAG_IDX;
 import static org.apache.ignite.internal.pagemem.PageIdAllocator.INDEX_PARTITION;
 import static org.apache.ignite.internal.processors.cache.persistence.pagemem.PageMemoryImpl.CHECKPOINT_POOL_OVERFLOW_ERROR_MSG;
-import static org.apache.ignite.internal.processors.database.DataRegionMetricsSelfTest.NO_OP_METRICS;
 
 /**
  *
@@ -154,14 +152,12 @@ public class PageMemoryImplTest extends GridCommonAbstractTest {
 
         memory.finishCheckpoint();
 
-        GridTestUtils.runAsync(new Runnable() {
-            @Override public void run() {
-                try {
-                    acquireAndReleaseWriteLock(memory, lastPage.get()); //we should be able get lock again
-                }
-                catch (IgniteCheckedException e) {
-                    throw new AssertionError(e);
-                }
+        GridTestUtils.runAsync(() -> {
+            try {
+                acquireAndReleaseWriteLock(memory, lastPage.get()); //we should be able get lock again
+            }
+            catch (IgniteCheckedException e) {
+                throw new AssertionError(e);
             }
         }).get(getTestTimeout());
     }
@@ -429,7 +425,7 @@ public class PageMemoryImplTest extends GridCommonAbstractTest {
             long ptr = mem.writeLock(grpId, pageId, page);
 
             try {
-                DummyPageIO.VERSIONS.latest().initNewPage(ptr, pageId, PAGE_SIZE);
+                DummyPageIO.VERSIONS.latest().initNewPage(ptr, pageId, PAGE_SIZE, null);
 
                 for (int i = PageIO.COMMON_HEADER_END; i < mem.pageSize(); i++)
                     PageUtils.putByte(ptr, i, val);
@@ -490,22 +486,20 @@ public class PageMemoryImplTest extends GridCommonAbstractTest {
         AtomicBoolean stop = new AtomicBoolean(false);
 
         try {
-            GridTestUtils.runAsync(new Runnable() {
-                @Override public void run() {
-                    for (FullPageId page : pages) {
-                        if (ThreadLocalRandom.current().nextDouble() < 0.5) // Mark dirty 50% of pages
-                            try {
-                                acquireAndReleaseWriteLock(memory, page);
-
-                                if (stop.get())
-                                    break;
-                            }
-                            catch (IgniteCheckedException e) {
-                                log.error("runAsync ended with exception", e);
-
-                                fail();
-                            }
-                    }
+            GridTestUtils.runAsync(() -> {
+                for (FullPageId page : pages) {
+                    if (ThreadLocalRandom.current().nextDouble() < 0.5) // Mark dirty 50% of pages
+                        try {
+                            acquireAndReleaseWriteLock(memory, page);
+
+                            if (stop.get())
+                                break;
+                        }
+                        catch (IgniteCheckedException e) {
+                            log.error("runAsync ended with exception", e);
+
+                            fail();
+                        }
                 }
             }).get(5_000);
         }
@@ -641,57 +635,47 @@ public class PageMemoryImplTest extends GridCommonAbstractTest {
         Mockito.when(cl0.syncedPagesCounter()).thenReturn(new AtomicInteger(1_000_000));
         Mockito.when(cl0.writtenPagesCounter()).thenReturn(new AtomicInteger(1_000_000));
 
-        PageMemoryImpl mem = cpBufChecker == null ? new PageMemoryImpl(
-            provider,
-            sizes,
-            sharedCtx,
-            sharedCtx.pageStore(),
-            PAGE_SIZE,
-            replaceWriter,
-            new GridInClosure3X<Long, FullPageId, PageMemoryEx>() {
-                @Override public void applyx(Long page, FullPageId fullId, PageMemoryEx pageMem) {
-                }
-            }, new CheckpointLockStateChecker() {
-                @Override public boolean checkpointLockIsHeldByThread() {
-                    return true;
-                }
-            },
-            new DataRegionMetricsImpl(igniteCfg.getDataStorageConfiguration().getDefaultDataRegionConfiguration(),
-                kernalCtx.metric(),
-                kernalCtx.performanceStatistics(),
-                NO_OP_METRICS),
-            throttlingPlc,
-            noThrottle
-        ) : new PageMemoryImpl(
-            provider,
-            sizes,
-            sharedCtx,
-            sharedCtx.pageStore(),
-            PAGE_SIZE,
-            replaceWriter,
-            new GridInClosure3X<Long, FullPageId, PageMemoryEx>() {
-                @Override public void applyx(Long page, FullPageId fullId, PageMemoryEx pageMem) {
+        PageMemoryImpl mem = cpBufChecker == null ?
+            new PageMemoryImpl(
+                provider,
+                sizes,
+                sharedCtx,
+                sharedCtx.pageStore(),
+                PAGE_SIZE,
+                replaceWriter,
+                new GridInClosure3X<Long, FullPageId, PageMemoryEx>() {
+                    @Override public void applyx(Long page, FullPageId fullId, PageMemoryEx pageMem) {
+                    }
+                },
+                () -> true,
+                new DataRegionMetricsImpl(igniteCfg.getDataStorageConfiguration().getDefaultDataRegionConfiguration(), kernalCtx),
+                throttlingPlc,
+                noThrottle
+            ) :
+            new PageMemoryImpl(
+                provider,
+                sizes,
+                sharedCtx,
+                sharedCtx.pageStore(),
+                PAGE_SIZE,
+                replaceWriter,
+                new GridInClosure3X<Long, FullPageId, PageMemoryEx>() {
+                    @Override public void applyx(Long page, FullPageId fullId, PageMemoryEx pageMem) {
+                    }
+                },
+                () -> true,
+                new DataRegionMetricsImpl(igniteCfg.getDataStorageConfiguration().getDefaultDataRegionConfiguration(), kernalCtx),
+                throttlingPlc,
+                noThrottle
+            ) {
+                @Override public FullPageId pullPageFromCpBuffer() {
+                    FullPageId pageId = super.pullPageFromCpBuffer();
+
+                    cpBufChecker.apply(pageId);
+
+                    return pageId;
                 }
-            }, new CheckpointLockStateChecker() {
-            @Override public boolean checkpointLockIsHeldByThread() {
-                return true;
-            }
-        },
-            new DataRegionMetricsImpl(igniteCfg.getDataStorageConfiguration().getDefaultDataRegionConfiguration(),
-                kernalCtx.metric(),
-                kernalCtx.performanceStatistics(),
-                NO_OP_METRICS),
-            throttlingPlc,
-            noThrottle
-        ) {
-            @Override public FullPageId pullPageFromCpBuffer() {
-                FullPageId pageId = super.pullPageFromCpBuffer();
-
-                cpBufChecker.apply(pageId);
-
-                return pageId;
-            }
-        };
+            };
 
         mem.metrics().enableMetrics();
 
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/PageMemoryNoStoreLeakTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/PageMemoryNoStoreLeakTest.java
index f5c06fd..5cd217e 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/PageMemoryNoStoreLeakTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/PageMemoryNoStoreLeakTest.java
@@ -22,8 +22,9 @@ import org.apache.ignite.internal.mem.DirectMemoryProvider;
 import org.apache.ignite.internal.mem.unsafe.UnsafeMemoryProvider;
 import org.apache.ignite.internal.pagemem.PageMemory;
 import org.apache.ignite.internal.pagemem.impl.PageMemoryNoStoreImpl;
-import org.apache.ignite.internal.processors.metric.impl.LongAdderMetric;
+import org.apache.ignite.internal.processors.cache.persistence.DataRegionMetricsImpl;
 import org.apache.ignite.internal.util.typedef.internal.D;
+import org.apache.ignite.testframework.junits.GridTestKernalContext;
 import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
 import org.junit.Test;
 
@@ -63,8 +64,9 @@ public class PageMemoryNoStoreLeakTest extends GridCommonAbstractTest {
                 null,
                 PAGE_SIZE,
                 plcCfg,
-                new LongAdderMetric("NO_OP", null),
-                true);
+                new DataRegionMetricsImpl(plcCfg, new GridTestKernalContext(log())),
+                true
+            );
 
             try {
                 mem.start();
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/wal/memtracker/PageMemoryTracker.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/wal/memtracker/PageMemoryTracker.java
index 8db79ea..e0b35be 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/wal/memtracker/PageMemoryTracker.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/wal/memtracker/PageMemoryTracker.java
@@ -34,6 +34,7 @@ import java.util.concurrent.locks.ReentrantReadWriteLock;
 import java.util.stream.Collectors;
 import org.apache.ignite.IgniteCheckedException;
 import org.apache.ignite.IgniteLogger;
+import org.apache.ignite.configuration.DataRegionConfiguration;
 import org.apache.ignite.internal.GridKernalContext;
 import org.apache.ignite.internal.IgniteEx;
 import org.apache.ignite.internal.mem.DirectMemoryProvider;
@@ -54,6 +55,7 @@ import org.apache.ignite.internal.processors.cache.GridCacheSharedContext;
 import org.apache.ignite.internal.processors.cache.mvcc.MvccUtils;
 import org.apache.ignite.internal.processors.cache.mvcc.txlog.TxLog;
 import org.apache.ignite.internal.processors.cache.persistence.DataRegion;
+import org.apache.ignite.internal.processors.cache.persistence.DataRegionMetricsImpl;
 import org.apache.ignite.internal.processors.cache.persistence.GridCacheDatabaseSharedManager;
 import org.apache.ignite.internal.processors.cache.persistence.checkpoint.CheckpointListener;
 import org.apache.ignite.internal.processors.cache.persistence.file.FilePageStoreManager;
@@ -75,6 +77,8 @@ import org.mockito.Mockito;
 
 import static org.apache.ignite.internal.processors.cache.persistence.tree.io.PageIO.T_CACHE_ID_DATA_REF_MVCC_LEAF;
 import static org.apache.ignite.internal.processors.cache.persistence.tree.io.PageIO.T_DATA_REF_MVCC_LEAF;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
 /**
  * Page memory tracker.
@@ -226,27 +230,7 @@ public class PageMemoryTracker implements IgnitePlugin {
 
         pageSize = ctx.igniteConfiguration().getDataStorageConfiguration().getPageSize();
 
-        EncryptionSpi encSpi = ctx.igniteConfiguration().getEncryptionSpi();
-
-        pageMemoryMock = Mockito.mock(PageMemory.class);
-
-        Mockito.doReturn(pageSize).when(pageMemoryMock).pageSize();
-        Mockito.when(pageMemoryMock.realPageSize(Mockito.anyInt())).then(mock -> {
-            int grpId = (Integer)mock.getArguments()[0];
-
-            if (gridCtx.encryption().getActiveKey(grpId) == null)
-                return pageSize;
-
-            return pageSize
-                - (encSpi.encryptedSizeNoPadding(pageSize) - pageSize)
-                - encSpi.blockSize() /* For CRC. */;
-        });
-
-        Mockito.when(pageMemoryMock.pageBuffer(Mockito.anyLong())).then(mock -> {
-            long pageAddr = (Long)mock.getArguments()[0];
-
-            return GridUnsafe.wrapPointer(pageAddr, pageSize);
-        });
+        pageMemoryMock = mockPageMemory();
 
         GridCacheSharedContext sharedCtx = gridCtx.cache().context();
 
@@ -304,6 +288,38 @@ public class PageMemoryTracker implements IgnitePlugin {
     }
 
     /**
+     * Creates a mock for the Page Memory.
+     */
+    private PageMemory mockPageMemory() {
+        PageMemory mock = mock(PageMemory.class);
+
+        when(mock.pageSize()).thenReturn(pageSize);
+
+        when(mock.realPageSize(Mockito.anyInt())).then(invocation -> {
+            int grpId = (Integer)invocation.getArguments()[0];
+
+            if (gridCtx.encryption().getActiveKey(grpId) == null)
+                return pageSize;
+
+            EncryptionSpi encSpi = ctx.igniteConfiguration().getEncryptionSpi();
+
+            return pageSize
+                - (encSpi.encryptedSizeNoPadding(pageSize) - pageSize)
+                - encSpi.blockSize() /* For CRC. */;
+        });
+
+        when(mock.pageBuffer(Mockito.anyLong())).then(invocation -> {
+            long pageAddr = (Long)invocation.getArguments()[0];
+
+            return GridUnsafe.wrapPointer(pageAddr, pageSize);
+        });
+
+        when(mock.metrics()).thenReturn(new DataRegionMetricsImpl(new DataRegionConfiguration(), gridCtx));
+
+        return mock;
+    }
+
+    /**
      * Stop tracking, release resources.
      */
     synchronized void stop() {
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/wal/scanner/WalScannerTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/wal/scanner/WalScannerTest.java
index 2e408d2..3d394b4 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/wal/scanner/WalScannerTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/wal/scanner/WalScannerTest.java
@@ -358,7 +358,7 @@ public class WalScannerTest {
     public static byte[] dummyPage(int pageSize, long pageId) {
         ByteBuffer pageBuf = ByteBuffer.allocateDirect(pageSize);
 
-        DummyPageIO.VERSIONS.latest().initNewPage(GridUnsafe.bufferAddress(pageBuf), pageId, pageSize);
+        DummyPageIO.VERSIONS.latest().initNewPage(GridUnsafe.bufferAddress(pageBuf), pageId, pageSize, null);
 
         byte[] pageData = new byte[pageSize];
 
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/query/CacheDataPageScanQueryTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/query/CacheDataPageScanQueryTest.java
index 3f67f09..59cd97e 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/query/CacheDataPageScanQueryTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/query/CacheDataPageScanQueryTest.java
@@ -100,7 +100,7 @@ public class CacheDataPageScanQueryTest extends GridCommonAbstractTest {
 
         IgniteInternalCache<Long, String> cache = ignite.cachex(CACHE);
         CacheGroupMetricsImpl metrics = cache.context().group().metrics();
-        DataRegionMetricsImpl rmx = cache.context().dataRegion().memoryMetrics();
+        DataRegionMetricsImpl rmx = cache.context().dataRegion().metrics();
 
         long maxKey = 10_000;
 
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cluster/BaselineAutoAdjustTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cluster/BaselineAutoAdjustTest.java
index dcaf9a5..8b7b6fd 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cluster/BaselineAutoAdjustTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cluster/BaselineAutoAdjustTest.java
@@ -59,10 +59,7 @@ import static org.apache.ignite.cluster.ClusterState.ACTIVE;
 import static org.apache.ignite.events.EventType.EVT_NODE_JOINED;
 import static org.apache.ignite.testframework.GridTestUtils.runMultiThreadedAsync;
 import static org.apache.ignite.testframework.GridTestUtils.waitForCondition;
-import static org.hamcrest.CoreMatchers.equalTo;
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.CoreMatchers.not;
-import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
@@ -424,7 +421,7 @@ public class BaselineAutoAdjustTest extends GridCommonAbstractTest {
         if (isPersistent())
             assertEquals(initBaseline, baselineAfterNodeLeft);
         else {
-            assertThat(initBaseline, is(not(equalTo(baselineAfterNodeLeft))));
+            assertNotEquals(initBaseline, baselineAfterNodeLeft);
 
             assertEquals(1, baselineAfterNodeLeft.size());
         }
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/database/BPlusTreeSelfTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/database/BPlusTreeSelfTest.java
index e15d360..73ceba7 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/processors/database/BPlusTreeSelfTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/database/BPlusTreeSelfTest.java
@@ -55,6 +55,7 @@ import org.apache.ignite.internal.pagemem.PageIdAllocator;
 import org.apache.ignite.internal.pagemem.PageMemory;
 import org.apache.ignite.internal.pagemem.PageUtils;
 import org.apache.ignite.internal.pagemem.impl.PageMemoryNoStoreImpl;
+import org.apache.ignite.internal.processors.cache.persistence.DataRegionMetricsImpl;
 import org.apache.ignite.internal.processors.cache.persistence.DataStructure;
 import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageLockTrackerManager;
 import org.apache.ignite.internal.processors.cache.persistence.tree.BPlusTree;
@@ -66,7 +67,6 @@ import org.apache.ignite.internal.processors.cache.persistence.tree.io.PageIO;
 import org.apache.ignite.internal.processors.cache.persistence.tree.reuse.ReuseList;
 import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageLockListener;
 import org.apache.ignite.internal.processors.failure.FailureProcessor;
-import org.apache.ignite.internal.processors.metric.impl.LongAdderMetric;
 import org.apache.ignite.internal.util.GridConcurrentHashSet;
 import org.apache.ignite.internal.util.GridRandom;
 import org.apache.ignite.internal.util.GridStripedLock;
@@ -2930,7 +2930,7 @@ public class BPlusTreeSelfTest extends GridCommonAbstractTest {
             null,
             PAGE_SIZE,
             plcCfg,
-            new LongAdderMetric("NO_OP", null),
+            new DataRegionMetricsImpl(plcCfg, new GridTestKernalContext(log())),
             true);
 
         pageMem.start();
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/database/CacheFreeListSelfTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/database/CacheFreeListSelfTest.java
index 82c54f7..463cc0c 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/processors/database/CacheFreeListSelfTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/database/CacheFreeListSelfTest.java
@@ -52,7 +52,6 @@ import org.apache.ignite.internal.processors.cache.persistence.freelist.FreeList
 import org.apache.ignite.internal.processors.cache.persistence.tree.io.CacheVersionIO;
 import org.apache.ignite.internal.processors.cache.version.GridCacheVersion;
 import org.apache.ignite.internal.processors.metric.GridMetricManager;
-import org.apache.ignite.internal.processors.metric.impl.LongAdderMetric;
 import org.apache.ignite.internal.processors.performancestatistics.PerformanceStatisticsProcessor;
 import org.apache.ignite.plugin.extensions.communication.MessageReader;
 import org.apache.ignite.plugin.extensions.communication.MessageWriter;
@@ -64,8 +63,6 @@ import org.apache.ignite.testframework.junits.logger.GridTestLog4jLogger;
 import org.jetbrains.annotations.Nullable;
 import org.junit.Test;
 
-import static org.apache.ignite.internal.processors.database.DataRegionMetricsSelfTest.NO_OP_METRICS;
-
 /**
  *
  */
@@ -485,13 +482,14 @@ public class CacheFreeListSelfTest extends GridCommonAbstractTest {
     /**
      * @return Page memory.
      */
-    protected PageMemory createPageMemory(int pageSize, DataRegionConfiguration plcCfg) throws Exception {
-        PageMemory pageMem = new PageMemoryNoStoreImpl(log,
+    protected PageMemory createPageMemory(int pageSize, DataRegionConfiguration plcCfg) {
+        PageMemory pageMem = new PageMemoryNoStoreImpl(
+            log,
             new UnsafeMemoryProvider(log),
             null,
             pageSize,
             plcCfg,
-            new LongAdderMetric("NO_OP", null),
+            new DataRegionMetricsImpl(plcCfg, new GridTestKernalContext(log())),
             true);
 
         pageMem.start();
@@ -520,17 +518,13 @@ public class CacheFreeListSelfTest extends GridCommonAbstractTest {
         ctx.add(new GridMetricManager(ctx));
         ctx.add(new PerformanceStatisticsProcessor(ctx));
 
-        DataRegionMetricsImpl regionMetrics = new DataRegionMetricsImpl(plcCfg,
-            ctx.metric(),
-            ctx.performanceStatistics(),
-            NO_OP_METRICS);
+        DataRegionMetricsImpl regionMetrics = new DataRegionMetricsImpl(plcCfg, ctx);
 
         DataRegion dataRegion = new DataRegion(pageMem, plcCfg, regionMetrics, new NoOpPageEvictionTracker());
 
         return new CacheFreeList(
             1,
             "freelist",
-            regionMetrics,
             dataRegion,
             null,
             metaPageId,
@@ -726,7 +720,7 @@ public class CacheFreeListSelfTest extends GridCommonAbstractTest {
         }
 
         /** {@inheritDoc} */
-        @Override public int valueBytesLength(CacheObjectContext ctx) throws IgniteCheckedException {
+        @Override public int valueBytesLength(CacheObjectContext ctx) {
             return data.length;
         }
 
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/database/DataRegionMetricsSelfTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/database/DataRegionMetricsSelfTest.java
index 0d6c25d..b2411ff 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/processors/database/DataRegionMetricsSelfTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/database/DataRegionMetricsSelfTest.java
@@ -19,7 +19,6 @@ package org.apache.ignite.internal.processors.database;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.atomic.AtomicInteger;
 import org.apache.ignite.DataRegionMetrics;
-import org.apache.ignite.DataRegionMetricsProvider;
 import org.apache.ignite.configuration.DataRegionConfiguration;
 import org.apache.ignite.configuration.IgniteConfiguration;
 import org.apache.ignite.internal.processors.cache.persistence.DataRegionMetricsImpl;
@@ -38,19 +37,6 @@ import static java.lang.Thread.sleep;
  *
  */
 public class DataRegionMetricsSelfTest extends GridCommonAbstractTest {
-    /** For test purposes only. */
-    public static final DataRegionMetricsProvider NO_OP_METRICS = new DataRegionMetricsProvider() {
-        /** {@inheritDoc} */
-        @Override public long partiallyFilledPagesFreeSpace() {
-            return 0;
-        }
-
-        /** {@inheritDoc} */
-        @Override public long emptyDataPages() {
-            return 0;
-        }
-    };
-
     /** */
     private DataRegionMetricsImpl memMetrics;
 
@@ -80,7 +66,7 @@ public class DataRegionMetricsSelfTest extends GridCommonAbstractTest {
         ctx.add(new GridMetricManager(ctx));
         ctx.add(new PerformanceStatisticsProcessor(ctx));
 
-        memMetrics = new DataRegionMetricsImpl(plcCfg, ctx.metric(), ctx.performanceStatistics(), NO_OP_METRICS);
+        memMetrics = new DataRegionMetricsImpl(plcCfg, ctx);
 
         memMetrics.enableMetrics();
     }
@@ -316,7 +302,7 @@ public class DataRegionMetricsSelfTest extends GridCommonAbstractTest {
                 startLatch.await();
 
                 for (int i = 0; i < iterationsCnt; i++) {
-                    memMetrics.totalAllocatedPages().increment();
+                    memMetrics.pageMetrics().totalPages().increment();
 
                     sleep(delay);
                 }
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/database/IndexStorageSelfTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/database/IndexStorageSelfTest.java
index 1d34704..442c84a 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/processors/database/IndexStorageSelfTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/database/IndexStorageSelfTest.java
@@ -30,10 +30,11 @@ import org.apache.ignite.internal.pagemem.FullPageId;
 import org.apache.ignite.internal.pagemem.PageIdAllocator;
 import org.apache.ignite.internal.pagemem.PageMemory;
 import org.apache.ignite.internal.pagemem.impl.PageMemoryNoStoreImpl;
+import org.apache.ignite.internal.processors.cache.persistence.DataRegionMetricsImpl;
 import org.apache.ignite.internal.processors.cache.persistence.IndexStorageImpl;
 import org.apache.ignite.internal.processors.cache.persistence.RootPage;
-import org.apache.ignite.internal.processors.metric.impl.LongAdderMetric;
 import org.apache.ignite.internal.util.typedef.internal.U;
+import org.apache.ignite.testframework.junits.GridTestKernalContext;
 import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
 import org.junit.Test;
 
@@ -178,7 +179,7 @@ public class IndexStorageSelfTest extends GridCommonAbstractTest {
             null,
             PAGE_SIZE,
             plcCfg,
-            new LongAdderMetric("NO_OP", null),
+            new DataRegionMetricsImpl(plcCfg, new GridTestKernalContext(log())),
             true);
     }
 }
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/util/collection/IntHashMapTest.java b/modules/core/src/test/java/org/apache/ignite/internal/util/collection/IntHashMapTest.java
index 704ca2d..7aa5b10 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/util/collection/IntHashMapTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/util/collection/IntHashMapTest.java
@@ -17,16 +17,20 @@
 
 package org.apache.ignite.internal.util.collection;
 
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.stream.IntStream;
 import org.apache.ignite.internal.util.typedef.internal.U;
-import org.junit.Assert;
 import org.junit.Test;
 
 import static org.apache.ignite.internal.util.collection.IntHashMap.INITIAL_CAPACITY;
 import static org.apache.ignite.internal.util.collection.IntHashMap.MAXIMUM_CAPACITY;
 import static org.apache.ignite.testframework.GridTestUtils.assertThrows;
+import static org.hamcrest.Matchers.arrayContainingInAnyOrder;
+import static org.hamcrest.Matchers.is;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
 
 /**
  * Test for the specific implementation of IntMap.
@@ -40,14 +44,11 @@ public class IntHashMapTest extends AbstractBaseIntMapTest {
     /** */
     @Test
     public void removeBackShift() {
-        HashMap<Integer, Integer> bijection = new HashMap<>();
-        bijection.put(1, 14);
-        bijection.put(2, 14);
-        bijection.put(3, 14);
-        bijection.put(4, 14);
-        bijection.put(5, 14);
-
-        IntMap<String> directPositionMap = bijectionHashFunctionMap(bijection);
+        IntHashMap<String> directPositionMap = new IntHashMap<String>() {
+            @Override protected int index(int key) {
+                return 14;
+            }
+        };
 
         directPositionMap.put(1, value(1));
         directPositionMap.put(2, value(2));
@@ -57,24 +58,17 @@ public class IntHashMapTest extends AbstractBaseIntMapTest {
 
         directPositionMap.remove(1);
 
-        Assert.assertEquals(4, directPositionMap.size());
+        assertEquals(4, directPositionMap.size());
     }
 
     /** */
     @Test
     public void distance() {
-        HashMap<Integer, Integer> bijection = new HashMap<>();
-        bijection.put(1, 14);
-        bijection.put(2, 14);
-        bijection.put(3, 14);
-        bijection.put(4, 14);
-        bijection.put(5, 14);
-        bijection.put(6, 14);
-        bijection.put(7, 14);
-        bijection.put(8, 14);
-        bijection.put(9, 14);
-
-        IntHashMap<String> directPositionMap = (IntHashMap<String>)bijectionHashFunctionMap(bijection);
+        IntHashMap<String> directPositionMap = new IntHashMap<String>() {
+            @Override protected int index(int key) {
+                return 14;
+            }
+        };
 
         directPositionMap.put(1, value(1));
         directPositionMap.put(2, value(2));
@@ -142,20 +136,28 @@ public class IntHashMapTest extends AbstractBaseIntMapTest {
     }
 
     /**
-     * @param initSize Initial size.
+     * Tests the copy constructor.
      */
-    private int realCapacityForInitialSize(int initSize) {
-        return ((Object[]) U.field(new IntHashMap<String>(initSize), "entries")).length;
+    @Test
+    public void testCopyConstructor() {
+        IntMap<String> expected = new IntHashMap<>();
+
+        IntStream.range(0, 10).forEach(i -> expected.put(i, String.valueOf(i)));
+
+        IntMap<Object> actual = new IntHashMap<>(expected);
+
+        Object[] expectedKeys = Arrays.stream(expected.keys()).boxed().toArray();
+        Object[] actualKeys = Arrays.stream(actual.keys()).boxed().toArray();
+
+        assertThat(actualKeys, arrayContainingInAnyOrder(expectedKeys));
+
+        expected.forEach((key, expectedVal) -> assertThat(actual.get(key), is((Object) expectedVal)));
     }
 
     /**
-     * @param bijection Bijection.
+     * @param initSize Initial size.
      */
-    private IntMap<String> bijectionHashFunctionMap(Map<Integer, Integer> bijection) {
-        return new IntHashMap<String>() {
-            @Override protected int index(int key) {
-                return bijection.get(key);
-            }
-        };
+    private static int realCapacityForInitialSize(int initSize) {
+        return ((Object[]) U.field(new IntHashMap<String>(initSize), "entries")).length;
     }
 }
diff --git a/modules/indexing/pom.xml b/modules/indexing/pom.xml
index a88df81..8b3fd1c 100644
--- a/modules/indexing/pom.xml
+++ b/modules/indexing/pom.xml
@@ -141,6 +141,13 @@
             <scope>test</scope>
         </dependency>
 
+        <dependency>
+            <groupId>org.hamcrest</groupId>
+            <artifactId>hamcrest-library</artifactId>
+            <version>1.3</version>
+            <scope>test</scope>
+        </dependency>
+
     </dependencies>
 
     <build>
diff --git a/modules/indexing/src/test/java/org/apache/ignite/internal/metric/AbstractIndexPageMetricsTest.java b/modules/indexing/src/test/java/org/apache/ignite/internal/metric/AbstractIndexPageMetricsTest.java
new file mode 100644
index 0000000..a1aae5b
--- /dev/null
+++ b/modules/indexing/src/test/java/org/apache/ignite/internal/metric/AbstractIndexPageMetricsTest.java
@@ -0,0 +1,207 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.metric;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import org.apache.ignite.IgniteCache;
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.client.Person;
+import org.apache.ignite.cluster.ClusterState;
+import org.apache.ignite.configuration.CacheConfiguration;
+import org.apache.ignite.configuration.DataRegionConfiguration;
+import org.apache.ignite.configuration.DataStorageConfiguration;
+import org.apache.ignite.configuration.IgniteConfiguration;
+import org.apache.ignite.failure.StopNodeFailureHandler;
+import org.apache.ignite.internal.IgniteEx;
+import org.apache.ignite.internal.processors.cache.GridCacheProcessor;
+import org.apache.ignite.internal.processors.cache.persistence.DataRegion;
+import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+/**
+ * Base class for testing index pages metrics.
+ */
+@RunWith(Parameterized.class)
+public abstract class AbstractIndexPageMetricsTest extends GridCommonAbstractTest {
+    /** */
+    @Parameterized.Parameters(name = "numCaches = {0}")
+    public static Object[] data() {
+        return new Object[] { 1, 3 };
+    }
+
+    /** */
+    private IgniteEx grid;
+
+    /** */
+    private List<IgniteCache<Integer, Person>> caches;
+
+    /** */
+    IndexPageCounter indexPageCounter;
+
+    /**
+     * Number of caches that will be created on the test Ignite node.
+     */
+    @Parameterized.Parameter
+    public int numCaches;
+
+    /** {@inheritDoc} */
+    @Override protected void beforeTest() throws Exception {
+        super.beforeTest();
+
+        grid = startGrid();
+        grid.cluster().state(ClusterState.ACTIVE);
+
+        caches = IntStream.range(0, numCaches)
+            .mapToObj(i -> "cache " + i)
+            .map(this::createCache)
+            .collect(Collectors.toList());
+
+        indexPageCounter = new IndexPageCounter(grid, isPersistenceEnabled());
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void afterTest() throws Exception {
+        super.afterTest();
+
+        grid.close();
+    }
+
+    /** {@inheritDoc} */
+    @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception {
+        return super.getConfiguration(igniteInstanceName)
+            .setFailureHandler(new StopNodeFailureHandler())
+            .setDataStorageConfiguration(
+                new DataStorageConfiguration()
+                    .setDefaultDataRegionConfiguration(
+                        new DataRegionConfiguration()
+                            .setPersistenceEnabled(isPersistenceEnabled())
+                    )
+            );
+    }
+
+    /**
+     * Overriding classes should return {@code true} if the test Ignite node should be launched with enabled
+     * persistence.
+     */
+    abstract boolean isPersistenceEnabled();
+
+    /**
+     * Validates that index page metrics correctly represent the number of in-memory index pages.
+     * <p>
+     * Implementation should use the {@link #indexPageCounter} to obtain the actual number of index pages.
+     */
+    abstract void validateIdxPagesCnt() throws IgniteCheckedException;
+
+    /**
+     * Inserts some data into the test caches and then removes all data.
+     */
+    @Test
+    public void testStoreAndDeleteEntries() throws Exception {
+        validateIdxPagesCnt();
+
+        fillData(1);
+
+        validateIdxPagesCnt();
+
+        fillData(100);
+
+        validateIdxPagesCnt();
+
+        clearData();
+
+        validateIdxPagesCnt();
+    }
+
+    /**
+     * Populates the test caches and then destroys a cache.
+     */
+    @Test
+    public void testStoreAndDeleteCacheGrp() throws Exception {
+        fillData(100);
+
+        validateIdxPagesCnt();
+
+        caches.get(0).destroy();
+
+        validateIdxPagesCnt();
+    }
+
+    /**
+     * Populates the test caches and then removes <i>some</i> data from them.
+     * <p>
+     * This test case verifies that index metrics are still correct even if some index pages get merged.
+     */
+    @Test
+    public void testStoreAndRemoveSomeEntries() throws Exception {
+        fillData(100);
+
+        validateIdxPagesCnt();
+
+        IntStream.range(0, 50)
+            .map(i -> (int) (Math.random() * 100))
+            .forEach(caches.get(0)::remove);
+
+        validateIdxPagesCnt();
+    }
+
+    /**
+     * Returns the {@link GridCacheProcessor} of the test Ignite node.
+     */
+    GridCacheProcessor gridCacheProcessor() {
+        return grid.context().cache();
+    }
+
+    /**
+     * Returns the default Data Region of the test Ignite node.
+     */
+    DataRegion defaultDataRegion() throws IgniteCheckedException {
+        return Objects.requireNonNull(
+            gridCacheProcessor().context().database().dataRegion(null)
+        );
+    }
+
+    /**
+     * Creates a test cache with the given name.
+     */
+    private IgniteCache<Integer, Person> createCache(String cacheName) {
+        CacheConfiguration<Integer, Person> cacheConfiguration = new CacheConfiguration<Integer, Person>(cacheName)
+            .setIndexedTypes(Integer.class, Person.class);
+        return grid.createCache(cacheConfiguration);
+    }
+
+    /**
+     * Inserts {@code numEntries} objects into every test cache.
+     */
+    private void fillData(int numEntries) {
+        IntStream.range(0, numEntries)
+            .mapToObj(i -> new Person(i, "foobar"))
+            .forEach(person -> caches.forEach(cache -> cache.put(person.getId(), person)));
+    }
+
+    /**
+     * Removes all data from all test caches.
+     */
+    private void clearData() {
+        caches.forEach(IgniteCache::clear);
+    }
+}
diff --git a/modules/indexing/src/test/java/org/apache/ignite/internal/metric/IndexPageCounter.java b/modules/indexing/src/test/java/org/apache/ignite/internal/metric/IndexPageCounter.java
new file mode 100644
index 0000000..70dd065
--- /dev/null
+++ b/modules/indexing/src/test/java/org/apache/ignite/internal/metric/IndexPageCounter.java
@@ -0,0 +1,102 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.metric;
+
+import java.util.HashMap;
+import java.util.Map;
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.internal.IgniteEx;
+import org.apache.ignite.internal.pagemem.FullPageId;
+import org.apache.ignite.internal.pagemem.PageIdAllocator;
+import org.apache.ignite.internal.pagemem.PageIdUtils;
+import org.apache.ignite.internal.pagemem.PageMemory;
+import org.apache.ignite.internal.processors.cache.persistence.DataRegion;
+import org.apache.ignite.internal.processors.cache.persistence.pagemem.PageMemoryImpl;
+import org.apache.ignite.internal.processors.cache.persistence.tree.io.PageIO;
+
+/**
+ * Helper class for counting index pages.
+ */
+class IndexPageCounter {
+    /** Ignite node. */
+    private final IgniteEx grid;
+
+    /** {@code true} if the corresponding Ignite node is started with enabled persistence. */
+    private final boolean persistenceEnabled;
+
+    /**
+     * Mapping from a page ID to the rotation id of the same page.
+     * <p>
+     * Needed to detect if a page got moved to a free list after some data got removed.
+     */
+    private final Map<Long, Long> pageIdToRotationId = new HashMap<>();
+
+    /**
+     * @param grid Grid.
+     * @param persistenceEnabled Persistence enabled.
+     */
+    IndexPageCounter(IgniteEx grid, boolean persistenceEnabled) {
+        this.grid = grid;
+        this.persistenceEnabled = persistenceEnabled;
+    }
+
+    /**
+     * Returns the number of index pages residing inside the Page Memory of the given cache group.
+     */
+    long countIdxPagesInMemory(int grpId) throws IgniteCheckedException {
+        DataRegion dataRegion = grid.context().cache().context().database().dataRegion(null);
+        PageMemory pageMemory = dataRegion.pageMemory();
+
+        long idxPageCnt = 0;
+
+        for (int i = 0; i < pageMemory.loadedPages(); i++) {
+            long pageId = PageIdUtils.pageId(PageIdAllocator.INDEX_PARTITION, (byte)0, i);
+
+            if (persistenceEnabled) {
+                // if persistence is enabled, avoid loading a displaced page into memory
+                PageMemoryImpl pageMemoryImpl = (PageMemoryImpl) dataRegion.pageMemory();
+
+                if (!pageMemoryImpl.hasLoadedPage(new FullPageId(pageId, grpId)))
+                    continue;
+            }
+
+            long pageAddr = pageMemory.acquirePage(grpId, pageId);
+
+            try {
+                long pageReadAddr = pageMemory.readLockForce(grpId, pageId, pageAddr);
+
+                try {
+                    // check the rotation ID in case a page was freed (its page type does not get overwritten)
+                    long rotationId = PageIdUtils.rotationId(PageIO.getPageId(pageReadAddr));
+                    long prevRotationId = pageIdToRotationId.computeIfAbsent(pageId, id -> rotationId);
+
+                    if (prevRotationId == rotationId && PageIO.isIndexPage(PageIO.getType(pageReadAddr)))
+                        idxPageCnt += 1;
+                }
+                finally {
+                    pageMemory.readUnlock(grpId, pageId, pageAddr);
+                }
+            }
+            finally {
+                pageMemory.releasePage(grpId, pageId, pageAddr);
+            }
+        }
+
+        return idxPageCnt;
+    }
+}
diff --git a/modules/indexing/src/test/java/org/apache/ignite/internal/metric/IndexPagesMetricsInMemoryTest.java b/modules/indexing/src/test/java/org/apache/ignite/internal/metric/IndexPagesMetricsInMemoryTest.java
new file mode 100644
index 0000000..06ca0a6
--- /dev/null
+++ b/modules/indexing/src/test/java/org/apache/ignite/internal/metric/IndexPagesMetricsInMemoryTest.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.metric;
+
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.internal.processors.cache.persistence.DataRegion;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+public class IndexPagesMetricsInMemoryTest extends AbstractIndexPageMetricsTest {
+    /** {@inheritDoc} */
+    @Override boolean isPersistenceEnabled() {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override void validateIdxPagesCnt() throws IgniteCheckedException {
+        DataRegion dataRegion = defaultDataRegion();
+
+        long actualIdxPages = gridCacheProcessor().caches().stream()
+            .mapToInt(cache -> cache.context().groupId())
+            .mapToObj(dataRegion.metrics()::cacheGrpPageMetrics)
+            .mapToLong(metrics -> metrics.indexPages().value())
+            .sum();
+
+        long expIdxPages = indexPageCounter.countIdxPagesInMemory(0);
+
+        assertThat(actualIdxPages, is(expIdxPages));
+        assertThat(dataRegion.metrics().pageMetrics().indexPages().value(), is(expIdxPages));
+    }
+}
diff --git a/modules/indexing/src/test/java/org/apache/ignite/internal/metric/IndexPagesMetricsPageDisplacementTest.java b/modules/indexing/src/test/java/org/apache/ignite/internal/metric/IndexPagesMetricsPageDisplacementTest.java
new file mode 100644
index 0000000..04efe0d6
--- /dev/null
+++ b/modules/indexing/src/test/java/org/apache/ignite/internal/metric/IndexPagesMetricsPageDisplacementTest.java
@@ -0,0 +1,194 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.metric;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.ignite.IgniteCache;
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.client.Person;
+import org.apache.ignite.cluster.ClusterState;
+import org.apache.ignite.configuration.CacheConfiguration;
+import org.apache.ignite.configuration.DataRegionConfiguration;
+import org.apache.ignite.configuration.DataStorageConfiguration;
+import org.apache.ignite.configuration.IgniteConfiguration;
+import org.apache.ignite.configuration.PageReplacementMode;
+import org.apache.ignite.failure.StopNodeFailureHandler;
+import org.apache.ignite.internal.IgniteEx;
+import org.apache.ignite.internal.pagemem.PageIdAllocator;
+import org.apache.ignite.internal.pagemem.PageIdUtils;
+import org.apache.ignite.internal.pagemem.PageMemory;
+import org.apache.ignite.internal.processors.cache.persistence.DataRegion;
+import org.apache.ignite.internal.processors.cache.persistence.file.FilePageStore;
+import org.apache.ignite.internal.processors.cache.persistence.file.FilePageStoreManager;
+import org.apache.ignite.internal.processors.cache.persistence.pagemem.PageMetrics;
+import org.apache.ignite.internal.processors.cache.persistence.tree.io.PageIO;
+import org.apache.ignite.internal.processors.cache.persistence.wal.crc.IgniteDataIntegrityViolationException;
+import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.Matchers.greaterThan;
+import static org.junit.Assert.assertThat;
+
+/**
+ * Test class for checking that index page metrics stay correct in case of page displacement.
+ */
+public class IndexPagesMetricsPageDisplacementTest extends GridCommonAbstractTest {
+    /** */
+    private static final String TEST_CACHE_NAME = "test";
+
+    /** */
+    private IgniteEx grid;
+
+    /** */
+    private IgniteCache<Integer, Person> cache;
+
+    /** */
+    private IndexPageCounter idxPageCounter;
+
+    /** {@inheritDoc} */
+    @Override protected void beforeTest() throws Exception {
+        cleanPersistenceDir();
+
+        super.beforeTest();
+
+        grid = startGrid();
+        grid.cluster().state(ClusterState.ACTIVE);
+
+        CacheConfiguration<Integer, Person> cacheConfiguration = new CacheConfiguration<Integer, Person>(TEST_CACHE_NAME)
+            .setIndexedTypes(Integer.class, Person.class);
+        cache = grid.createCache(cacheConfiguration);
+
+        idxPageCounter = new IndexPageCounter(grid, true);
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void afterTest() throws Exception {
+        super.afterTest();
+
+        grid.close();
+
+        cleanPersistenceDir();
+    }
+
+    /** {@inheritDoc} */
+    @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception {
+        return super.getConfiguration(igniteInstanceName)
+            .setFailureHandler(new StopNodeFailureHandler())
+            .setDataStorageConfiguration(
+                new DataStorageConfiguration()
+                    .setDefaultDataRegionConfiguration(
+                        new DataRegionConfiguration()
+                            .setMaxSize(12 * 1024 * 1024) // 12 MB
+                            .setPersistenceEnabled(true)
+                            .setPageReplacementMode(PageReplacementMode.RANDOM_LRU)
+                            .setEvictionThreshold(0.1)
+                    )
+            );
+    }
+
+    /**
+     * Inserts data into the cache as long as it fills enough for some data pages to get dumped to the
+     * storage. Since index page metrics should reflect the number of in-memory pages, the metric value is expected to
+     * go down.
+     */
+    @Test
+    public void testPageDisplacement() throws IgniteCheckedException {
+        int grpId = grid.cachex(TEST_CACHE_NAME).context().groupId();
+
+        DataRegion dataRegion = grid.context().cache().context().database().dataRegion(null);
+        PageMetrics pageMetrics = dataRegion.metrics().cacheGrpPageMetrics(grpId);
+
+        int personId = 0;
+        long idxPagesOnDisk;
+        long idxPagesInMemory;
+
+        do {
+            // insert data into the cache until some index pages get displaced to the storage
+            for (int i = 0; i < 100; i++, personId++)
+                cache.put(personId, new Person(personId, "foobar"));
+
+            forceCheckpoint(grid);
+
+            idxPagesOnDisk = getIdxPagesOnDisk(grpId).size();
+            idxPagesInMemory = idxPageCounter.countIdxPagesInMemory(grpId);
+
+            assertThat(pageMetrics.indexPages().value(), is(idxPagesInMemory));
+
+        } while (idxPagesOnDisk <= idxPagesInMemory);
+
+        // load pages back into memory and check that the metric value has increased
+        touchIdxPages(grpId);
+
+        long allIdxPagesInMemory = idxPageCounter.countIdxPagesInMemory(grpId);
+
+        assertThat(allIdxPagesInMemory, greaterThan(idxPagesInMemory));
+        assertThat(pageMetrics.indexPages().value(), is(allIdxPagesInMemory));
+    }
+
+    /**
+     * Acquires all pages inside the index partition for the given cache group in order to load them back into
+     * memory if some of them had been displaced to the storage earlier.
+     */
+    private void touchIdxPages(int grpId) throws IgniteCheckedException {
+        DataRegion dataRegion = grid.context().cache().context().database().dataRegion(null);
+        PageMemory pageMemory = dataRegion.pageMemory();
+
+        for (long pageId : getIdxPagesOnDisk(grpId))
+            // acquire, but not release all pages, so that they don't get displaced back to the storage
+            pageMemory.acquirePage(grpId, pageId);
+    }
+
+    /**
+     * Returns IDs of index pages currently residing in the storage.
+     */
+    private List<Long> getIdxPagesOnDisk(int grpId) throws IgniteCheckedException {
+        FilePageStoreManager pageStoreMgr = (FilePageStoreManager) grid.context().cache().context().pageStore();
+        FilePageStore pageStore = (FilePageStore) pageStoreMgr.getStore(grpId, PageIdAllocator.INDEX_PARTITION);
+
+        List<Long> result = new ArrayList<>();
+
+        ByteBuffer buf = ByteBuffer
+            .allocateDirect(pageStore.getPageSize())
+            .order(ByteOrder.nativeOrder());
+
+        // Page Store contains a one-page header
+        long numPages = pageStore.size() / pageStore.getPageSize() - 1;
+
+        for (int i = 0; i < numPages; i++) {
+            long pageId = PageIdUtils.pageId(PageIdAllocator.INDEX_PARTITION, (byte)0, i);
+
+            try {
+                pageStore.read(pageId, buf, false);
+            } catch (IgniteDataIntegrityViolationException ignored) {
+                // sometimes we try to access an invalid page, in which case this exception will be thrown.
+                // We simply ignore it and try to access other pages.
+            }
+
+            if (PageIO.isIndexPage(PageIO.getType(buf)))
+                result.add(PageIO.getPageId(buf));
+
+            buf.clear();
+        }
+
+        return result;
+    }
+}
diff --git a/modules/indexing/src/test/java/org/apache/ignite/internal/metric/IndexPagesMetricsPersistentTest.java b/modules/indexing/src/test/java/org/apache/ignite/internal/metric/IndexPagesMetricsPersistentTest.java
new file mode 100644
index 0000000..e8d7aa7
--- /dev/null
+++ b/modules/indexing/src/test/java/org/apache/ignite/internal/metric/IndexPagesMetricsPersistentTest.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.metric;
+
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.internal.processors.cache.IgniteInternalCache;
+import org.apache.ignite.internal.processors.cache.persistence.DataRegion;
+import org.apache.ignite.internal.processors.cache.persistence.DataRegionMetricsImpl;
+import org.apache.ignite.internal.processors.cache.persistence.pagemem.PageMetrics;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+public class IndexPagesMetricsPersistentTest extends AbstractIndexPageMetricsTest {
+    /** {@inheritDoc} */
+    @Override boolean isPersistenceEnabled() {
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void beforeTest() throws Exception {
+        cleanPersistenceDir();
+
+        super.beforeTest();
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void afterTest() throws Exception {
+        super.afterTest();
+
+        cleanPersistenceDir();
+    }
+
+    /** {@inheritDoc} */
+    @Override void validateIdxPagesCnt() throws IgniteCheckedException {
+        DataRegion dataRegion = defaultDataRegion();
+        DataRegionMetricsImpl dataRegionMetrics = dataRegion.metrics();
+
+        long totalIdxPages = 0;
+
+        for (IgniteInternalCache<?, ?> cache : gridCacheProcessor().caches()) {
+            int grpId = cache.context().groupId();
+
+            long idxPages = indexPageCounter.countIdxPagesInMemory(grpId);
+
+            PageMetrics metrics = dataRegionMetrics.cacheGrpPageMetrics(grpId);
+
+            assertThat(metrics.indexPages().value(), is(idxPages));
+
+            totalIdxPages += idxPages;
+        }
+
+        assertThat(dataRegionMetrics.pageMetrics().indexPages().value(), is(totalIdxPages));
+    }
+}
diff --git a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/query/h2/database/inlinecolumn/InlineIndexColumnTest.java b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/query/h2/database/inlinecolumn/InlineIndexColumnTest.java
index 3223f33..794102f 100644
--- a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/query/h2/database/inlinecolumn/InlineIndexColumnTest.java
+++ b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/query/h2/database/inlinecolumn/InlineIndexColumnTest.java
@@ -18,6 +18,7 @@
 package org.apache.ignite.internal.processors.query.h2.database.inlinecolumn;
 
 import java.io.Serializable;
+import java.nio.charset.StandardCharsets;
 import java.sql.Date;
 import java.sql.Time;
 import java.sql.Timestamp;
@@ -25,7 +26,6 @@ import java.util.Arrays;
 import java.util.TimeZone;
 import java.util.UUID;
 import java.util.concurrent.ThreadLocalRandom;
-import org.apache.commons.io.Charsets;
 import org.apache.ignite.IgniteCheckedException;
 import org.apache.ignite.configuration.DataRegionConfiguration;
 import org.apache.ignite.internal.cache.query.index.IndexProcessor;
@@ -43,9 +43,10 @@ import org.apache.ignite.internal.pagemem.PageMemory;
 import org.apache.ignite.internal.pagemem.impl.PageMemoryNoStoreImpl;
 import org.apache.ignite.internal.processors.cache.CacheObjectValueContext;
 import org.apache.ignite.internal.processors.cache.index.AbstractIndexingCommonTest;
-import org.apache.ignite.internal.processors.metric.impl.LongAdderMetric;
+import org.apache.ignite.internal.processors.cache.persistence.DataRegionMetricsImpl;
 import org.apache.ignite.internal.processors.query.h2.opt.GridH2ValueCacheObject;
 import org.apache.ignite.testframework.junits.GridTestBinaryMarshaller;
+import org.apache.ignite.testframework.junits.GridTestKernalContext;
 import org.apache.ignite.testframework.junits.WithSystemProperty;
 import org.h2.api.JavaObjectSerializer;
 import org.h2.util.DateTimeUtils;
@@ -117,13 +118,13 @@ public class InlineIndexColumnTest extends AbstractIndexingCommonTest {
     @Test
     public void testConvert() {
         // 8 bytes total: 1b, 1b, 3b, 3b.
-        byte[] bytes = StringInlineIndexKeyType.trimUTF8("00\u20ac\u20ac".getBytes(Charsets.UTF_8), 7);
+        byte[] bytes = StringInlineIndexKeyType.trimUTF8("00\u20ac\u20ac".getBytes(StandardCharsets.UTF_8), 7);
         assertEquals(5, bytes.length);
 
         String s = new String(bytes);
         assertEquals(3, s.length());
 
-        bytes = StringInlineIndexKeyType.trimUTF8("aaaaaa".getBytes(Charsets.UTF_8), 4);
+        bytes = StringInlineIndexKeyType.trimUTF8("aaaaaa".getBytes(StandardCharsets.UTF_8), 4);
         assertEquals(4, bytes.length);
     }
 
@@ -249,16 +250,21 @@ public class InlineIndexColumnTest extends AbstractIndexingCommonTest {
      * @throws Exception If failed.
      */
     private <T> int putAndCompare(T v1, T v2, Class<T> cls, int maxSize) throws Exception {
-        DataRegionConfiguration plcCfg = new DataRegionConfiguration().setInitialSize(1024 * MB)
+        DataRegionConfiguration plcCfg = new DataRegionConfiguration()
+            .setInitialSize(1024 * MB)
             .setMaxSize(1024 * MB);
 
-        PageMemory pageMem = new PageMemoryNoStoreImpl(log,
+        DataRegionMetricsImpl dataRegionMetrics = new DataRegionMetricsImpl(plcCfg, new GridTestKernalContext(log()));
+
+        PageMemory pageMem = new PageMemoryNoStoreImpl(
+            log,
             new UnsafeMemoryProvider(log),
             null,
             PAGE_SIZE,
             plcCfg,
-            new LongAdderMetric("NO_OP", null),
-            false);
+            dataRegionMetrics,
+            false
+        );
 
         pageMem.start();
 
@@ -309,7 +315,7 @@ public class InlineIndexColumnTest extends AbstractIndexingCommonTest {
     @Test
     public void testStringCut() {
         // 6 bytes total: 3b, 3b.
-        byte[] bytes = StringInlineIndexKeyType.trimUTF8("\u20ac\u20ac".getBytes(Charsets.UTF_8), 2);
+        byte[] bytes = StringInlineIndexKeyType.trimUTF8("\u20ac\u20ac".getBytes(StandardCharsets.UTF_8), 2);
 
         assertNull(bytes);
     }
@@ -321,13 +327,15 @@ public class InlineIndexColumnTest extends AbstractIndexingCommonTest {
         DataRegionConfiguration plcCfg = new DataRegionConfiguration().setInitialSize(1024 * MB)
             .setMaxSize(1024 * MB);
 
-        PageMemory pageMem = new PageMemoryNoStoreImpl(log(),
-            new UnsafeMemoryProvider(log()),
+        PageMemory pageMem = new PageMemoryNoStoreImpl(
+            log,
+            new UnsafeMemoryProvider(log),
             null,
             PAGE_SIZE,
             plcCfg,
-            new LongAdderMetric("NO_OP", null),
-            false);
+            new DataRegionMetricsImpl(plcCfg, new GridTestKernalContext(log())),
+            false
+        );
 
         pageMem.start();
 
@@ -372,13 +380,15 @@ public class InlineIndexColumnTest extends AbstractIndexingCommonTest {
         DataRegionConfiguration plcCfg = new DataRegionConfiguration().setInitialSize(1024 * MB)
             .setMaxSize(1024 * MB);
 
-        PageMemory pageMem = new PageMemoryNoStoreImpl(log(),
-            new UnsafeMemoryProvider(log()),
+        PageMemory pageMem = new PageMemoryNoStoreImpl(
+            log,
+            new UnsafeMemoryProvider(log),
             null,
             PAGE_SIZE,
             plcCfg,
-            new LongAdderMetric("NO_OP", null),
-            false);
+            new DataRegionMetricsImpl(plcCfg, new GridTestKernalContext(log())),
+            false
+        );
 
         pageMem.start();
 
@@ -432,13 +442,17 @@ public class InlineIndexColumnTest extends AbstractIndexingCommonTest {
         DataRegionConfiguration plcCfg = new DataRegionConfiguration().setInitialSize(1024 * MB)
             .setMaxSize(1024 * MB);
 
-        PageMemory pageMem = new PageMemoryNoStoreImpl(log(),
-            new UnsafeMemoryProvider(log()),
+        DataRegionMetricsImpl dataRegionMetrics = new DataRegionMetricsImpl(plcCfg, new GridTestKernalContext(log()));
+
+        PageMemory pageMem = new PageMemoryNoStoreImpl(
+            log,
+            new UnsafeMemoryProvider(log),
             null,
             PAGE_SIZE,
             plcCfg,
-            new LongAdderMetric("NO_OP", null),
-            false);
+            dataRegionMetrics,
+            false
+        );
 
         pageMem.start();
 
@@ -496,13 +510,15 @@ public class InlineIndexColumnTest extends AbstractIndexingCommonTest {
         DataRegionConfiguration plcCfg = new DataRegionConfiguration().setInitialSize(1024 * MB)
             .setMaxSize(1024 * MB);
 
-        PageMemory pageMem = new PageMemoryNoStoreImpl(log(),
-            new UnsafeMemoryProvider(log()),
+        PageMemory pageMem = new PageMemoryNoStoreImpl(
+            log,
+            new UnsafeMemoryProvider(log),
             null,
             PAGE_SIZE,
             plcCfg,
-            new LongAdderMetric("NO_OP", null),
-            false);
+            new DataRegionMetricsImpl(plcCfg, new GridTestKernalContext(log())),
+            false
+        );
 
         pageMem.start();
 
@@ -847,13 +863,15 @@ public class InlineIndexColumnTest extends AbstractIndexingCommonTest {
         DataRegionConfiguration plcCfg = new DataRegionConfiguration().setInitialSize(1024 * MB)
             .setMaxSize(1024 * MB);
 
-        PageMemory pageMem = new PageMemoryNoStoreImpl(log(),
-            new UnsafeMemoryProvider(log()),
+        PageMemory pageMem = new PageMemoryNoStoreImpl(
+            log,
+            new UnsafeMemoryProvider(log),
             null,
             PAGE_SIZE,
             plcCfg,
-            new LongAdderMetric("NO_OP", null),
-            false);
+            new DataRegionMetricsImpl(plcCfg, new GridTestKernalContext(log())),
+            false
+        );
 
         pageMem.start();
 
diff --git a/modules/indexing/src/test/java/org/apache/ignite/testsuites/IgniteCacheWithIndexingAndPersistenceTestSuite.java b/modules/indexing/src/test/java/org/apache/ignite/testsuites/IgniteCacheWithIndexingAndPersistenceTestSuite.java
index 060759d8..616e516 100644
--- a/modules/indexing/src/test/java/org/apache/ignite/testsuites/IgniteCacheWithIndexingAndPersistenceTestSuite.java
+++ b/modules/indexing/src/test/java/org/apache/ignite/testsuites/IgniteCacheWithIndexingAndPersistenceTestSuite.java
@@ -17,6 +17,8 @@
 
 package org.apache.ignite.testsuites;
 
+import org.apache.ignite.internal.metric.IndexPagesMetricsPageDisplacementTest;
+import org.apache.ignite.internal.metric.IndexPagesMetricsPersistentTest;
 import org.apache.ignite.internal.processors.cache.StartCachesInParallelTest;
 import org.apache.ignite.internal.processors.cache.index.IoStatisticsBasicIndexSelfTest;
 import org.apache.ignite.internal.processors.query.CleanupIndexTreeCheckpointFailoverTest;
@@ -30,7 +32,9 @@ import org.junit.runners.Suite;
 @Suite.SuiteClasses({
     StartCachesInParallelTest.class,
     IoStatisticsBasicIndexSelfTest.class,
-    CleanupIndexTreeCheckpointFailoverTest.class
+    CleanupIndexTreeCheckpointFailoverTest.class,
+    IndexPagesMetricsPersistentTest.class,
+    IndexPagesMetricsPageDisplacementTest.class
 })
 public class IgniteCacheWithIndexingAndPersistenceTestSuite {
 }
diff --git a/modules/indexing/src/test/java/org/apache/ignite/testsuites/IgniteCacheWithIndexingTestSuite.java b/modules/indexing/src/test/java/org/apache/ignite/testsuites/IgniteCacheWithIndexingTestSuite.java
index 110e5d2..813a612 100644
--- a/modules/indexing/src/test/java/org/apache/ignite/testsuites/IgniteCacheWithIndexingTestSuite.java
+++ b/modules/indexing/src/test/java/org/apache/ignite/testsuites/IgniteCacheWithIndexingTestSuite.java
@@ -17,6 +17,7 @@
 
 package org.apache.ignite.testsuites;
 
+import org.apache.ignite.internal.metric.IndexPagesMetricsInMemoryTest;
 import org.apache.ignite.internal.processors.cache.BinaryTypeMismatchLoggingTest;
 import org.apache.ignite.internal.processors.cache.BinaryTypeRegistrationTest;
 import org.apache.ignite.internal.processors.cache.CacheBinaryKeyConcurrentQueryTest;
@@ -112,7 +113,9 @@ import org.junit.runners.Suite;
 
     H2TreeCorruptedTreeExceptionTest.class,
 
-    WrongIndexedTypesTest.class
+    WrongIndexedTypesTest.class,
+
+    IndexPagesMetricsInMemoryTest.class
 })
 public class IgniteCacheWithIndexingTestSuite {
 }
diff --git a/modules/platforms/cpp/examples/cluster-compute-example/CMakeLists.txt b/modules/platforms/cpp/examples/cluster-compute-example/CMakeLists.txt
index 22554f1..281cfd5 100644
--- a/modules/platforms/cpp/examples/cluster-compute-example/CMakeLists.txt
+++ b/modules/platforms/cpp/examples/cluster-compute-example/CMakeLists.txt
@@ -1,11 +1,12 @@
 #
-# Copyright 2019 GridGain Systems, Inc. and Contributors.
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
 #
-# Licensed under the GridGain Community Edition License (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+#      http://www.apache.org/licenses/LICENSE-2.0
 #
 # Unless required by applicable law or agreed to in writing, software
 # distributed under the License is distributed on an "AS IS" BASIS,
diff --git a/modules/yardstick/src/main/java/org/apache/ignite/yardstick/cache/Loader.java b/modules/yardstick/src/main/java/org/apache/ignite/yardstick/cache/Loader.java
index b11ec34..467e9a5 100644
--- a/modules/yardstick/src/main/java/org/apache/ignite/yardstick/cache/Loader.java
+++ b/modules/yardstick/src/main/java/org/apache/ignite/yardstick/cache/Loader.java
@@ -121,7 +121,7 @@ public class Loader implements IgniteClosure<Integer, Integer> {
 
         try {
             final DataRegionMetricsImpl impl = igniteEx.context().cache().context().database().dataRegion(dataRegName)
-                .memoryMetrics();
+                .metrics();
 
             impl.enableMetrics();