You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by al...@apache.org on 2019/05/13 07:53:39 UTC

[ignite] branch master updated: IGNITE-11367 Fixed several issues in PageMemoryTracker - Fixes #6300.

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

alexpl 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 9e5732a  IGNITE-11367 Fixed several issues in PageMemoryTracker - Fixes #6300.
9e5732a is described below

commit 9e5732a3ee986d833a3c1c41bd06d0f7316a2798
Author: Aleksey Plekhanov <pl...@gmail.com>
AuthorDate: Mon May 13 10:53:02 2019 +0300

    IGNITE-11367 Fixed several issues in PageMemoryTracker - Fixes #6300.
---
 .../persistence/freelist/io/PagesListNodeIO.java   |  24 ++-
 .../wal/ExplicitWalDeltaConsistencyTest.java       |  82 +++++++
 .../wal/memtracker/PageMemoryTracker.java          | 240 +++++++++++++++------
 3 files changed, 280 insertions(+), 66 deletions(-)

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 8eba4b1..94852f9 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
@@ -17,19 +17,22 @@
 
 package org.apache.ignite.internal.processors.cache.persistence.freelist.io;
 
+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.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;
 import org.apache.ignite.internal.util.GridStringBuilder;
+import org.apache.ignite.internal.util.GridUnsafe;
 
 import static org.apache.ignite.internal.processors.cache.persistence.tree.util.PageHandler.copyMemory;
 
 /**
  * TODO optimize: now we have slow {@link #removePage(long, long)}
  */
-public class PagesListNodeIO extends PageIO {
+public class PagesListNodeIO extends PageIO implements CompactablePageIO {
     /** */
     public static final IOVersions<PagesListNodeIO> VERSIONS = new IOVersions<>(
         new PagesListNodeIO(1)
@@ -233,6 +236,25 @@ public class PagesListNodeIO extends PageIO {
     }
 
     /** {@inheritDoc} */
+    @Override public void compactPage(ByteBuffer page, ByteBuffer out, int pageSize) {
+        copyPage(page, out, pageSize);
+
+        long pageAddr = GridUnsafe.bufferAddress(out);
+
+        // Just drop all the extra garbage at the end.
+        out.limit(offset(getCount(pageAddr)));
+    }
+
+    /** {@inheritDoc} */
+    @Override public void restorePage(ByteBuffer compactPage, int pageSize) {
+        assert compactPage.isDirect();
+        assert compactPage.position() == 0;
+        assert compactPage.limit() <= pageSize;
+
+        compactPage.limit(pageSize); // Just add garbage to the end.
+    }
+
+    /** {@inheritDoc} */
     @Override protected void printPage(long addr, int pageSize, GridStringBuilder sb) throws IgniteCheckedException {
         sb.a("PagesListNode [\n\tpreviousPageId=").appendHex(getPreviousId(addr))
             .a(",\n\tnextPageId=").appendHex(getNextId(addr))
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/wal/ExplicitWalDeltaConsistencyTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/wal/ExplicitWalDeltaConsistencyTest.java
index 28668c9..ee90444 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/wal/ExplicitWalDeltaConsistencyTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/wal/ExplicitWalDeltaConsistencyTest.java
@@ -17,9 +17,14 @@
 
 package org.apache.ignite.internal.processors.cache.persistence.wal;
 
+import java.util.concurrent.CountDownLatch;
 import org.apache.ignite.IgniteCache;
+import org.apache.ignite.cache.CacheMode;
+import org.apache.ignite.cache.affinity.rendezvous.RendezvousAffinityFunction;
+import org.apache.ignite.configuration.CacheConfiguration;
 import org.apache.ignite.internal.IgniteEx;
 import org.apache.ignite.internal.processors.cache.persistence.wal.memtracker.PageMemoryTrackerPluginProvider;
+import org.apache.ignite.testframework.GridTestUtils;
 import org.junit.Test;
 
 /**
@@ -98,4 +103,81 @@ public class ExplicitWalDeltaConsistencyTest extends AbstractWalDeltaConsistency
 
         assertTrue(PageMemoryTrackerPluginProvider.tracker(ignite).checkPages(true));
     }
+
+    /**
+     * Test reused pages consistency.
+     */
+    @Test
+    public void testReusePages() throws Exception {
+        IgniteEx ignite = startGrid(0);
+
+        ignite.cluster().active(true);
+
+        CacheConfiguration<Integer, Object> ccfg = new CacheConfiguration<>(DEFAULT_CACHE_NAME);
+
+        ccfg.setCacheMode(CacheMode.PARTITIONED).setAffinity(new RendezvousAffinityFunction().setPartitions(1));
+
+        IgniteCache<Integer, Object> cache = ignite.createCache(ccfg);
+
+        for (int i = 0; i < 1_000; i++)
+            cache.put(i, new byte[3000]);
+
+        for (int i = 1_000; i < 2_000; i++)
+            cache.put(i, new byte[500]);
+
+        for (int i = 0; i < 2_000; i++)
+            cache.remove(i);
+
+        stopGrid(0);
+
+        ignite = startGrid(0);
+
+        ignite.cluster().active(true);
+
+        cache = ignite.cache(DEFAULT_CACHE_NAME);
+
+        for (int i = 0; i < 2_000; i++)
+            cache.put(i, i);
+
+        assertTrue(PageMemoryTrackerPluginProvider.tracker(ignite).checkPages(true));
+    }
+
+    /**
+     * Test pages consistency after recovery.
+     */
+    @Test
+    public void testRecovery() throws Exception {
+        IgniteEx ignite = startGrid(0);
+
+        ignite.cluster().active(true);
+
+        CountDownLatch latch = new CountDownLatch(1);
+
+        GridTestUtils.runAsync(() -> {
+            IgniteCache<Integer, Object> cache = ignite.createCache(DEFAULT_CACHE_NAME);
+
+            for (int i = 0; i < 1_000; i++) {
+                cache.put(i, i);
+
+                if (i == 100)
+                    latch.countDown();
+            }
+        });
+
+        latch.await();
+
+        // Non-graceful stop.
+        ignite.close();
+
+        IgniteEx ignite0 = startGrid(0);
+
+        ignite0.cluster().active(true);
+
+        IgniteCache<Integer, Object> cache = ignite0.cache(DEFAULT_CACHE_NAME);
+
+        for (int i = 1_000; i < 2_000; i++)
+            cache.put(i, i);
+
+        assertTrue(PageMemoryTrackerPluginProvider.tracker(ignite0).checkPages(true));
+    }
 }
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 a4b7707..c50ac02 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
@@ -28,7 +28,10 @@ import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReadWriteLock;
 import java.util.concurrent.locks.ReentrantLock;
+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.internal.GridKernalContext;
@@ -42,6 +45,7 @@ import org.apache.ignite.internal.pagemem.PageMemory;
 import org.apache.ignite.internal.pagemem.store.IgnitePageStoreManager;
 import org.apache.ignite.internal.pagemem.wal.IgniteWriteAheadLogManager;
 import org.apache.ignite.internal.pagemem.wal.WALPointer;
+import org.apache.ignite.internal.pagemem.wal.record.MemoryRecoveryRecord;
 import org.apache.ignite.internal.pagemem.wal.record.PageSnapshot;
 import org.apache.ignite.internal.pagemem.wal.record.WALRecord;
 import org.apache.ignite.internal.pagemem.wal.record.delta.PageDeltaRecord;
@@ -56,6 +60,7 @@ import org.apache.ignite.internal.processors.cache.persistence.GridCacheDatabase
 import org.apache.ignite.internal.processors.cache.persistence.file.FilePageStoreManager;
 import org.apache.ignite.internal.processors.cache.persistence.metastorage.MetaStorage;
 import org.apache.ignite.internal.processors.cache.persistence.pagemem.PageMemoryImpl;
+import org.apache.ignite.internal.processors.cache.persistence.tree.io.CompactablePageIO;
 import org.apache.ignite.internal.processors.cache.persistence.tree.io.PageIO;
 import org.apache.ignite.internal.processors.cache.persistence.wal.FileWriteAheadLogManager;
 import org.apache.ignite.internal.processors.cache.tree.AbstractDataLeafIO;
@@ -120,6 +125,9 @@ public class PageMemoryTracker implements IgnitePlugin {
     /** Memory region. */
     private volatile DirectMemoryRegion memoryRegion;
 
+    /** Memory region lock, to prevent race between memory region deallocation and delta records applying. */
+    private final ReadWriteLock memoryRegionLock = new ReentrantReadWriteLock();
+
     /** Max pages. */
     private volatile int maxPages;
 
@@ -135,6 +143,12 @@ public class PageMemoryTracker implements IgnitePlugin {
     /** Checkpoint listener. */
     private DbCheckpointListener checkpointLsnr;
 
+    /** Temporary byte buffer, used to compact local pages. */
+    private volatile ByteBuffer tmpBuf1;
+
+    /** Temporary byte buffer, used to compact remote pages. */
+    private volatile ByteBuffer tmpBuf2;
+
     /**
      * @param ctx Plugin context.
      * @param cfg Configuration.
@@ -163,7 +177,8 @@ public class PageMemoryTracker implements IgnitePlugin {
                 @Override public void resumeLogging(WALPointer lastPtr) throws IgniteCheckedException {
                     super.resumeLogging(lastPtr);
 
-                    emptyPds = (lastPtr == null);
+                    if (lastPtr == null)
+                        emptyPds = true;
                 }
             };
         }
@@ -184,8 +199,7 @@ public class PageMemoryTracker implements IgnitePlugin {
                     cleanupPages(fullPageId -> fullPageId.groupId() == grp.groupId());
                 }
 
-                @Override
-                public void onPartitionDestroyed(int grpId, int partId, int tag) throws IgniteCheckedException {
+                @Override public void onPartitionDestroyed(int grpId, int partId, int tag) throws IgniteCheckedException {
                     super.onPartitionDestroyed(grpId, partId, tag);
 
                     cleanupPages(fullPageId -> fullPageId.groupId() == grpId
@@ -222,6 +236,12 @@ public class PageMemoryTracker implements IgnitePlugin {
                 - encSpi.blockSize() /* For CRC. */;
         });
 
+        Mockito.when(pageMemoryMock.pageBuffer(Mockito.anyLong())).then(mock -> {
+            long pageAddr = (Long)mock.getArguments()[0];
+
+            return GridUnsafe.wrapPointer(pageAddr, pageSize);
+        });
+
         GridCacheSharedContext sharedCtx = gridCtx.cache().context();
 
         // Initialize one memory region for all data regions of target ignite node.
@@ -246,18 +266,21 @@ public class PageMemoryTracker implements IgnitePlugin {
 
         freeSlotsCnt = maxPages;
 
+        tmpBuf1 = ByteBuffer.allocateDirect(pageSize);
+        tmpBuf2 = ByteBuffer.allocateDirect(pageSize);
+
         if (cfg.isCheckPagesOnCheckpoint()) {
             checkpointLsnr = new DbCheckpointListener() {
                 @Override public void onMarkCheckpointBegin(Context ctx) throws IgniteCheckedException {
-                    if (!checkPages(false))
+                    if (!checkPages(false, true))
                         throw new IgniteCheckedException("Page memory is inconsistent after applying WAL delta records.");
                 }
 
-                @Override public void beforeCheckpointBegin(Context ctx) throws IgniteCheckedException {
+                @Override public void beforeCheckpointBegin(Context ctx) {
                     /* No-op. */
                 }
 
-                @Override public void onCheckpointBegin(Context ctx) throws IgniteCheckedException {
+                @Override public void onCheckpointBegin(Context ctx) {
                     /* No-op. */
                 }
             };
@@ -289,7 +312,14 @@ public class PageMemoryTracker implements IgnitePlugin {
 
         stats.clear();
 
-        memoryProvider.shutdown(true);
+        memoryRegionLock.writeLock().lock();
+
+        try {
+            memoryProvider.shutdown(true);
+        }
+        finally {
+            memoryRegionLock.writeLock().unlock();
+        }
 
         if (checkpointLsnr != null) {
             ((GridCacheDatabaseSharedManager)gridCtx.cache().context().database())
@@ -366,17 +396,13 @@ public class PageMemoryTracker implements IgnitePlugin {
             pageSlot.lock();
 
             try {
-                page = new DirectMemoryPage(pageSlot);
-
-                page.fullPageId(fullPageId);
+                page = new DirectMemoryPage(pageSlot, fullPageId);
 
                 pages.put(fullPageId, page);
 
                 if (pageSlot.owningPage() != null) {
                     // Clear memory if slot was already used.
-                    ByteBuffer pageBuf = GridUnsafe.wrapPointer(pageAddr, pageSize);
-
-                    pageBuf.put(new byte[pageSize]);
+                    GridUnsafe.setMemory(pageAddr, pageSize, (byte)0);
                 }
 
                 pageSlot.owningPage(page);
@@ -408,58 +434,78 @@ public class PageMemoryTracker implements IgnitePlugin {
      * Apply WAL record to local memory region.
      */
     private void applyWalRecord(WALRecord record) throws IgniteCheckedException {
-        if (!started)
-            return;
+        memoryRegionLock.readLock().lock();
 
-        if (record instanceof PageSnapshot) {
-            PageSnapshot snapshot = (PageSnapshot)record;
+        try {
+            if (!started)
+                return;
 
-            int grpId = snapshot.fullPageId().groupId();
-            long pageId = snapshot.fullPageId().pageId();
+            if (record instanceof MemoryRecoveryRecord && !emptyPds) {
+                synchronized (pageAllocatorMux) {
+                    pages.clear();
 
-            FullPageId fullPageId = new FullPageId(pageId, grpId);
+                    lastPageIdx = 0;
 
-            DirectMemoryPage page = page(fullPageId);
+                    freeSlotsCnt = maxPages;
 
-            page.lock();
+                    freeSlots.clear();
 
-            try {
-                GridUnsafe.copyMemory(GridUnsafe.bufferAddress(snapshot.pageDataBuffer()), page.address(), pageSize);
+                    stats.clear();
+                }
+            }
+            else if (record instanceof PageSnapshot) {
+                PageSnapshot snapshot = (PageSnapshot)record;
 
-                page.changeHistory().clear();
+                int grpId = snapshot.fullPageId().groupId();
+                long pageId = snapshot.fullPageId().pageId();
 
-                page.changeHistory().add(record);
-            }
-            finally {
-                page.unlock();
+                FullPageId fullPageId = new FullPageId(pageId, grpId);
+
+                DirectMemoryPage page = page(fullPageId);
+
+                page.lock();
+
+               try {
+                    GridUnsafe.copyMemory(GridUnsafe.bufferAddress(snapshot.pageDataBuffer()), page.address(), pageSize);
+
+                    page.changeHistory().clear();
+
+                    page.changeHistory().add(record);
+                }
+                finally {
+                    page.unlock();
+                }
             }
-        }
-        else if (record instanceof PageDeltaRecord) {
-            PageDeltaRecord deltaRecord = (PageDeltaRecord)record;
+            else if (record instanceof PageDeltaRecord) {
+                PageDeltaRecord deltaRecord = (PageDeltaRecord)record;
 
-            int grpId = deltaRecord.groupId();
-            long pageId = deltaRecord.pageId();
+                int grpId = deltaRecord.groupId();
+                long pageId = deltaRecord.pageId();
 
-            FullPageId fullPageId = new FullPageId(pageId, grpId);
+                FullPageId fullPageId = new FullPageId(pageId, grpId);
 
-            DirectMemoryPage page = page(fullPageId);
+                DirectMemoryPage page = page(fullPageId);
 
-            page.lock();
+                page.lock();
 
-            try {
-                deltaRecord.applyDelta(pageMemoryMock, page.address());
+                try {
+                    deltaRecord.applyDelta(pageMemoryMock, page.address());
 
-                page.changeHistory().add(record);
-            }
-            finally {
-                page.unlock();
+                    page.changeHistory().add(record);
+                }
+                finally {
+                    page.unlock();
+                }
             }
-        }
-        else
-            return;
+            else
+                return;
 
-        // Increment statistics.
-        stats.computeIfAbsent(record.type(), r -> new AtomicInteger()).incrementAndGet();
+            // Increment statistics.
+            stats.computeIfAbsent(record.type(), r -> new AtomicInteger()).incrementAndGet();
+        }
+        finally {
+            memoryRegionLock.readLock().unlock();
+        }
     }
 
     /**
@@ -488,6 +534,19 @@ public class PageMemoryTracker implements IgnitePlugin {
      * @return {@code true} if content of all tracked pages equals to content of these pages in the ignite instance.
      */
     public boolean checkPages(boolean checkAll) throws IgniteCheckedException {
+        return checkPages(checkAll, false);
+    }
+
+    /**
+     * Checks if there are any differences between the Ignite's data regions content and pages inside the tracker.
+     *
+     * @param checkAll Check all tracked pages, otherwise check until first error.
+     * @param checkPageCnt Check tracked and allocated pages count. This check can be done only if there is no
+     * concurrent modification of pages in the system (for example when checkpointWriteLock is held). Some threads
+     * (for example MVCC vacuum cleaner) can modify pages even if there is no activity from a users point of view.
+     * @return {@code true} if content of all tracked pages equals to content of these pages in the ignite instance.
+     */
+    private boolean checkPages(boolean checkAll, boolean checkPageCnt) throws IgniteCheckedException {
         if (!started)
             throw new IgniteCheckedException("Page memory checking only possible when tracker is started.");
 
@@ -503,11 +562,13 @@ public class PageMemoryTracker implements IgnitePlugin {
 
             dumpStats();
 
-            if (emptyPds && pages.size() != totalAllocated) {
+            if (emptyPds && checkPageCnt && pages.size() != totalAllocated) {
                 res = false;
 
                 log.error("Started from empty PDS, but tracked pages count not equals to allocated pages count");
 
+                dumpPagesCountDiff();
+
                 if (!checkAll)
                     return false;
             }
@@ -585,17 +646,17 @@ public class PageMemoryTracker implements IgnitePlugin {
      * Compare pages content.
      *
      * @param fullPageId Full page ID.
-     * @param expectedPage Expected page.
+     * @param expPage Expected page.
      * @param actualPageAddr Actual page address.
      * @return {@code True} if pages are equals, {@code False} otherwise.
      * @throws IgniteCheckedException If fails.
      */
-    private boolean comparePages(FullPageId fullPageId, DirectMemoryPage expectedPage, long actualPageAddr) throws IgniteCheckedException {
-        long expPageArrd = expectedPage.address();
+    private boolean comparePages(FullPageId fullPageId, DirectMemoryPage expPage, long actualPageAddr) throws IgniteCheckedException {
+        long expPageAddr = expPage.address();
 
         GridCacheProcessor cacheProc = gridCtx.cache();
 
-        ByteBuffer locBuf = GridUnsafe.wrapPointer(expPageArrd, pageSize);
+        ByteBuffer locBuf = GridUnsafe.wrapPointer(expPageAddr, pageSize);
         ByteBuffer rmtBuf = GridUnsafe.wrapPointer(actualPageAddr, pageSize);
 
         PageIO pageIo = PageIO.getPageIO(actualPageAddr);
@@ -609,17 +670,29 @@ public class PageMemoryTracker implements IgnitePlugin {
 
             // Reset lock info as there is no sense to log it into WAL.
             for (int i = 0; i < cnt; i++) {
-                io.setMvccLockCoordinatorVersion(expPageArrd, i, io.getMvccLockCoordinatorVersion(actualPageAddr, i));
-                io.setMvccLockCounter(expPageArrd, i, io.getMvccLockCounter(actualPageAddr, i));
+                io.setMvccLockCoordinatorVersion(expPageAddr, i, io.getMvccLockCoordinatorVersion(actualPageAddr, i));
+                io.setMvccLockCounter(expPageAddr, i, io.getMvccLockCounter(actualPageAddr, i));
             }
         }
 
+        // Compare only meaningful data.
+        if (pageIo instanceof CompactablePageIO) {
+            tmpBuf1.clear();
+            tmpBuf2.clear();
+
+            ((CompactablePageIO)pageIo).compactPage(locBuf, tmpBuf1, pageSize);
+            ((CompactablePageIO)pageIo).compactPage(rmtBuf, tmpBuf2, pageSize);
+
+            locBuf = tmpBuf1;
+            rmtBuf = tmpBuf2;
+        }
+
         if (!locBuf.equals(rmtBuf)) {
             log.error("Page buffers are not equals: " + fullPageId);
 
             dumpDiff(locBuf, rmtBuf);
 
-            dumpHistory(expectedPage);
+            dumpHistory(expPage);
 
             return false;
         }
@@ -677,6 +750,47 @@ public class PageMemoryTracker implements IgnitePlugin {
     }
 
     /**
+     * Dump diff between allocated and tracked page counts.
+     */
+    private void dumpPagesCountDiff() throws IgniteCheckedException {
+        Map<Integer, Long> pagesByGroups = pages.keySet().stream().collect(
+            Collectors.groupingBy(FullPageId::groupId, Collectors.counting()));
+
+        IgnitePageStoreManager pageStoreMgr = gridCtx.cache().context().pageStore();
+
+        for (Map.Entry<Integer, Long> groupPages : pagesByGroups.entrySet()) {
+            int grpId = groupPages.getKey();
+            long grpPagesAllocated = pageStoreMgr.pagesAllocated(grpId);
+
+            if (grpPagesAllocated != groupPages.getValue()) {
+                log.error(">>> Page count for groupId " + grpId + ": allocated=" + grpPagesAllocated +
+                    ", tracked=" + groupPages.getValue());
+
+                Map<Integer, Long> pagesByParts = pages.keySet().stream().filter(id -> id.groupId() == grpId)
+                    .collect(Collectors.groupingBy(id -> PageIdUtils.partId(id.pageId()), Collectors.counting()));
+
+                for (Map.Entry<Integer, Long> partPages : pagesByParts.entrySet()) {
+                    long partPagesAllocated = pageStoreMgr.pages(grpId, partPages.getKey());
+
+                    if (partPagesAllocated != partPages.getValue()) {
+                        log.error(">>>> Page count for partId " + partPages.getKey() + ": allocated=" +
+                            partPagesAllocated + ", tracked=" + partPages.getValue());
+                    }
+                }
+
+                int partCnt = gridCtx.cache().cacheGroup(grpId).config().getAffinity().partitions();
+
+                for (int partId = 0; partId < partCnt; partId++) {
+                    if (pageStoreMgr.exists(grpId, partId) && !pagesByParts.keySet().contains(partId)) {
+                        log.error(">>>> Page count for partId " + partId + ": allocated=" +
+                            pageStoreMgr.pages(grpId, partId) + ", tracked=0");
+                    }
+                }
+            }
+        }
+    }
+
+    /**
      *
      */
     private static class DirectMemoryPage {
@@ -687,13 +801,14 @@ public class PageMemoryTracker implements IgnitePlugin {
         private final List<WALRecord> changeHist = new LinkedList<>();
 
         /** Full page id. */
-        private volatile FullPageId fullPageId;
+        private final FullPageId fullPageId;
 
         /**
          * @param slot Memory page slot.
          */
-        private DirectMemoryPage(DirectMemoryPageSlot slot) {
+        private DirectMemoryPage(DirectMemoryPageSlot slot, FullPageId fullPageId) {
             this.slot = slot;
+            this.fullPageId = fullPageId;
         }
 
         /**
@@ -726,6 +841,7 @@ public class PageMemoryTracker implements IgnitePlugin {
         /**
          * Change history.
          */
+        @SuppressWarnings("AssignmentOrReturnOfFieldWithMutableType")
         public List<WALRecord> changeHistory() {
             return changeHist;
         }
@@ -738,13 +854,6 @@ public class PageMemoryTracker implements IgnitePlugin {
         }
 
         /**
-         * @param fullPageId Full page id.
-         */
-        public void fullPageId(FullPageId fullPageId) {
-            this.fullPageId = fullPageId;
-        }
-
-        /**
          * @return Memory page slot.
          */
         public DirectMemoryPageSlot slot() {
@@ -780,6 +889,7 @@ public class PageMemoryTracker implements IgnitePlugin {
         /**
          * Lock page slot.
          */
+        @SuppressWarnings("LockAcquiredButNotSafelyReleased")
         public void lock() {
             lock.lock();
         }