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 2021/02/19 06:38:42 UTC

[ignite] branch master updated: IGNITE-13761 Clock and Segmented-LRU page replacement - Fixes #8513.

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 693cc44  IGNITE-13761 Clock and Segmented-LRU page replacement - Fixes #8513.
693cc44 is described below

commit 693cc44cddf186c0eaf76fb24ce7d2d2010276ce
Author: Aleksey Plekhanov <pl...@gmail.com>
AuthorDate: Fri Feb 19 09:29:57 2021 +0300

    IGNITE-13761 Clock and Segmented-LRU page replacement - Fixes #8513.
    
    Signed-off-by: Aleksey Plekhanov <pl...@gmail.com>
---
 .../jmh/misc/JmhSegmentedLruListBenchmark.java     | 122 +++++
 .../configuration/DataRegionConfiguration.java     |  36 +-
 .../ignite/configuration/PageReplacementMode.java  |  87 ++++
 .../pagemem/ClockPageReplacementFlags.java         | 154 ++++++
 .../pagemem/ClockPageReplacementPolicy.java        |  96 ++++
 .../pagemem/ClockPageReplacementPolicyFactory.java |  33 ++
 .../cache/persistence/pagemem/PageMemoryImpl.java  | 532 ++++++++-------------
 .../cache/persistence/pagemem/PagePool.java        |   8 +
 .../persistence/pagemem/PageReplacementPolicy.java |  69 +++
 .../pagemem/PageReplacementPolicyFactory.java      |  39 ++
 .../pagemem/RandomLruPageReplacementPolicy.java    | 247 ++++++++++
 .../RandomLruPageReplacementPolicyFactory.java     |  33 ++
 .../persistence/pagemem/SegmentedLruPageList.java  | 364 ++++++++++++++
 .../pagemem/SegmentedLruPageReplacementPolicy.java | 102 ++++
 .../SegmentedLruPageReplacementPolicyFactory.java  |  33 ++
 .../pagemem/BPlusTreePageMemoryImplTest.java       |   2 +
 .../BPlusTreeReuseListPageMemoryImplTest.java      |   2 +
 .../pagemem/ClockPageReplacementFlagsTest.java     | 119 +++++
 .../pagemem/IndexStoragePageMemoryImplTest.java    |   2 +
 .../pagemem/PageMemoryImplNoLoadTest.java          |   2 +
 .../pagemem/SegmentedLruPageListTest.java          | 366 ++++++++++++++
 .../ignite/testsuites/IgniteBasicTestSuite.java    |   4 +
 .../benchmark-cache-pagereplacements.properties    | 124 +++++
 .../benchmark-cache-pegereplacements.properties    |  83 ----
 .../ignite-localhost-pagereplacement-config.xml    |  79 +++
 .../ignite/yardstick/IgniteBenchmarkArguments.java |  23 +
 .../org/apache/ignite/yardstick/IgniteNode.java    |   8 +-
 ...=> IgniteAbstractPageReplacementBenchmark.java} |  89 +++-
 .../IgniteGetWithPageReplacementBenchmark.java     |  41 ++
 .../IgnitePutWithPageReplacementBenchmark.java     |  43 ++
 30 files changed, 2506 insertions(+), 436 deletions(-)

diff --git a/modules/benchmarks/src/main/java/org/apache/ignite/internal/benchmarks/jmh/misc/JmhSegmentedLruListBenchmark.java b/modules/benchmarks/src/main/java/org/apache/ignite/internal/benchmarks/jmh/misc/JmhSegmentedLruListBenchmark.java
new file mode 100644
index 0000000..fa418dd
--- /dev/null
+++ b/modules/benchmarks/src/main/java/org/apache/ignite/internal/benchmarks/jmh/misc/JmhSegmentedLruListBenchmark.java
@@ -0,0 +1,122 @@
+/*
+ * 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.benchmarks.jmh.misc;
+
+import java.util.Random;
+import java.util.concurrent.TimeUnit;
+import org.apache.ignite.internal.mem.DirectMemoryProvider;
+import org.apache.ignite.internal.mem.DirectMemoryRegion;
+import org.apache.ignite.internal.mem.unsafe.UnsafeMemoryProvider;
+import org.apache.ignite.internal.processors.cache.persistence.pagemem.SegmentedLruPageList;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Fork;
+import org.openjdk.jmh.annotations.Level;
+import org.openjdk.jmh.annotations.Measurement;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.annotations.TearDown;
+import org.openjdk.jmh.annotations.Threads;
+import org.openjdk.jmh.annotations.Warmup;
+import org.openjdk.jmh.runner.Runner;
+import org.openjdk.jmh.runner.options.Options;
+import org.openjdk.jmh.runner.options.OptionsBuilder;
+
+/**
+ * Benchmarks {@link SegmentedLruPageList} class.
+ */
+@State(Scope.Benchmark)
+@Fork(1)
+@Threads(2)
+@BenchmarkMode(Mode.AverageTime)
+@OutputTimeUnit(TimeUnit.NANOSECONDS)
+@Warmup(iterations = 5, time = 2)
+@Measurement(iterations = 10, time = 3)
+public class JmhSegmentedLruListBenchmark {
+    /** Pages count. */
+    private static final int PAGES_CNT = 1000;
+
+    /** Random numbers generator. */
+    private Random rnd;
+
+    /** Direct memory provider. */
+    DirectMemoryProvider provider;
+
+    /** LRU list. */
+    private SegmentedLruPageList lruList;
+
+    /**
+     * Setup.
+     */
+    @Setup(Level.Iteration)
+    public void setup() {
+        rnd = new Random(0);
+
+        provider = new UnsafeMemoryProvider(null);
+        provider.initialize(new long[] {SegmentedLruPageList.requiredMemory(PAGES_CNT)});
+
+        DirectMemoryRegion region = provider.nextRegion();
+
+        lruList = new SegmentedLruPageList(PAGES_CNT, region.address());
+
+        for (int i = 0; i < PAGES_CNT; i++)
+            lruList.addToTail(i, false);
+    }
+
+    /**
+     * Tear down.
+     */
+    @TearDown(Level.Iteration)
+    public void tearDown() {
+        provider.shutdown(true);
+    }
+
+    /**
+     * Benchmark {@link SegmentedLruPageList#moveToTail(int)} method.
+     */
+    @Benchmark
+    public void moveToTail() {
+        int nextIdx = rnd.nextInt(PAGES_CNT);
+
+        lruList.moveToTail(nextIdx);
+    }
+
+    /**
+     * Benchmark {@link SegmentedLruPageList#poll()} and {@link SegmentedLruPageList#addToTail(int, boolean)} methods.
+     */
+    @Benchmark
+    public void pollAndAdd() {
+        lruList.addToTail(lruList.poll(), rnd.nextBoolean());
+    }
+
+    /**
+     *
+     * @param args Args.
+     * @throws Exception Exception.
+     */
+    public static void main(String[] args) throws Exception {
+        final Options options = new OptionsBuilder()
+            .include(JmhSegmentedLruListBenchmark.class.getSimpleName())
+            .build();
+
+        new Runner(options).run();
+    }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/configuration/DataRegionConfiguration.java b/modules/core/src/main/java/org/apache/ignite/configuration/DataRegionConfiguration.java
index 39c4876..3f3b09a 100644
--- a/modules/core/src/main/java/org/apache/ignite/configuration/DataRegionConfiguration.java
+++ b/modules/core/src/main/java/org/apache/ignite/configuration/DataRegionConfiguration.java
@@ -80,6 +80,9 @@ public final class DataRegionConfiguration implements Serializable {
     /** Default length of interval over which {@link DataRegionMetrics#getAllocationRate()} metric is calculated. */
     public static final int DFLT_RATE_TIME_INTERVAL_MILLIS = 60_000;
 
+    /** Default page replacement mode. */
+    public static final PageReplacementMode DFLT_PAGE_REPLACEMENT_MODE = PageReplacementMode.CLOCK;
+
     /** Data region name. */
     private String name = DFLT_DATA_REG_DEFAULT_NAME;
 
@@ -93,9 +96,12 @@ public final class DataRegionConfiguration implements Serializable {
     /** An optional path to a memory mapped files directory for this data region. */
     private String swapPath;
 
-    /** An algorithm for memory pages eviction. */
+    /** An algorithm for memory pages eviction (persistence is disabled). */
     private DataPageEvictionMode pageEvictionMode = DataPageEvictionMode.DISABLED;
 
+    /** An algorithm for memory pages replacement (persistence is enabled). */
+    private PageReplacementMode pageReplacementMode = DFLT_PAGE_REPLACEMENT_MODE;
+
     /**
      * A threshold for memory pages eviction initiation. For instance, if the threshold is 0.9 it means that the page
      * memory will start the eviction only after 90% data region is occupied.
@@ -243,6 +249,9 @@ public final class DataRegionConfiguration implements Serializable {
      * memory exception will be thrown if the memory region usage, defined by this data region, goes beyond its
      * capacity which is {@link #getMaxSize()}.
      *
+     * Note: Page eviction is used only when persistence is disabled for data region. For persistent data regions see
+     * page replacement mode ({@link #getPageReplacementMode()}).
+     *
      * @return Memory pages eviction algorithm. {@link DataPageEvictionMode#DISABLED} used by default.
      */
     public DataPageEvictionMode getPageEvictionMode() {
@@ -262,6 +271,31 @@ public final class DataRegionConfiguration implements Serializable {
     }
 
     /**
+     * Gets memory pages replacement mode. If persistence is enabled and Ignite store on disk more data then available
+     * data region memory ({@link #getMaxSize()}) page replacement can be started to rotate memory pages with the disk.
+     * This parameter defines the algorithm to find pages to replace.
+     *
+     * Note: For not persistent data regions see page eviction mode ({@link #getPageEvictionMode()}).
+     *
+     * @return Memory pages replacement algorithm. {@link PageReplacementMode#CLOCK} used by default.
+     */
+    public PageReplacementMode getPageReplacementMode() {
+        return pageReplacementMode;
+    }
+
+    /**
+     * Sets memory pages replacement mode.
+     *
+     * @param replacementMode Page replacement mode.
+     * @return {@code this} for chaining.
+     */
+    public DataRegionConfiguration setPageReplacementMode(PageReplacementMode replacementMode) {
+        pageReplacementMode = replacementMode;
+
+        return this;
+    }
+
+    /**
      * Gets a threshold for memory pages eviction initiation. For instance, if the threshold is 0.9 it means that the
      * page memory will start the eviction only after 90% of the data region is occupied.
      *
diff --git a/modules/core/src/main/java/org/apache/ignite/configuration/PageReplacementMode.java b/modules/core/src/main/java/org/apache/ignite/configuration/PageReplacementMode.java
new file mode 100644
index 0000000..c7a0ac2
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/configuration/PageReplacementMode.java
@@ -0,0 +1,87 @@
+/*
+* 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.configuration;
+
+import org.apache.ignite.DataRegionMetrics;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Defines memory page replacement algorithm. A mode is set for a specific {@link DataRegionConfiguration}.
+ */
+public enum PageReplacementMode {
+    /**
+     * Random-LRU algorithm.
+     *
+     * Every time page is accessed, its timestamp gets updated. When a page fault occurs and it's required to replace
+     * some pages, the algorithm randomly chooses 5 pages from the page memory and evicts a page with the latest
+     * timestamp.
+     *
+     * This algorithm has zero maintenance cost, but not very effective in terms of finding the next page to replace.
+     * Recommended to use in environments, where there are no page replacements (large enough data region to store all
+     * amount of data) or page replacements are rare (See {@link DataRegionMetrics#getPagesReplaceRate()} metric, which
+     * can be helpful here).
+     */
+    RANDOM_LRU,
+
+    /**
+     * Segmented-LRU algorithm.
+     *
+     * Segmented-LRU algorithm is a scan-resistant variation of the Least Recently Used (LRU) algorithm.
+     * Segmented-LRU pages list is divided into two segments, a probationary segment, and a protected segment. Pages in
+     * each segment are ordered from the least to the most recently accessed. New pages are added to the most recently
+     * accessed end (tail) of the probationary segment. Existing pages are removed from wherever they currently reside
+     * and added to the most recently accessed end of the protected segment. Pages in the protected segment have thus
+     * been accessed at least twice. The protected segment is finite, so migration of a page from the probationary
+     * segment to the protected segment may force the migration of the LRU page in the protected segment to the most
+     * recently used end of the probationary segment, giving this page another chance to be accessed before being
+     * replaced. Page to replace is polled from the least recently accessed end (head) of the probationary segment.
+     *
+     * This algorithm requires additional memory to store pages list and need to update this list on each page access,
+     * but have near to optimal page to replace selection policy. So, there can be a little performance drop for
+     * environments without page replacement (compared to random-LRU and CLOCK), but for environments with a high rate
+     * of page replacement and a large amount of one-time scans segmented-LRU can outperform random-LRU and CLOCK.
+     */
+    SEGMENTED_LRU,
+
+    /**
+     * CLOCK algorithm.
+     *
+     * The clock algorithm keeps a circular list of pages in memory, with the "hand" pointing to the last examined page
+     * frame in the list. When a page fault occurs and no empty frames exist, then the hit flag of the page is inspected
+     * at the hand's location. If the hit flag is 0, the new page is put in place of the page the "hand" points to, and
+     * the hand is advanced one position. Otherwise, the hit flag is cleared, then the clock hand is incremented and the
+     * process is repeated until a page is replaced.
+     *
+     * This algorithm has near to zero maintenance cost and replacement policy efficiency between random-LRU and
+     * segmented-LRU.
+     */
+    CLOCK;
+
+    /** Enumerated values. */
+    private static final PageReplacementMode[] VALS = values();
+
+    /**
+     * Efficiently gets enumerated value from its ordinal.
+     *
+     * @param ord Ordinal value.
+     * @return Enumerated value or {@code null} if ordinal out of range.
+     */
+    @Nullable public static PageReplacementMode fromOrdinal(int ord) {
+        return ord >= 0 && ord < VALS.length ? VALS[ord] : null;
+    }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/ClockPageReplacementFlags.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/ClockPageReplacementFlags.java
new file mode 100644
index 0000000..84b06d2
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/ClockPageReplacementFlags.java
@@ -0,0 +1,154 @@
+/*
+ * 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 java.util.function.LongUnaryOperator;
+import org.apache.ignite.configuration.PageReplacementMode;
+import org.apache.ignite.internal.util.GridUnsafe;
+
+/**
+ * Clock page replacement algorithm implementation.
+ *
+ * @see PageReplacementMode#CLOCK
+ */
+public class ClockPageReplacementFlags {
+    /** Total pages count. */
+    private final int pagesCnt;
+
+    /** Index of the next candidate ("hand"). */
+    private int curIdx;
+
+    /** Pointer to memory region to store page hit flags. */
+    private final long flagsPtr;
+
+    /**
+     * @param totalPagesCnt Total pages count.
+     * @param memPtr Pointer to memory region.
+     */
+    ClockPageReplacementFlags(int totalPagesCnt, long memPtr) {
+        pagesCnt = totalPagesCnt;
+        flagsPtr = memPtr;
+
+        GridUnsafe.setMemory(flagsPtr, (totalPagesCnt + 7) >> 3, (byte)0);
+    }
+
+    /**
+     * Find page to replace.
+     *
+     * @return Page index to replace.
+     */
+    public int poll() {
+        // This method is always executed under exclusive lock, no other synchronization or CAS required.
+        while (true) {
+            if (curIdx >= pagesCnt)
+                curIdx = 0;
+
+            long ptr = flagsPtr + ((curIdx >> 3) & (~7L));
+
+            long flags = GridUnsafe.getLong(ptr);
+
+            if (((curIdx & 63) == 0) && (flags == ~0L)) {
+                GridUnsafe.putLong(ptr, 0L);
+
+                curIdx += 64;
+
+                continue;
+            }
+
+            long mask = ~0L << curIdx;
+
+            int bitIdx = Long.numberOfTrailingZeros(~flags & mask);
+
+            if (bitIdx == 64) {
+                GridUnsafe.putLong(ptr, flags & ~mask);
+
+                curIdx = (curIdx & ~63) + 64;
+            }
+            else {
+                mask &= ~(~0L << bitIdx);
+
+                GridUnsafe.putLong(ptr, flags & ~mask);
+
+                curIdx = (curIdx & ~63) + bitIdx + 1;
+
+                if (curIdx <= pagesCnt)
+                    return curIdx - 1;
+            }
+        }
+    }
+
+    /**
+     * Get page hit flag.
+     *
+     * @param pageIdx Page index.
+     */
+    boolean getFlag(int pageIdx) {
+        long flags = GridUnsafe.getLong(flagsPtr + ((pageIdx >> 3) & (~7L)));
+
+        return (flags & (1L << pageIdx)) != 0L;
+    }
+
+    /**
+     * Clear page hit flag.
+     *
+     * @param pageIdx Page index.
+     */
+    public void clearFlag(int pageIdx) {
+        compareAndSwapFlag(pageIdx, flags -> flags & ~(1L << pageIdx));
+    }
+
+    /**
+     * Set page hit flag.
+     *
+     * @param pageIdx Page index.
+     */
+    public void setFlag(int pageIdx) {
+        compareAndSwapFlag(pageIdx, flags -> flags | (1L << pageIdx));
+    }
+
+    /**
+     * CAS page hit flag value.
+     *
+     * @param pageIdx Page index.
+     * @param func Function to apply to flags.
+     */
+    private void compareAndSwapFlag(int pageIdx, LongUnaryOperator func) {
+        long ptr = flagsPtr + ((pageIdx >> 3) & (~7L));
+
+        long oldFlags;
+        long newFlags;
+
+        do {
+            oldFlags = GridUnsafe.getLong(ptr);
+            newFlags = func.applyAsLong(oldFlags);
+
+            if (oldFlags == newFlags)
+                return;
+        }
+        while (!GridUnsafe.compareAndSwapLong(null, ptr, oldFlags, newFlags));
+    }
+
+    /**
+     * Memory required to service {@code pagesCnt} pages.
+     *
+     * @param pagesCnt Pages count.
+     */
+    public static long requiredMemory(int pagesCnt) {
+        return ((pagesCnt + 63) / 8) & (~7L) /* 1 bit per page + 8 byte align */;
+    }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/ClockPageReplacementPolicy.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/ClockPageReplacementPolicy.java
new file mode 100644
index 0000000..85f7b31
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/ClockPageReplacementPolicy.java
@@ -0,0 +1,96 @@
+/*
+ * 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.IgniteCheckedException;
+import org.apache.ignite.configuration.PageReplacementMode;
+import org.apache.ignite.internal.pagemem.FullPageId;
+import org.apache.ignite.internal.pagemem.PageIdUtils;
+
+import static org.apache.ignite.internal.processors.cache.persistence.pagemem.PageMemoryImpl.INVALID_REL_PTR;
+import static org.apache.ignite.internal.processors.cache.persistence.pagemem.PageMemoryImpl.OUTDATED_REL_PTR;
+
+/**
+ * CLOCK page replacement policy implementation.
+ *
+ * @see PageReplacementMode#CLOCK
+ */
+public class ClockPageReplacementPolicy extends PageReplacementPolicy {
+    /** Pages hit-flags store. */
+    private final ClockPageReplacementFlags flags;
+
+    /**
+     * @param seg Page memory segment.
+     * @param ptr Pointer to memory region.
+     * @param pagesCnt Pages count.
+     */
+    protected ClockPageReplacementPolicy(PageMemoryImpl.Segment seg, long ptr, int pagesCnt) {
+        super(seg);
+
+        flags = new ClockPageReplacementFlags(pagesCnt, ptr);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void onHit(long relPtr) {
+        int pageIdx = (int)seg.pageIndex(relPtr);
+
+        flags.setFlag(pageIdx);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void onRemove(long relPtr) {
+        int pageIdx = (int)seg.pageIndex(relPtr);
+
+        flags.clearFlag(pageIdx);
+    }
+
+    /** {@inheritDoc} */
+    @Override public long replace() throws IgniteCheckedException {
+        LoadedPagesMap loadedPages = seg.loadedPages();
+
+        for (int i = 0; i < loadedPages.size(); i++) {
+            int pageIdx = flags.poll();
+
+            long relPtr = seg.relative(pageIdx);
+            long absPtr = seg.absolute(relPtr);
+
+            FullPageId fullId = PageHeader.fullPageId(absPtr);
+
+            // Check loaded pages map for outdated page.
+            relPtr = loadedPages.get(
+                fullId.groupId(),
+                fullId.effectivePageId(),
+                seg.partGeneration(fullId.groupId(), PageIdUtils.partId(fullId.pageId())),
+                INVALID_REL_PTR,
+                OUTDATED_REL_PTR
+            );
+
+            assert relPtr != INVALID_REL_PTR;
+
+            if (relPtr == OUTDATED_REL_PTR)
+                return seg.refreshOutdatedPage(fullId.groupId(), fullId.pageId(), true);
+
+            if (seg.tryToRemovePage(fullId, absPtr))
+                return relPtr;
+
+            flags.setFlag(pageIdx);
+        }
+
+        throw seg.oomException("no pages to replace");
+    }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/ClockPageReplacementPolicyFactory.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/ClockPageReplacementPolicyFactory.java
new file mode 100644
index 0000000..7dc9e50
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/ClockPageReplacementPolicyFactory.java
@@ -0,0 +1,33 @@
+/*
+ * 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;
+
+/**
+ * {@link ClockPageReplacementPolicy} factory.
+ */
+public class ClockPageReplacementPolicyFactory implements PageReplacementPolicyFactory {
+    /** {@inheritDoc} */
+    @Override public long requiredMemory(int pagesCnt) {
+        return ClockPageReplacementFlags.requiredMemory(pagesCnt);
+    }
+
+    /** {@inheritDoc} */
+    @Override public PageReplacementPolicy create(PageMemoryImpl.Segment seg, long ptr, int pagesCnt) {
+        return new ClockPageReplacementPolicy(seg, ptr, pagesCnt);
+    }
+}
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 67405cf..1024219 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
@@ -25,7 +25,6 @@ import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
 import java.util.concurrent.ArrayBlockingQueue;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.RejectedExecutionException;
@@ -42,6 +41,7 @@ import org.apache.ignite.IgniteLogger;
 import org.apache.ignite.IgniteSystemProperties;
 import org.apache.ignite.configuration.DataRegionConfiguration;
 import org.apache.ignite.configuration.DataStorageConfiguration;
+import org.apache.ignite.configuration.PageReplacementMode;
 import org.apache.ignite.events.EventType;
 import org.apache.ignite.events.PageReplacementStartedEvent;
 import org.apache.ignite.failure.FailureContext;
@@ -70,12 +70,9 @@ import org.apache.ignite.internal.processors.cache.persistence.DataRegionMetrics
 import org.apache.ignite.internal.processors.cache.persistence.PageStoreWriter;
 import org.apache.ignite.internal.processors.cache.persistence.StorageException;
 import org.apache.ignite.internal.processors.cache.persistence.checkpoint.CheckpointProgress;
-import org.apache.ignite.internal.processors.cache.persistence.freelist.io.PagesListMetaIO;
 import org.apache.ignite.internal.processors.cache.persistence.partstate.GroupPartitionId;
 import org.apache.ignite.internal.processors.cache.persistence.tree.io.DataPageIO;
 import org.apache.ignite.internal.processors.cache.persistence.tree.io.PageIO;
-import org.apache.ignite.internal.processors.cache.persistence.tree.io.PagePartitionCountersIO;
-import org.apache.ignite.internal.processors.cache.persistence.tree.io.PagePartitionMetaIO;
 import org.apache.ignite.internal.processors.cache.persistence.tree.io.TrackingPageIO;
 import org.apache.ignite.internal.processors.cache.persistence.wal.WALPointer;
 import org.apache.ignite.internal.processors.cache.persistence.wal.crc.IgniteDataIntegrityViolationException;
@@ -138,10 +135,10 @@ public class PageMemoryImpl implements PageMemoryEx {
     public static final long RELATIVE_PTR_MASK = 0xFFFFFFFFFFFFFFL;
 
     /** Invalid relative pointer value. */
-    public static final long INVALID_REL_PTR = RELATIVE_PTR_MASK;
+    static final long INVALID_REL_PTR = RELATIVE_PTR_MASK;
 
     /** Pointer which means that this page is outdated (for example, cache was destroyed, partition eviction'd happened */
-    private static final long OUTDATED_REL_PTR = INVALID_REL_PTR + 1;
+    static final long OUTDATED_REL_PTR = INVALID_REL_PTR + 1;
 
     /** Page lock offset. */
     public static final int PAGE_LOCK_OFFSET = 32;
@@ -157,9 +154,6 @@ public class PageMemoryImpl implements PageMemoryEx {
      */
     public static final int PAGE_OVERHEAD = 48;
 
-    /** Number of random pages that will be picked for eviction. */
-    public static final int RANDOM_PAGES_EVICT_NUM = 5;
-
     /** Try again tag. */
     public static final int TRY_AGAIN_TAG = -1;
 
@@ -192,6 +186,9 @@ public class PageMemoryImpl implements PageMemoryEx {
     private final boolean useBackwardShiftMap =
         IgniteSystemProperties.getBoolean(IGNITE_LOADED_PAGES_BACKWARD_SHIFT_MAP, DFLT_LOADED_PAGES_BACKWARD_SHIFT_MAP);
 
+    /** Page replacement policy factory. */
+    private final PageReplacementPolicyFactory pageReplacementPolicyFactory;
+
     /** */
     private final ExecutorService asyncRunner;
 
@@ -340,6 +337,28 @@ public class PageMemoryImpl implements PageMemoryEx {
             TimeUnit.SECONDS,
             new ArrayBlockingQueue<>(Runtime.getRuntime().availableProcessors()),
             new IgniteThreadFactory(ctx.igniteInstanceName(), "page-mem-op"));
+
+        DataRegionConfiguration memCfg = getDataRegionConfiguration();
+
+        PageReplacementMode pageReplacementMode = memCfg == null ? DataRegionConfiguration.DFLT_PAGE_REPLACEMENT_MODE :
+                memCfg.getPageReplacementMode();
+
+        switch (pageReplacementMode) {
+            case RANDOM_LRU:
+                pageReplacementPolicyFactory = new RandomLruPageReplacementPolicyFactory();
+
+                break;
+            case SEGMENTED_LRU:
+                pageReplacementPolicyFactory = new SegmentedLruPageReplacementPolicyFactory();
+
+                break;
+            case CLOCK:
+                pageReplacementPolicyFactory = new ClockPageReplacementPolicyFactory();
+
+                break;
+            default:
+                throw new IgniteException("Unexpected page replacement mode: " + pageReplacementMode);
+        }
     }
 
     /** {@inheritDoc} */
@@ -376,6 +395,7 @@ public class PageMemoryImpl implements PageMemoryEx {
             long totalAllocated = 0;
             int pages = 0;
             long totalTblSize = 0;
+            long totalReplSize = 0;
 
             for (int i = 0; i < regs - 1; i++) {
                 assert i < segments.length;
@@ -388,19 +408,21 @@ public class PageMemoryImpl implements PageMemoryEx {
 
                 pages += segments[i].pages();
                 totalTblSize += segments[i].tableSize();
+                totalReplSize += segments[i].replacementSize();
             }
 
             initWriteThrottle();
 
             this.segments = segments;
 
-            if (log.isInfoEnabled())
+            if (log.isInfoEnabled()) {
                 log.info("Started page memory [memoryAllocated=" + U.readableSize(totalAllocated, false) +
                     ", pages=" + pages +
                     ", tableSize=" + U.readableSize(totalTblSize, false) +
+                    ", replacementSize=" + U.readableSize(totalReplSize, false) +
                     ", checkpointBuffer=" + U.readableSize(checkpointBuf, false) +
                     ']');
-
+            }
         }
     }
 
@@ -533,9 +555,6 @@ public class PageMemoryImpl implements PageMemoryEx {
         // because there is no crc inside them.
         Segment seg = segment(grpId, pageId);
 
-        DelayedDirtyPageStoreWrite delayedWriter = delayedPageReplacementTracker != null
-            ? delayedPageReplacementTracker.delayedPageWrite() : null;
-
         seg.writeLock().lock();
 
         boolean isTrackingPage = changeTracker != null &&
@@ -555,14 +574,17 @@ public class PageMemoryImpl implements PageMemoryEx {
                 OUTDATED_REL_PTR
             );
 
-            if (relPtr == OUTDATED_REL_PTR)
-                relPtr = refreshOutdatedPage(seg, grpId, pageId, false);
+            if (relPtr == OUTDATED_REL_PTR) {
+                relPtr = seg.refreshOutdatedPage(grpId, pageId, false);
+
+                seg.pageReplacementPolicy.onRemove(relPtr);
+            }
 
             if (relPtr == INVALID_REL_PTR)
                 relPtr = seg.borrowOrAllocateFreePage(pageId);
 
             if (relPtr == INVALID_REL_PTR)
-                relPtr = seg.removePageForReplacement(delayedWriter == null ? flushDirtyPage : delayedWriter);
+                relPtr = seg.removePageForReplacement();
 
             long absPtr = seg.absolute(relPtr);
 
@@ -606,6 +628,8 @@ public class PageMemoryImpl implements PageMemoryEx {
                 }
             }
 
+            seg.pageReplacementPolicy.onMiss(relPtr);
+
             seg.loadedPages.put(grpId, PageIdUtils.effectivePageId(pageId), relPtr, seg.partGeneration(grpId, partId));
         }
         catch (IgniteOutOfMemoryException oom) {
@@ -617,7 +641,6 @@ public class PageMemoryImpl implements PageMemoryEx {
                 ", maxSize=" + U.readableSize(dataRegionCfg.getMaxSize(), false) +
                 ", persistenceEnabled=" + dataRegionCfg.isPersistenceEnabled() + "] Try the following:" + U.nl() +
                 "  ^-- Increase maximum off-heap memory size (DataRegionConfiguration.maxSize)" + U.nl() +
-                "  ^-- Enable Ignite persistence (DataRegionConfiguration.persistenceEnabled)" + U.nl() +
                 "  ^-- Enable eviction or expiration policies"
             );
 
@@ -631,9 +654,9 @@ public class PageMemoryImpl implements PageMemoryEx {
             seg.writeLock().unlock();
         }
 
-        //Finish replacement only when an exception wasn't thrown otherwise it possible to corrupt B+Tree.
-        if (delayedWriter != null)
-            delayedWriter.finishReplacement();
+        // Finish replacement only when an exception wasn't thrown otherwise it possible to corrupt B+Tree.
+        if (delayedPageReplacementTracker != null)
+            delayedPageReplacementTracker.delayedPageWrite().finishReplacement();
 
         //we have allocated 'tracking' page, we need to allocate regular one
         return isTrackingPage ? allocatePage(grpId, partId, flags) : pageId;
@@ -739,6 +762,8 @@ public class PageMemoryImpl implements PageMemoryEx {
 
                 seg.acquirePage(absPtr);
 
+                seg.pageReplacementPolicy.onHit(relPtr);
+
                 statHolder.trackLogicalRead(absPtr + PAGE_OVERHEAD);
 
                 return absPtr;
@@ -748,9 +773,6 @@ public class PageMemoryImpl implements PageMemoryEx {
             seg.readLock().unlock();
         }
 
-        DelayedDirtyPageStoreWrite delayedWriter = delayedPageReplacementTracker != null
-            ? delayedPageReplacementTracker.delayedPageWrite() : null;
-
         FullPageId fullId = new FullPageId(pageId, grpId);
 
         seg.writeLock().lock();
@@ -777,7 +799,7 @@ public class PageMemoryImpl implements PageMemoryEx {
                     pageAllocated.set(true);
 
                 if (relPtr == INVALID_REL_PTR)
-                    relPtr = seg.removePageForReplacement(delayedWriter == null ? flushDirtyPage : delayedWriter);
+                    relPtr = seg.removePageForReplacement();
 
                 absPtr = seg.absolute(relPtr);
 
@@ -791,6 +813,8 @@ public class PageMemoryImpl implements PageMemoryEx {
                 // We can clear dirty flag after the page has been allocated.
                 setDirty(fullId, absPtr, false, false);
 
+                seg.pageReplacementPolicy.onMiss(relPtr);
+
                 seg.loadedPages.put(
                     grpId,
                     fullId.effectivePageId(),
@@ -826,7 +850,7 @@ public class PageMemoryImpl implements PageMemoryEx {
             else if (relPtr == OUTDATED_REL_PTR) {
                 assert PageIdUtils.pageIndex(pageId) == 0 : fullId;
 
-                relPtr = refreshOutdatedPage(seg, grpId, pageId, false);
+                relPtr = seg.refreshOutdatedPage(grpId, pageId, false);
 
                 absPtr = seg.absolute(relPtr);
 
@@ -843,10 +867,16 @@ public class PageMemoryImpl implements PageMemoryEx {
                         ", absPtr=" + U.hexLong(absPtr) + ']';
 
                 rwLock.init(absPtr + PAGE_LOCK_OFFSET, PageIdUtils.tag(pageId));
+
+                seg.pageReplacementPolicy.onRemove(relPtr);
+                seg.pageReplacementPolicy.onMiss(relPtr);
             }
-            else
+            else {
                 absPtr = seg.absolute(relPtr);
 
+                seg.pageReplacementPolicy.onHit(relPtr);
+            }
+
             seg.acquirePage(absPtr);
 
             if (!readPageFromStore)
@@ -862,8 +892,8 @@ public class PageMemoryImpl implements PageMemoryEx {
         finally {
             seg.writeLock().unlock();
 
-            if (delayedWriter != null)
-                delayedWriter.finishReplacement();
+            if (delayedPageReplacementTracker != null)
+                delayedPageReplacementTracker.delayedPageWrite().finishReplacement();
 
             if (readPageFromStore) {
                 assert lockedPageAbsPtr != -1 : "Page is expected to have a valid address [pageId=" + fullId +
@@ -906,57 +936,6 @@ public class PageMemoryImpl implements PageMemoryEx {
         }
     }
 
-    /**
-     * @param seg Segment.
-     * @param grpId Cache group ID.
-     * @param pageId Page ID.
-     * @param rmv {@code True} if page should be removed.
-     * @return Relative pointer to refreshed page.
-     */
-    private long refreshOutdatedPage(Segment seg, int grpId, long pageId, boolean rmv) {
-        assert seg.writeLock().isHeldByCurrentThread();
-
-        int tag = seg.partGeneration(grpId, PageIdUtils.partId(pageId));
-
-        long relPtr = seg.loadedPages.refresh(grpId, PageIdUtils.effectivePageId(pageId), tag);
-
-        long absPtr = seg.absolute(relPtr);
-
-        GridUnsafe.setMemory(absPtr + PAGE_OVERHEAD, pageSize(), (byte)0);
-
-        PageHeader.dirty(absPtr, false);
-
-        long tmpBufPtr = PageHeader.tempBufferPointer(absPtr);
-
-        if (tmpBufPtr != INVALID_REL_PTR) {
-            GridUnsafe.setMemory(checkpointPool.absolute(tmpBufPtr) + PAGE_OVERHEAD, pageSize(), (byte)0);
-
-            PageHeader.tempBufferPointer(absPtr, INVALID_REL_PTR);
-
-            // We pinned the page when allocated the temp buffer, release it now.
-            PageHeader.releasePage(absPtr);
-
-            releaseCheckpointBufferPage(tmpBufPtr);
-        }
-
-        if (rmv)
-            seg.loadedPages.remove(grpId, PageIdUtils.effectivePageId(pageId));
-
-        CheckpointPages cpPages = seg.checkpointPages;
-
-        if (cpPages != null)
-            cpPages.markAsSaved(new FullPageId(pageId, grpId));
-
-        Collection<FullPageId> dirtyPages = seg.dirtyPages;
-
-        if (dirtyPages != null) {
-            if (dirtyPages.remove(new FullPageId(pageId, grpId)))
-                seg.dirtyPagesCntr.decrementAndGet();
-        }
-
-        return relPtr;
-    }
-
     /** */
     private void releaseCheckpointBufferPage(long tmpBufPtr) {
         int resCntr = checkpointPool.releaseFreePage(tmpBufPtr);
@@ -1245,13 +1224,14 @@ public class PageMemoryImpl implements PageMemoryEx {
                     return;
 
                 if (relPtr == OUTDATED_REL_PTR) {
-                    relPtr = refreshOutdatedPage(
-                        seg,
+                    relPtr = seg.refreshOutdatedPage(
                         fullId.groupId(),
                         fullId.effectivePageId(),
                         true
                     );
 
+                    seg.pageReplacementPolicy.onRemove(relPtr);
+
                     seg.pool.releaseFreePage(relPtr);
                 }
 
@@ -1973,13 +1953,10 @@ public class PageMemoryImpl implements PageMemoryEx {
     /**
      *
      */
-    private class Segment extends ReentrantReadWriteLock {
+    class Segment extends ReentrantReadWriteLock {
         /** */
         private static final long serialVersionUID = 0L;
 
-        /** */
-        private static final double FULL_SCAN_THRESHOLD = 0.4;
-
         /** Pointer to acquired pages integer counter. */
         private static final int ACQUIRED_PAGES_SIZEOF = 4;
 
@@ -1995,9 +1972,15 @@ public class PageMemoryImpl implements PageMemoryEx {
         /** */
         private PagePool pool;
 
+        /** */
+        private final PageReplacementPolicy pageReplacementPolicy;
+
         /** Bytes required to store {@link #loadedPages}. */
         private long memPerTbl;
 
+        /** Bytes required to store {@link #pageReplacementPolicy} service data. */
+        private long memPerRepl;
+
         /** Pages marked as dirty since the last checkpoint. */
         private volatile Collection<FullPageId> dirtyPages = new GridConcurrentHashSet<>();
 
@@ -2038,16 +2021,23 @@ public class PageMemoryImpl implements PageMemoryEx {
 
             memPerTbl = useBackwardShiftMap
                 ? RobinHoodBackwardShiftHashMap.requiredMemory(pages)
-                : requiredSegmentTableMemory(pages);
+                : FullPageIdTable.requiredMemory(pages);
 
             loadedPages = useBackwardShiftMap
                 ? new RobinHoodBackwardShiftHashMap(ldPagesAddr, memPerTbl)
                 : new FullPageIdTable(ldPagesAddr, memPerTbl, true);
 
-            DirectMemoryRegion poolRegion = region.slice(memPerTbl + ldPagesMapOffInRegion);
+            pages = (int)((totalMemory - memPerTbl - ldPagesMapOffInRegion) / sysPageSize);
+
+            memPerRepl = pageReplacementPolicyFactory.requiredMemory(pages);
+
+            DirectMemoryRegion poolRegion = region.slice(memPerTbl + memPerRepl + ldPagesMapOffInRegion);
 
             pool = new PagePool(idx, poolRegion, sysPageSize, rwLock);
 
+            pageReplacementPolicy = pageReplacementPolicyFactory.create(this,
+                    region.address() + memPerTbl + ldPagesMapOffInRegion, pool.pages());
+
             maxDirtyPages = throttlingPlc != ThrottlingPolicy.DISABLED
                 ? pool.pages() * 3L / 4
                 : Math.min(pool.pages() * 2L / 3, cpPoolPages);
@@ -2096,6 +2086,13 @@ public class PageMemoryImpl implements PageMemoryEx {
         }
 
         /**
+         * @return Memory allocated for page replacement service data.
+         */
+        private long replacementSize() {
+            return memPerRepl;
+        }
+
+        /**
          * @param absPtr Page absolute address to acquire.
          */
         private void acquirePage(long absPtr) {
@@ -2142,11 +2139,10 @@ public class PageMemoryImpl implements PageMemoryEx {
          *
          * @param fullPageId Candidate page full ID.
          * @param absPtr Absolute pointer of the page to evict.
-         * @param saveDirtyPage implementation to save dirty page to persistent storage.
          * @return {@code True} if it is ok to replace this page, {@code false} if another page should be selected.
          * @throws IgniteCheckedException If failed to write page to the underlying store during eviction.
          */
-        private boolean preparePageRemoval(FullPageId fullPageId, long absPtr, PageStoreWriter saveDirtyPage) throws IgniteCheckedException {
+        public boolean tryToRemovePage(FullPageId fullPageId, long absPtr) throws IgniteCheckedException {
             assert writeLock().isHeldByCurrentThread();
 
             // Do not evict cache meta pages.
@@ -2167,6 +2163,9 @@ public class PageMemoryImpl implements PageMemoryEx {
 
                     memMetrics.updatePageReplaceRate(U.currentTimeMillis() - PageHeader.readTimestamp(absPtr));
 
+                    PageStoreWriter saveDirtyPage = delayedPageReplacementTracker != null
+                        ? delayedPageReplacementTracker.delayedPageWrite() : flushDirtyPage;
+
                     saveDirtyPage.writePage(
                         fullPageId,
                         wrapPointer(absPtr + PAGE_OVERHEAD, pageSize()),
@@ -2180,6 +2179,8 @@ public class PageMemoryImpl implements PageMemoryEx {
 
                     checkpointPages.markAsSaved(fullPageId);
 
+                    loadedPages.remove(fullPageId.groupId(), fullPageId.effectivePageId());
+
                     return true;
                 }
 
@@ -2188,6 +2189,8 @@ public class PageMemoryImpl implements PageMemoryEx {
             else {
                 memMetrics.updatePageReplaceRate(U.currentTimeMillis() - PageHeader.readTimestamp(absPtr));
 
+                loadedPages.remove(fullPageId.groupId(), fullPageId.effectivePageId());
+
                 // Page was not modified, ok to evict.
                 return true;
             }
@@ -2232,13 +2235,62 @@ public class PageMemoryImpl implements PageMemoryEx {
         }
 
         /**
+         * @param grpId Cache group ID.
+         * @param pageId Page ID.
+         * @param rmv {@code True} if page should be removed.
+         * @return Relative pointer to refreshed page.
+         */
+        public long refreshOutdatedPage(int grpId, long pageId, boolean rmv) {
+            assert writeLock().isHeldByCurrentThread();
+
+            int tag = partGeneration(grpId, PageIdUtils.partId(pageId));
+
+            long relPtr = loadedPages.refresh(grpId, PageIdUtils.effectivePageId(pageId), tag);
+
+            long absPtr = absolute(relPtr);
+
+            GridUnsafe.setMemory(absPtr + PAGE_OVERHEAD, pageSize(), (byte)0);
+
+            PageHeader.dirty(absPtr, false);
+
+            long tmpBufPtr = PageHeader.tempBufferPointer(absPtr);
+
+            if (tmpBufPtr != INVALID_REL_PTR) {
+                GridUnsafe.setMemory(checkpointPool.absolute(tmpBufPtr) + PAGE_OVERHEAD, pageSize(), (byte)0);
+
+                PageHeader.tempBufferPointer(absPtr, INVALID_REL_PTR);
+
+                // We pinned the page when allocated the temp buffer, release it now.
+                PageHeader.releasePage(absPtr);
+
+                releaseCheckpointBufferPage(tmpBufPtr);
+            }
+
+            if (rmv)
+                loadedPages.remove(grpId, PageIdUtils.effectivePageId(pageId));
+
+            CheckpointPages cpPages = checkpointPages;
+
+            if (cpPages != null)
+                cpPages.markAsSaved(new FullPageId(pageId, grpId));
+
+            Collection<FullPageId> dirtyPages = this.dirtyPages;
+
+            if (dirtyPages != null) {
+                if (dirtyPages.remove(new FullPageId(pageId, grpId)))
+                    dirtyPagesCntr.decrementAndGet();
+            }
+
+            return relPtr;
+        }
+
+        /**
          * Removes random oldest page for page replacement from memory to storage.
          *
          * @return Relative address for removed page, now it can be replaced by allocated or reloaded page.
          * @throws IgniteCheckedException If failed to evict page.
-         * @param saveDirtyPage Replaced page writer, implementation to save dirty page to persistent storage.
          */
-        private long removePageForReplacement(PageStoreWriter saveDirtyPage) throws IgniteCheckedException {
+        private long removePageForReplacement() throws IgniteCheckedException {
             assert getWriteHoldCount() > 0;
 
             if (pageReplacementWarned == 0) {
@@ -2262,243 +2314,33 @@ public class PageMemoryImpl implements PageMemoryEx {
                 }
             }
 
-            final ThreadLocalRandom rnd = ThreadLocalRandom.current();
-
-            final int cap = loadedPages.capacity();
-
-            if (acquiredPages() >= loadedPages.size()) {
-                DataRegionConfiguration dataRegionCfg = getDataRegionConfiguration();
-
-                throw new IgniteOutOfMemoryException("Failed to evict page from segment (all pages are acquired)."
-                    + U.nl() + "Out of memory in data region [" +
-                    "name=" + dataRegionCfg.getName() +
-                    ", initSize=" + U.readableSize(dataRegionCfg.getInitialSize(), false) +
-                    ", maxSize=" + U.readableSize(dataRegionCfg.getMaxSize(), false) +
-                    ", persistenceEnabled=" + dataRegionCfg.isPersistenceEnabled() + "] Try the following:" + U.nl() +
-                    "  ^-- Increase maximum off-heap memory size (DataRegionConfiguration.maxSize)" + U.nl() +
-                    "  ^-- Enable Ignite persistence (DataRegionConfiguration.persistenceEnabled)" + U.nl() +
-                    "  ^-- Enable eviction or expiration policies"
-                );
-            }
-
-            // With big number of random picked pages we may fall into infinite loop, because
-            // every time the same page may be found.
-            Set<Long> ignored = null;
-
-            long relRmvAddr = INVALID_REL_PTR;
-
-            int iterations = 0;
-
-            while (true) {
-                long cleanAddr = INVALID_REL_PTR;
-                long cleanTs = Long.MAX_VALUE;
-                long dirtyAddr = INVALID_REL_PTR;
-                long dirtyTs = Long.MAX_VALUE;
-                long metaAddr = INVALID_REL_PTR;
-                long metaTs = Long.MAX_VALUE;
-
-                for (int i = 0; i < RANDOM_PAGES_EVICT_NUM; i++) {
-                    ++iterations;
-
-                    if (iterations > pool.pages() * FULL_SCAN_THRESHOLD)
-                        break;
-
-                    // We need to lookup for pages only in current segment for thread safety,
-                    // so peeking random memory will lead to checking for found page segment.
-                    // It's much faster to check available pages for segment right away.
-                    ReplaceCandidate nearest = loadedPages.getNearestAt(rnd.nextInt(cap));
-
-                    assert nearest != null && nearest.relativePointer() != INVALID_REL_PTR;
-
-                    long rndAddr = nearest.relativePointer();
-
-                    int partGen = nearest.generation();
-
-                    final long absPageAddr = absolute(rndAddr);
-
-                    FullPageId fullId = PageHeader.fullPageId(absPageAddr);
-
-                    // Check page mapping consistency.
-                    assert fullId.equals(nearest.fullId()) : "Invalid page mapping [tableId=" + nearest.fullId() +
-                        ", actual=" + fullId + ", nearest=" + nearest;
-
-                    boolean outdated = partGen < partGeneration(fullId.groupId(), PageIdUtils.partId(fullId.pageId()));
-
-                    if (outdated)
-                        return refreshOutdatedPage(this, fullId.groupId(), fullId.pageId(), true);
-
-                    boolean pinned = PageHeader.isAcquired(absPageAddr);
-
-                    boolean skip = ignored != null && ignored.contains(rndAddr);
-
-                    final boolean dirty = isDirty(absPageAddr);
-
-                    CheckpointPages checkpointPages = this.checkpointPages;
-
-                    if (relRmvAddr == rndAddr || pinned || skip ||
-                        fullId.pageId() == META_PAGE_ID ||
-                        (dirty && (checkpointPages == null || !checkpointPages.contains(fullId)))
-                    ) {
-                        i--;
-
-                        continue;
-                    }
-
-                    final long pageTs = PageHeader.readTimestamp(absPageAddr);
-
-                    final boolean storMeta = isStoreMetadataPage(absPageAddr);
-
-                    if (pageTs < cleanTs && !dirty && !storMeta) {
-                        cleanAddr = rndAddr;
-
-                        cleanTs = pageTs;
-                    }
-                    else if (pageTs < dirtyTs && dirty && !storMeta) {
-                        dirtyAddr = rndAddr;
-
-                        dirtyTs = pageTs;
-                    }
-                    else if (pageTs < metaTs && storMeta) {
-                        metaAddr = rndAddr;
-
-                        metaTs = pageTs;
-                    }
-
-                    if (cleanAddr != INVALID_REL_PTR)
-                        relRmvAddr = cleanAddr;
-                    else if (dirtyAddr != INVALID_REL_PTR)
-                        relRmvAddr = dirtyAddr;
-                    else
-                        relRmvAddr = metaAddr;
-                }
-
-                if (relRmvAddr == INVALID_REL_PTR)
-                    return tryToFindSequentially(cap, saveDirtyPage);
-
-                final long absRmvAddr = absolute(relRmvAddr);
-
-                final FullPageId fullPageId = PageHeader.fullPageId(absRmvAddr);
-
-                if (!preparePageRemoval(fullPageId, absRmvAddr, saveDirtyPage)) {
-                    if (iterations > 10) {
-                        if (ignored == null)
-                            ignored = new HashSet<>();
-
-                        ignored.add(relRmvAddr);
-                    }
-
-                    if (iterations > pool.pages() * FULL_SCAN_THRESHOLD)
-                        return tryToFindSequentially(cap, saveDirtyPage);
-
-                    continue;
-                }
-
-                loadedPages.remove(
-                    fullPageId.groupId(),
-                    fullPageId.effectivePageId()
-                );
-
-                return relRmvAddr;
-            }
-        }
-
-        /**
-         * @param absPageAddr Absolute page address
-         * @return {@code True} if page is related to partition metadata, which is loaded in saveStoreMetadata().
-         */
-        private boolean isStoreMetadataPage(long absPageAddr) {
-            try {
-                long dataAddr = absPageAddr + PAGE_OVERHEAD;
+            if (acquiredPages() >= loadedPages.size())
+                throw oomException("all pages are acquired");
 
-                int type = PageIO.getType(dataAddr);
-                int ver = PageIO.getVersion(dataAddr);
-
-                PageIO io = PageIO.getPageIO(type, ver);
-
-                return io instanceof PagePartitionMetaIO
-                    || io instanceof PagesListMetaIO
-                    || io instanceof PagePartitionCountersIO;
-            }
-            catch (IgniteCheckedException ignored) {
-                return false;
-            }
+            return pageReplacementPolicy.replace();
         }
 
         /**
-         * Will scan all segment pages to find one to evict it
+         * Creates out of memory exception with additional information.
          *
-         * @param cap Capacity.
-         * @param saveDirtyPage Evicted page writer.
+         * @param reason Reason.
          */
-        private long tryToFindSequentially(int cap, PageStoreWriter saveDirtyPage) throws IgniteCheckedException {
-            assert getWriteHoldCount() > 0;
-
-            long prevAddr = INVALID_REL_PTR;
-            int pinnedCnt = 0;
-            int failToPrepare = 0;
-
-            for (int i = 0; i < cap; i++) {
-                final ReplaceCandidate nearest = loadedPages.getNearestAt(i);
-
-                assert nearest != null && nearest.relativePointer() != INVALID_REL_PTR;
-
-                final long addr = nearest.relativePointer();
-
-                int partGen = nearest.generation();
-
-                final long absPageAddr = absolute(addr);
-
-                FullPageId fullId = PageHeader.fullPageId(absPageAddr);
-
-                if (partGen < partGeneration(fullId.groupId(), PageIdUtils.partId(fullId.pageId())))
-                    return refreshOutdatedPage(this, fullId.groupId(), fullId.pageId(), true);
-
-                boolean pinned = PageHeader.isAcquired(absPageAddr);
-
-                if (pinned)
-                    pinnedCnt++;
-
-                if (addr == prevAddr || pinned)
-                    continue;
-
-                final long absEvictAddr = absolute(addr);
-
-                final FullPageId fullPageId = PageHeader.fullPageId(absEvictAddr);
-
-                if (preparePageRemoval(fullPageId, absEvictAddr, saveDirtyPage)) {
-                    loadedPages.remove(
-                        fullPageId.groupId(),
-                        fullPageId.effectivePageId()
-                    );
-
-                    return addr;
-                }
-                else
-                    failToPrepare++;
-
-                prevAddr = addr;
-            }
-
+        public IgniteOutOfMemoryException oomException(String reason) {
             DataRegionConfiguration dataRegionCfg = getDataRegionConfiguration();
 
-            throw new IgniteOutOfMemoryException("Failed to find a page for eviction [segmentCapacity=" + cap +
+            return new IgniteOutOfMemoryException("Failed to find a page for eviction (" + reason + ") [" +
+                "segmentCapacity=" + loadedPages.capacity() +
                 ", loaded=" + loadedPages.size() +
                 ", maxDirtyPages=" + maxDirtyPages +
                 ", dirtyPages=" + dirtyPagesCntr +
-                ", cpPages=" + (checkpointPages == null ? 0 : checkpointPages.size()) +
-                ", pinnedInSegment=" + pinnedCnt +
-                ", failedToPrepare=" + failToPrepare +
+                ", cpPages=" + (checkpointPages() == null ? 0 : checkpointPages().size()) +
+                ", pinned=" + acquiredPages() +
                 ']' + U.nl() + "Out of memory in data region [" +
-                (dataRegionCfg == null ? "NULL" : (
-                    "name=" + dataRegionCfg.getName() +
-                    ", initSize=" + U.readableSize(dataRegionCfg.getInitialSize(), false) +
-                    ", maxSize=" + U.readableSize(dataRegionCfg.getMaxSize(), false) +
-                    ", persistenceEnabled=" + dataRegionCfg.isPersistenceEnabled()
-                )) +
-                "]" +
-                " Try the following:" + U.nl() +
+                "name=" + dataRegionCfg.getName() +
+                ", initSize=" + U.readableSize(dataRegionCfg.getInitialSize(), false) +
+                ", maxSize=" + U.readableSize(dataRegionCfg.getMaxSize(), false) +
+                ", persistenceEnabled=" + dataRegionCfg.isPersistenceEnabled() + "] Try the following:" + U.nl() +
                 "  ^-- Increase maximum off-heap memory size (DataRegionConfiguration.maxSize)" + U.nl() +
-                "  ^-- Enable Ignite persistence (DataRegionConfiguration.persistenceEnabled)" + U.nl() +
                 "  ^-- Enable eviction or expiration policies"
             );
         }
@@ -2509,16 +2351,36 @@ public class PageMemoryImpl implements PageMemoryEx {
          * @param relPtr Relative pointer.
          * @return Absolute pointer.
          */
-        private long absolute(long relPtr) {
+        public long absolute(long relPtr) {
             return pool.absolute(relPtr);
         }
 
         /**
+         * Delegate to the corresponding page pool.
+         *
+         * @param pageIdx Page index.
+         * @return Relative pointer.
+         */
+        public long relative(long pageIdx) {
+            return pool.relative(pageIdx);
+        }
+
+        /**
+         * Delegate to the corresponding page pool.
+         *
+         * @param relPtr Relative pointer.
+         * @return Page index in the pool.
+         */
+        public long pageIndex(long relPtr) {
+            return pool.pageIndex(relPtr);
+        }
+
+        /**
          * @param grpId Cache group ID.
          * @param partId Partition ID.
          * @return Partition generation. Growing, 1-based partition version. Changed
          */
-        private int partGeneration(int grpId, int partId) {
+        public int partGeneration(int grpId, int partId) {
             assert getReadHoldCount() > 0 || getWriteHoldCount() > 0;
 
             Integer tag = partGenerationMap.get(new GroupPartitionId(grpId, partId));
@@ -2529,6 +2391,27 @@ public class PageMemoryImpl implements PageMemoryEx {
         }
 
         /**
+         * Gets loaded pages map.
+         */
+        public LoadedPagesMap loadedPages() {
+            return loadedPages;
+        }
+
+        /**
+         * Gets checkpoint pages.
+         */
+        public CheckpointPages checkpointPages() {
+            return checkpointPages;
+        }
+
+        /**
+         * Gets page pool.
+         */
+        public PagePool pool() {
+            return pool;
+        }
+
+        /**
          * Increments partition generation due to partition invalidation (e.g. partition was rebalanced to other node
          * and evicted).
          *
@@ -2571,17 +2454,6 @@ public class PageMemoryImpl implements PageMemoryEx {
     }
 
     /**
-     * Gets an estimate for the amount of memory required to store the given number of page IDs
-     * in a segment table.
-     *
-     * @param pages Number of pages to store.
-     * @return Memory size estimate.
-     */
-    private static long requiredSegmentTableMemory(int pages) {
-        return FullPageIdTable.requiredMemory(pages) + 8;
-    }
-
-    /**
      * @param ptr Pointer to update.
      * @param delta Delta.
      */
@@ -2687,6 +2559,8 @@ public class PageMemoryImpl implements PageMemoryEx {
 
                         GridUnsafe.setMemory(absPtr + PAGE_OVERHEAD, pageSize, (byte)0);
 
+                        seg.pageReplacementPolicy.onRemove(relPtr);
+
                         seg.pool.releaseFreePage(relPtr);
                     }
 
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/PagePool.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/PagePool.java
index 5e4c80b..e399c66 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/PagePool.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/PagePool.java
@@ -232,6 +232,14 @@ public class PagePool {
     }
 
     /**
+     * @param relPtr Relative pointer.
+     * @return Page index in the pool.
+     */
+    long pageIndex(long relPtr) {
+        return relPtr & ~SEGMENT_INDEX_MASK;
+    }
+
+    /**
      * @return Max number of pages in the pool.
      */
     public int pages() {
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/PageReplacementPolicy.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/PageReplacementPolicy.java
new file mode 100644
index 0000000..0d9881b
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/PageReplacementPolicy.java
@@ -0,0 +1,69 @@
+/*
+ * 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.IgniteCheckedException;
+
+/**
+ * Abstract page replacement policy.
+ */
+public abstract class PageReplacementPolicy {
+    /** Page memory segment. */
+    protected final PageMemoryImpl.Segment seg;
+
+    /**
+     * @param seg Page memory segment.
+     */
+    protected PageReplacementPolicy(PageMemoryImpl.Segment seg) {
+        this.seg = seg;
+    }
+
+    /**
+     * Existing page touched.
+     *
+     * Note: This method can be invoked under segment write lock or segment read lock.
+     */
+    public void onHit(long relPtr) {
+        // No-op.
+    }
+
+    /**
+     * New page added.
+     *
+     * Note: This method always invoked under segment write lock.
+     */
+    public void onMiss(long relPtr) {
+        // No-op.
+    }
+
+    /**
+     * Page removed from the page memory.
+     */
+    public void onRemove(long relPtr) {
+        // No-op.
+    }
+
+    /**
+     * Finds page to replace.
+     *
+     * Note: This method always invoked under segment write lock.
+     *
+     * @return Relative pointer to page.
+     */
+    public abstract long replace() throws IgniteCheckedException;
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/PageReplacementPolicyFactory.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/PageReplacementPolicyFactory.java
new file mode 100644
index 0000000..5808a5a
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/PageReplacementPolicyFactory.java
@@ -0,0 +1,39 @@
+/*
+ * 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;
+
+/**
+ * Page replacement policy factory.
+ */
+public interface PageReplacementPolicyFactory {
+    /**
+     * Calculaete amount of memory required to service {@code pagesCnt} pages.
+     *
+     * @param pagesCnt Pages count.
+     */
+    public long requiredMemory(int pagesCnt);
+
+    /**
+     * Create page replacement policy.
+     *
+     * @param seg Page memory segment.
+     * @param ptr Pointer to memory region.
+     * @param pagesCnt Pages count.
+     */
+    public PageReplacementPolicy create(PageMemoryImpl.Segment seg, long ptr, int pagesCnt);
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/RandomLruPageReplacementPolicy.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/RandomLruPageReplacementPolicy.java
new file mode 100644
index 0000000..3ffc260
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/RandomLruPageReplacementPolicy.java
@@ -0,0 +1,247 @@
+/*
+ * 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 java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.ThreadLocalRandom;
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.configuration.PageReplacementMode;
+import org.apache.ignite.internal.pagemem.FullPageId;
+import org.apache.ignite.internal.pagemem.PageIdUtils;
+import org.apache.ignite.internal.processors.cache.persistence.freelist.io.PagesListMetaIO;
+import org.apache.ignite.internal.processors.cache.persistence.tree.io.PageIO;
+import org.apache.ignite.internal.processors.cache.persistence.tree.io.PagePartitionCountersIO;
+import org.apache.ignite.internal.processors.cache.persistence.tree.io.PagePartitionMetaIO;
+
+import static org.apache.ignite.internal.pagemem.PageIdAllocator.META_PAGE_ID;
+import static org.apache.ignite.internal.processors.cache.persistence.pagemem.PageMemoryImpl.INVALID_REL_PTR;
+import static org.apache.ignite.internal.processors.cache.persistence.pagemem.PageMemoryImpl.PAGE_OVERHEAD;
+
+/**
+ * Random-LRU page replacement policy implementation.
+ *
+ * @see PageReplacementMode#RANDOM_LRU
+ */
+public class RandomLruPageReplacementPolicy extends PageReplacementPolicy {
+    /** Number of random pages that will be picked for eviction. */
+    public static final int RANDOM_PAGES_EVICT_NUM = 5;
+
+    /** */
+    private static final double FULL_SCAN_THRESHOLD = 0.4;
+
+    /**
+     * @param seg Page memory segment.
+     */
+    protected RandomLruPageReplacementPolicy(PageMemoryImpl.Segment seg) {
+        super(seg);
+    }
+
+    /** {@inheritDoc} */
+    @Override public long replace() throws IgniteCheckedException {
+        final ThreadLocalRandom rnd = ThreadLocalRandom.current();
+
+        LoadedPagesMap loadedPages = seg.loadedPages();
+        PagePool pool = seg.pool();
+
+        final int cap = loadedPages.capacity();
+
+        // With big number of random picked pages we may fall into infinite loop, because
+        // every time the same page may be found.
+        Set<Long> ignored = null;
+
+        long relRmvAddr = INVALID_REL_PTR;
+
+        int iterations = 0;
+
+        while (true) {
+            long cleanAddr = INVALID_REL_PTR;
+            long cleanTs = Long.MAX_VALUE;
+            long dirtyAddr = INVALID_REL_PTR;
+            long dirtyTs = Long.MAX_VALUE;
+            long metaAddr = INVALID_REL_PTR;
+            long metaTs = Long.MAX_VALUE;
+
+            for (int i = 0; i < RANDOM_PAGES_EVICT_NUM; i++) {
+                ++iterations;
+
+                if (iterations > pool.pages() * FULL_SCAN_THRESHOLD)
+                    break;
+
+                // We need to lookup for pages only in current segment for thread safety,
+                // so peeking random memory will lead to checking for found page segment.
+                // It's much faster to check available pages for segment right away.
+                ReplaceCandidate nearest = loadedPages.getNearestAt(rnd.nextInt(cap));
+
+                assert nearest != null && nearest.relativePointer() != INVALID_REL_PTR;
+
+                long rndAddr = nearest.relativePointer();
+
+                int partGen = nearest.generation();
+
+                final long absPageAddr = seg.absolute(rndAddr);
+
+                FullPageId fullId = PageHeader.fullPageId(absPageAddr);
+
+                // Check page mapping consistency.
+                assert fullId.equals(nearest.fullId()) : "Invalid page mapping [tableId=" + nearest.fullId() +
+                    ", actual=" + fullId + ", nearest=" + nearest;
+
+                boolean outdated = partGen < seg.partGeneration(fullId.groupId(), PageIdUtils.partId(fullId.pageId()));
+
+                if (outdated)
+                    return seg.refreshOutdatedPage(fullId.groupId(), fullId.pageId(), true);
+
+                boolean pinned = PageHeader.isAcquired(absPageAddr);
+
+                boolean skip = ignored != null && ignored.contains(rndAddr);
+
+                final boolean dirty = PageHeader.dirty(absPageAddr);
+
+                CheckpointPages checkpointPages = seg.checkpointPages();
+
+                if (relRmvAddr == rndAddr || pinned || skip ||
+                    fullId.pageId() == META_PAGE_ID ||
+                    (dirty && (checkpointPages == null || !checkpointPages.contains(fullId)))
+                ) {
+                    i--;
+
+                    continue;
+                }
+
+                final long pageTs = PageHeader.readTimestamp(absPageAddr);
+
+                final boolean storMeta = isStoreMetadataPage(absPageAddr);
+
+                if (pageTs < cleanTs && !dirty && !storMeta) {
+                    cleanAddr = rndAddr;
+
+                    cleanTs = pageTs;
+                }
+                else if (pageTs < dirtyTs && dirty && !storMeta) {
+                    dirtyAddr = rndAddr;
+
+                    dirtyTs = pageTs;
+                }
+                else if (pageTs < metaTs && storMeta) {
+                    metaAddr = rndAddr;
+
+                    metaTs = pageTs;
+                }
+
+                if (cleanAddr != INVALID_REL_PTR)
+                    relRmvAddr = cleanAddr;
+                else if (dirtyAddr != INVALID_REL_PTR)
+                    relRmvAddr = dirtyAddr;
+                else
+                    relRmvAddr = metaAddr;
+            }
+
+            if (relRmvAddr == INVALID_REL_PTR)
+                return tryToFindSequentially(cap);
+
+            final long absRmvAddr = seg.absolute(relRmvAddr);
+
+            final FullPageId fullPageId = PageHeader.fullPageId(absRmvAddr);
+
+            if (!seg.tryToRemovePage(fullPageId, absRmvAddr)) {
+                if (iterations > 10) {
+                    if (ignored == null)
+                        ignored = new HashSet<>();
+
+                    ignored.add(relRmvAddr);
+                }
+
+                if (iterations > seg.pool().pages() * FULL_SCAN_THRESHOLD)
+                    return tryToFindSequentially(cap);
+
+                continue;
+            }
+
+            return relRmvAddr;
+        }
+    }
+
+    /**
+     * @param absPageAddr Absolute page address
+     * @return {@code True} if page is related to partition metadata, which is loaded in saveStoreMetadata().
+     */
+    private static boolean isStoreMetadataPage(long absPageAddr) {
+        try {
+            long dataAddr = absPageAddr + PAGE_OVERHEAD;
+
+            int type = PageIO.getType(dataAddr);
+            int ver = PageIO.getVersion(dataAddr);
+
+            PageIO io = PageIO.getPageIO(type, ver);
+
+            return io instanceof PagePartitionMetaIO
+                || io instanceof PagesListMetaIO
+                || io instanceof PagePartitionCountersIO;
+        }
+        catch (IgniteCheckedException ignored) {
+            return false;
+        }
+    }
+
+    /**
+     * Will scan all segment pages to find one to evict it.
+     *
+     * @param cap Capacity.
+     */
+    private long tryToFindSequentially(int cap) throws IgniteCheckedException {
+        assert seg.getWriteHoldCount() > 0;
+
+        long prevAddr = INVALID_REL_PTR;
+
+        LoadedPagesMap loadedPages = seg.loadedPages();
+
+        for (int i = 0; i < cap; i++) {
+            final ReplaceCandidate nearest = loadedPages.getNearestAt(i);
+
+            assert nearest != null && nearest.relativePointer() != INVALID_REL_PTR;
+
+            final long addr = nearest.relativePointer();
+
+            int partGen = nearest.generation();
+
+            final long absPageAddr = seg.absolute(addr);
+
+            FullPageId fullId = PageHeader.fullPageId(absPageAddr);
+
+            if (partGen < seg.partGeneration(fullId.groupId(), PageIdUtils.partId(fullId.pageId())))
+                return seg.refreshOutdatedPage(fullId.groupId(), fullId.pageId(), true);
+
+            boolean pinned = PageHeader.isAcquired(absPageAddr);
+
+            if (addr == prevAddr || pinned)
+                continue;
+
+            final long absEvictAddr = seg.absolute(addr);
+
+            final FullPageId fullPageId = PageHeader.fullPageId(absEvictAddr);
+
+            if (seg.tryToRemovePage(fullPageId, absEvictAddr))
+                return addr;
+
+            prevAddr = addr;
+        }
+
+        throw seg.oomException("no pages to replace");
+    }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/RandomLruPageReplacementPolicyFactory.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/RandomLruPageReplacementPolicyFactory.java
new file mode 100644
index 0000000..b2a9b3d
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/RandomLruPageReplacementPolicyFactory.java
@@ -0,0 +1,33 @@
+/*
+ * 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;
+
+/**
+ * {@link RandomLruPageReplacementPolicy} factory.
+ */
+public class RandomLruPageReplacementPolicyFactory implements PageReplacementPolicyFactory {
+    /** {@inheritDoc} */
+    @Override public long requiredMemory(int pagesCnt) {
+        return 0;
+    }
+
+    /** {@inheritDoc} */
+    @Override public PageReplacementPolicy create(PageMemoryImpl.Segment seg, long ptr, int pagesCnt) {
+        return new RandomLruPageReplacementPolicy(seg);
+    }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/SegmentedLruPageList.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/SegmentedLruPageList.java
new file mode 100644
index 0000000..725fd39
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/SegmentedLruPageList.java
@@ -0,0 +1,364 @@
+/*
+ * 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.configuration.PageReplacementMode;
+import org.apache.ignite.internal.util.GridUnsafe;
+
+/**
+ * Pages Segmented-LRU (SLRU) list implementation.
+ *
+ * @see PageReplacementMode#SEGMENTED_LRU
+ */
+public class SegmentedLruPageList {
+    /** Ratio to limit count of protected pages. */
+    private static final double PROTECTED_TO_TOTAL_PAGES_RATIO = 0.5;
+
+    /** Null page index. */
+    static final int NULL_IDX = -1;
+
+    /** Index of the head page of LRU list. */
+    private int headIdx = NULL_IDX;
+
+    /** Index of the tail page of LRU list. */
+    private int tailIdx = NULL_IDX;
+
+    /** Index of the tail page of probationary segment. */
+    private int probTailIdx = NULL_IDX;
+
+    /** Count of protected pages in the list. */
+    private int protectedPagesCnt;
+
+    /** Protected pages segment limit. */
+    private final int protectedPagesLimit;
+
+    /** Pointer to memory region to store links. */
+    private final long linksPtr;
+
+    /** Pointer to memory region to store protected flags. */
+    private final long flagsPtr;
+
+    /**
+     * @param totalPagesCnt Total pages count.
+     * @param memPtr Pointer to memory region.
+     */
+    public SegmentedLruPageList(int totalPagesCnt, long memPtr) {
+        linksPtr = memPtr;
+        flagsPtr = memPtr + (((long)totalPagesCnt) << 3);
+
+        GridUnsafe.setMemory(linksPtr, ((long)totalPagesCnt) << 3, (byte)0xFF);
+        GridUnsafe.setMemory(flagsPtr, (totalPagesCnt + 7) >> 3, (byte)0);
+
+        protectedPagesLimit = (int)(totalPagesCnt * PROTECTED_TO_TOTAL_PAGES_RATIO);
+    }
+
+    /**
+     * Remove page from the head of LRU list.
+     *
+     * @return Page index or {@code -1} if list is empty.
+     */
+    public synchronized int poll() {
+        int idx = headIdx;
+
+        if (idx != NULL_IDX)
+            remove(idx);
+
+        return idx;
+    }
+
+    /**
+     * Remove page from LRU list by page index.
+     *
+     * @param pageIdx Page index.
+     */
+    public synchronized void remove(int pageIdx) {
+        remove0(pageIdx, protectedPage(pageIdx));
+    }
+
+    /**
+     * @param pageIdx Page index.
+     * @param clearProtectedFlag Clear protected page flag.
+     */
+    private void remove0(int pageIdx, boolean clearProtectedFlag) {
+        assert pageIdx != NULL_IDX;
+
+        int prevIdx = prev(pageIdx);
+        int nextIdx = next(pageIdx);
+
+        if (pageIdx == probTailIdx)
+            probTailIdx = prevIdx;
+
+        if (prevIdx == NULL_IDX) {
+            assert headIdx == pageIdx : "Unexpected LRU page index [headIdx=" + headIdx + ", pageIdx=" + pageIdx + ']';
+
+            headIdx = nextIdx;
+        }
+        else
+            next(prevIdx, nextIdx);
+
+        if (nextIdx == NULL_IDX) {
+            assert tailIdx == pageIdx : "Unexpected LRU page index [tailIdx=" + tailIdx + ", pageIdx=" + pageIdx + ']';
+
+            tailIdx = prevIdx;
+        }
+        else
+            prev(nextIdx, prevIdx);
+
+        clearLinks(pageIdx);
+
+        if (clearProtectedFlag) {
+            protectedPagesCnt--;
+
+            protectedPage(pageIdx, false);
+        }
+    }
+
+    /**
+     * Add page to the tail of protected or probationary LRU list.
+     *
+     * @param pageIdx Page index.
+     * @param protectedPage Protected page flag.
+     */
+    public synchronized void addToTail(int pageIdx, boolean protectedPage) {
+        assert prev(pageIdx) == NULL_IDX : prev(pageIdx);
+        assert next(pageIdx) == NULL_IDX : next(pageIdx);
+
+        if (headIdx == NULL_IDX || tailIdx == NULL_IDX) {
+            // In case of empty list.
+            assert headIdx == NULL_IDX : headIdx;
+            assert tailIdx == NULL_IDX : tailIdx;
+            assert probTailIdx == NULL_IDX : probTailIdx;
+            assert protectedPagesCnt == 0 : protectedPagesCnt;
+
+            headIdx = pageIdx;
+            tailIdx = pageIdx;
+
+            if (protectedPage) {
+                protectedPagesCnt = 1;
+
+                protectedPage(pageIdx, true);
+            }
+            else
+                probTailIdx = pageIdx;
+
+            return;
+        }
+
+        if (protectedPage) {
+            // Protected page - insert to the list tail.
+            assert next(tailIdx) == NULL_IDX : "Unexpected LRU page index [pageIdx=" + pageIdx +
+                ", tailIdx=" + tailIdx + ", nextLruIdx=" + next(tailIdx) + ']';
+
+            link(tailIdx, pageIdx);
+
+            tailIdx = pageIdx;
+
+            protectedPage(pageIdx, true);
+
+            // Move one page from protected segment to probationary segment if there are too many protected pages.
+            if (protectedPagesCnt >= protectedPagesLimit) {
+                probTailIdx = probTailIdx != NULL_IDX ? next(probTailIdx) : headIdx;
+
+                assert probTailIdx != NULL_IDX;
+
+                protectedPage(probTailIdx, false);
+            }
+            else
+                protectedPagesCnt++;
+        }
+        else {
+            if (probTailIdx == NULL_IDX) {
+                // First page in the probationary list - insert to the head.
+                assert prev(headIdx) == NULL_IDX : "Unexpected LRU page index [pageIdx=" + pageIdx +
+                    ", headIdx=" + headIdx + ", prevLruIdx=" + prev(headIdx) + ']';
+
+                link(pageIdx, headIdx);
+
+                headIdx = pageIdx;
+            }
+            else {
+                int protectedIdx = next(probTailIdx);
+
+                link(probTailIdx, pageIdx);
+
+                if (protectedIdx == NULL_IDX) {
+                    // There are no protected pages in the list.
+                    assert probTailIdx == tailIdx :
+                        "Unexpected LRU page index [probTailIdx=" + probTailIdx + ", tailIdx=" + tailIdx + ']';
+
+                    tailIdx = pageIdx;
+                }
+                else {
+                    // Link with last protected page.
+                    link(pageIdx, protectedIdx);
+                }
+            }
+
+            probTailIdx = pageIdx;
+        }
+    }
+
+    /**
+     * Move page to the tail of protected LRU list.
+     *
+     * @param pageIdx Page index.
+     */
+    public synchronized void moveToTail(int pageIdx) {
+        if (tailIdx == pageIdx)
+            return;
+
+        remove0(pageIdx, false);
+
+        if (protectedPage(pageIdx)) {
+            link(tailIdx, pageIdx);
+
+            tailIdx = pageIdx;
+        }
+        else
+            addToTail(pageIdx, true);
+    }
+
+    /**
+     * Link two pages.
+     *
+     * @param prevIdx Previous page index.
+     * @param nextIdx Next page index.
+     */
+    private void link(int prevIdx, int nextIdx) {
+        prev(nextIdx, prevIdx);
+        next(prevIdx, nextIdx);
+    }
+
+    /**
+     * Clear page links.
+     *
+     * @param pageIdx Page index.
+     */
+    private void clearLinks(int pageIdx) {
+        GridUnsafe.putLong(linksPtr + (((long)pageIdx) << 3), -1L);
+    }
+
+    /**
+     * Gets link to the previous page in the list.
+     *
+     * @param pageIdx Page index.
+     */
+    int prev(int pageIdx) {
+        return GridUnsafe.getInt(linksPtr + (((long)pageIdx) << 3));
+    }
+
+    /**
+     * Gets link to the next page in the list.
+     *
+     * @param pageIdx Page index.
+     */
+    int next(int pageIdx) {
+        return GridUnsafe.getInt(linksPtr + (((long)pageIdx) << 3) + 4);
+    }
+
+    /**
+     * Gets protected page flag.
+     *
+     * @param pageIdx Page index.
+     */
+    boolean protectedPage(int pageIdx) {
+        long flags = GridUnsafe.getLong(flagsPtr + ((pageIdx >> 3) & (~7)));
+
+        return (flags & (1L << pageIdx)) != 0L;
+    }
+
+    /**
+     * Sets link to the previous page in the list.
+     *
+     * @param pageIdx Page index.
+     * @param prevIdx Previous page index.
+     */
+    private void prev(int pageIdx, int prevIdx) {
+        GridUnsafe.putInt(linksPtr + (((long)pageIdx) << 3), prevIdx);
+    }
+
+    /**
+     * Sets link to the next page in the list.
+     *
+     * @param pageIdx Page index.
+     * @param nextIdx Next page index.
+     */
+    private void next(int pageIdx, int nextIdx) {
+        GridUnsafe.putInt(linksPtr + (((long)pageIdx) << 3) + 4, nextIdx);
+    }
+
+    /**
+     * Sets protected page flag.
+     *
+     * @param pageIdx Page index.
+     * @param protectedPage Protected page flag.
+     */
+    private void protectedPage(int pageIdx, boolean protectedPage) {
+        long ptr = flagsPtr + ((pageIdx >> 3) & (~7));
+
+        if (protectedPage)
+            GridUnsafe.putLong(ptr, GridUnsafe.getLong(ptr) | (1L << pageIdx));
+        else
+            GridUnsafe.putLong(ptr, GridUnsafe.getLong(ptr) & ~(1L << pageIdx));
+    }
+
+    /**
+     * Gets the index of the head page of LRU list.
+     */
+    synchronized int headIdx() {
+        return headIdx;
+    }
+
+    /**
+     * Gets the indexof the tail page of probationary segment.
+     */
+    synchronized int probTailIdx() {
+        return probTailIdx;
+    }
+
+    /**
+     * Gets the index of the tail page of LRU list.
+     */
+    synchronized int tailIdx() {
+        return tailIdx;
+    }
+
+    /**
+     * Gets protected pages count.
+     */
+    synchronized int protectedPagesCount() {
+        return protectedPagesCnt;
+    }
+
+    /**
+     * Gets protected pages limit.
+     */
+    int protectedPagesLimit() {
+        return protectedPagesLimit;
+    }
+
+    /**
+     * Memory required to service {@code pagesCnt} pages.
+     *
+     * @param pagesCnt Pages count.
+     */
+    public static long requiredMemory(int pagesCnt) {
+        return pagesCnt * 8 /* links = 2 ints per page */ +
+            ((pagesCnt + 63) / 8) & (~7L) /* protected flags = 1 bit per page + 8 byte align */;
+    }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/SegmentedLruPageReplacementPolicy.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/SegmentedLruPageReplacementPolicy.java
new file mode 100644
index 0000000..f03fa0e
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/SegmentedLruPageReplacementPolicy.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.processors.cache.persistence.pagemem;
+
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.configuration.PageReplacementMode;
+import org.apache.ignite.internal.pagemem.FullPageId;
+import org.apache.ignite.internal.pagemem.PageIdUtils;
+
+import static org.apache.ignite.internal.processors.cache.persistence.pagemem.PageMemoryImpl.INVALID_REL_PTR;
+import static org.apache.ignite.internal.processors.cache.persistence.pagemem.PageMemoryImpl.OUTDATED_REL_PTR;
+
+/**
+ * Segmented-LRU page replacement policy implementation.
+ *
+ * @see PageReplacementMode#SEGMENTED_LRU
+ */
+public class SegmentedLruPageReplacementPolicy extends PageReplacementPolicy {
+    /** LRU list. */
+    private final SegmentedLruPageList lruList;
+
+    /**
+     * @param seg Page memory segment.
+     */
+    protected SegmentedLruPageReplacementPolicy(PageMemoryImpl.Segment seg, long ptr, int pagesCnt) {
+        super(seg);
+
+        lruList = new SegmentedLruPageList(pagesCnt, ptr);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void onHit(long relPtr) {
+        int pageIdx = (int)seg.pageIndex(relPtr);
+
+        lruList.moveToTail(pageIdx);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void onMiss(long relPtr) {
+        int pageIdx = (int)seg.pageIndex(relPtr);
+
+        lruList.addToTail(pageIdx, false);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void onRemove(long relPtr) {
+        int pageIdx = (int)seg.pageIndex(relPtr);
+
+        lruList.remove(pageIdx);
+    }
+
+    /** {@inheritDoc} */
+    @Override public long replace() throws IgniteCheckedException {
+        LoadedPagesMap loadedPages = seg.loadedPages();
+
+        for (int i = 0; i < loadedPages.size(); i++) {
+            int pageIdx = lruList.poll();
+
+            long relPtr = seg.relative(pageIdx);
+            long absPtr = seg.absolute(relPtr);
+
+            FullPageId fullId = PageHeader.fullPageId(absPtr);
+
+            // Check loaded pages map for outdated page.
+            relPtr = loadedPages.get(
+                fullId.groupId(),
+                fullId.effectivePageId(),
+                seg.partGeneration(fullId.groupId(), PageIdUtils.partId(fullId.pageId())),
+                INVALID_REL_PTR,
+                OUTDATED_REL_PTR
+            );
+
+            assert relPtr != INVALID_REL_PTR;
+
+            if (relPtr == OUTDATED_REL_PTR)
+                return seg.refreshOutdatedPage(fullId.groupId(), fullId.pageId(), true);
+
+            if (seg.tryToRemovePage(fullId, absPtr))
+                return relPtr;
+
+            // Return page to the LRU list.
+            lruList.addToTail(pageIdx, true);
+        }
+
+        throw seg.oomException("no pages to replace");
+    }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/SegmentedLruPageReplacementPolicyFactory.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/SegmentedLruPageReplacementPolicyFactory.java
new file mode 100644
index 0000000..090c1eb
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/SegmentedLruPageReplacementPolicyFactory.java
@@ -0,0 +1,33 @@
+/*
+ * 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;
+
+/**
+ * {@link SegmentedLruPageReplacementPolicy} factory.
+ */
+public class SegmentedLruPageReplacementPolicyFactory implements PageReplacementPolicyFactory {
+    /** {@inheritDoc} */
+    @Override public long requiredMemory(int pagesCnt) {
+        return SegmentedLruPageList.requiredMemory(pagesCnt);
+    }
+
+    /** {@inheritDoc} */
+    @Override public PageReplacementPolicy create(PageMemoryImpl.Segment seg, long ptr, int pagesCnt) {
+        return new SegmentedLruPageReplacementPolicy(seg, ptr, pagesCnt);
+    }
+}
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 3e73281..33ae0cf 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
@@ -19,6 +19,7 @@ package org.apache.ignite.internal.processors.cache.persistence.pagemem;
 
 import java.util.Collections;
 import org.apache.ignite.configuration.DataRegionConfiguration;
+import org.apache.ignite.configuration.DataStorageConfiguration;
 import org.apache.ignite.configuration.IgniteConfiguration;
 import org.apache.ignite.internal.managers.encryption.GridEncryptionManager;
 import org.apache.ignite.internal.managers.systemview.GridSystemViewManager;
@@ -66,6 +67,7 @@ public class BPlusTreePageMemoryImplTest extends BPlusTreeSelfTest {
         cfg.setEncryptionSpi(new NoopEncryptionSpi());
         cfg.setMetricExporterSpi(new NoopMetricExporterSpi());
         cfg.setSystemViewExporterSpi(new JmxSystemViewExporterSpi());
+        cfg.setDataStorageConfiguration(new DataStorageConfiguration());
 
         GridTestKernalContext cctx = new GridTestKernalContext(log, cfg);
 
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 456c9d6..3c37874 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
@@ -19,6 +19,7 @@ package org.apache.ignite.internal.processors.cache.persistence.pagemem;
 
 import java.util.Collections;
 import org.apache.ignite.configuration.DataRegionConfiguration;
+import org.apache.ignite.configuration.DataStorageConfiguration;
 import org.apache.ignite.configuration.IgniteConfiguration;
 import org.apache.ignite.internal.managers.encryption.GridEncryptionManager;
 import org.apache.ignite.internal.managers.systemview.GridSystemViewManager;
@@ -65,6 +66,7 @@ public class BPlusTreeReuseListPageMemoryImplTest extends BPlusTreeReuseSelfTest
         cfg.setEncryptionSpi(new NoopEncryptionSpi());
         cfg.setMetricExporterSpi(new NoopMetricExporterSpi());
         cfg.setSystemViewExporterSpi(new JmxSystemViewExporterSpi());
+        cfg.setDataStorageConfiguration(new DataStorageConfiguration());
 
         GridTestKernalContext cctx = new GridTestKernalContext(log, cfg);
 
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/ClockPageReplacementFlagsTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/ClockPageReplacementFlagsTest.java
new file mode 100644
index 0000000..91c7671
--- /dev/null
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/ClockPageReplacementFlagsTest.java
@@ -0,0 +1,119 @@
+/*
+ * 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.mem.DirectMemoryProvider;
+import org.apache.ignite.internal.mem.DirectMemoryRegion;
+import org.apache.ignite.internal.mem.unsafe.UnsafeMemoryProvider;
+import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ *
+ */
+public class ClockPageReplacementFlagsTest extends GridCommonAbstractTest {
+    /** Max pages count. */
+    private static final int MAX_PAGES_CNT = 1000;
+
+    /** Memory provider. */
+    private static DirectMemoryProvider provider;
+
+    /** Memory region. */
+    private static DirectMemoryRegion region;
+
+    /** Clock replacement algorithm implementation. */
+    ClockPageReplacementFlags clockFlags;
+
+    /** */
+    @BeforeClass
+    public static void setUp() {
+        provider = new UnsafeMemoryProvider(log);
+        provider.initialize(new long[] {ClockPageReplacementFlags.requiredMemory(MAX_PAGES_CNT)});
+
+        region = provider.nextRegion();
+    }
+
+    /** */
+    @AfterClass
+    public static void tearDown() {
+        provider.shutdown(true);
+    }
+
+    /**
+     * Test setFlag() and getFlag() methods.
+     */
+    @Test
+    public void testSetGet() {
+        clockFlags = new ClockPageReplacementFlags(MAX_PAGES_CNT, region.address());
+
+        setRange(50, 100);
+
+        for (int i = 0; i < MAX_PAGES_CNT; i++)
+            assertEquals("Unexpectd value of " + i + " item", i >= 50 && i <= 100, clockFlags.getFlag(i));
+    }
+
+
+    /**
+     * Test poll() method.
+     */
+    @Test
+    public void testPoll() {
+        clockFlags = new ClockPageReplacementFlags(MAX_PAGES_CNT, region.address());
+
+        setRange(2, 2);
+        setRange(4, 7);
+        setRange(9, 254);
+        setRange(256, 320);
+        setRange(322, 499);
+        setRange(501, MAX_PAGES_CNT - 1);
+
+        assertEquals(0, clockFlags.poll());
+        assertEquals(1, clockFlags.poll());
+        assertEquals(3, clockFlags.poll());
+        assertEquals(8, clockFlags.poll());
+        assertEquals(255, clockFlags.poll());
+        assertEquals(321, clockFlags.poll());
+        assertEquals(500, clockFlags.poll());
+
+        setRange(0, 1);
+
+        assertEquals(2, clockFlags.poll());
+        assertEquals(3, clockFlags.poll());
+
+        setRange(4, MAX_PAGES_CNT - 2);
+
+        assertEquals(MAX_PAGES_CNT - 1, clockFlags.poll());
+        assertEquals(0, clockFlags.poll());
+
+        for (int i = 0; i < MAX_PAGES_CNT; i++)
+            assertEquals("Unexpectd value of " + i + " item", false, clockFlags.getFlag(i));
+    }
+
+    /**
+     * Set flag for range of page indexes.
+     *
+     * @param fromIdx From index.
+     * @param toIdx To index.
+     */
+    private void setRange(int fromIdx, int toIdx) {
+        for (int i = fromIdx; i <= toIdx; i++)
+            clockFlags.setFlag(i);
+    }
+}
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 19e67b4..fa89f05 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
@@ -20,6 +20,7 @@ package org.apache.ignite.internal.processors.cache.persistence.pagemem;
 import java.io.File;
 import java.util.Collections;
 import org.apache.ignite.configuration.DataRegionConfiguration;
+import org.apache.ignite.configuration.DataStorageConfiguration;
 import org.apache.ignite.configuration.IgniteConfiguration;
 import org.apache.ignite.internal.managers.encryption.GridEncryptionManager;
 import org.apache.ignite.internal.managers.systemview.GridSystemViewManager;
@@ -81,6 +82,7 @@ public class IndexStoragePageMemoryImplTest extends IndexStorageSelfTest {
         cfg.setEncryptionSpi(new NoopEncryptionSpi());
         cfg.setMetricExporterSpi(new NoopMetricExporterSpi());
         cfg.setSystemViewExporterSpi(new JmxSystemViewExporterSpi());
+        cfg.setDataStorageConfiguration(new DataStorageConfiguration());
 
         GridTestKernalContext cctx = new GridTestKernalContext(log, cfg);
 
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 51e29ff..149086b 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
@@ -20,6 +20,7 @@ package org.apache.ignite.internal.processors.cache.persistence.pagemem;
 import java.io.File;
 import java.util.Collections;
 import org.apache.ignite.configuration.DataRegionConfiguration;
+import org.apache.ignite.configuration.DataStorageConfiguration;
 import org.apache.ignite.configuration.IgniteConfiguration;
 import org.apache.ignite.internal.managers.encryption.GridEncryptionManager;
 import org.apache.ignite.internal.managers.systemview.GridSystemViewManager;
@@ -71,6 +72,7 @@ public class PageMemoryImplNoLoadTest extends PageMemoryNoLoadSelfTest {
         cfg.setEncryptionSpi(new NoopEncryptionSpi());
         cfg.setMetricExporterSpi(new NoopMetricExporterSpi());
         cfg.setSystemViewExporterSpi(new JmxSystemViewExporterSpi());
+        cfg.setDataStorageConfiguration(new DataStorageConfiguration());
 
         GridTestKernalContext cctx = new GridTestKernalContext(log, cfg);
 
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/SegmentedLruPageListTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/SegmentedLruPageListTest.java
new file mode 100644
index 0000000..ac78088
--- /dev/null
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/SegmentedLruPageListTest.java
@@ -0,0 +1,366 @@
+/*
+ * 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.mem.DirectMemoryProvider;
+import org.apache.ignite.internal.mem.DirectMemoryRegion;
+import org.apache.ignite.internal.mem.unsafe.UnsafeMemoryProvider;
+import org.apache.ignite.internal.util.typedef.F;
+import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.rules.TestWatcher;
+import org.junit.runner.Description;
+
+import static org.apache.ignite.internal.processors.cache.persistence.pagemem.SegmentedLruPageList.NULL_IDX;
+
+/**
+ * Test Segmented-LRU list implementation.
+ */
+public class SegmentedLruPageListTest extends GridCommonAbstractTest {
+    /** Max pages count. */
+    private static final int MAX_PAGES_CNT = 20;
+
+    /** Memory provider. */
+    private static DirectMemoryProvider provider;
+
+    /** Memory region. */
+    private static DirectMemoryRegion region;
+
+    /** LRU list. */
+    SegmentedLruPageList lru;
+
+    /** Test watcher. */
+    @Rule public TestRule testWatcher = new TestWatcher() {
+        @Override protected void failed(Throwable e, Description description) {
+            dump();
+        }
+    };
+
+    /** */
+    @BeforeClass
+    public static void setUp() {
+        provider = new UnsafeMemoryProvider(log);
+        provider.initialize(new long[] {SegmentedLruPageList.requiredMemory(MAX_PAGES_CNT)});
+
+        region = provider.nextRegion();
+    }
+
+    /** */
+    @AfterClass
+    public static void tearDown() {
+        provider.shutdown(true);
+    }
+
+    /** */
+    @Test
+    public void testAdd() {
+        // Check start with probationary page.
+        lru = new SegmentedLruPageList(MAX_PAGES_CNT, region.address());
+
+        addToTail(0, false);
+        assertProbationarySegment(0);
+        assertProtectedSegment();
+
+        addToTail(1, true);
+        assertProbationarySegment(0);
+        assertProtectedSegment(1);
+
+        addToTail(2, false);
+        assertProbationarySegment(0, 2);
+        assertProtectedSegment(1);
+
+        addToTail(3, true);
+        assertProbationarySegment(0, 2);
+        assertProtectedSegment(1, 3);
+
+        // Check start with protected page.
+        lru = new SegmentedLruPageList(MAX_PAGES_CNT, region.address());
+
+        addToTail(0, true);
+        assertProbationarySegment();
+        assertProtectedSegment(0);
+
+        addToTail(1, false);
+        assertProbationarySegment(1);
+        assertProtectedSegment(0);
+
+        addToTail(2, true);
+        assertProbationarySegment(1);
+        assertProtectedSegment(0, 2);
+
+        addToTail(3, false);
+        assertProbationarySegment(1, 3);
+        assertProtectedSegment(0, 2);
+    }
+
+    /** */
+    @Test
+    public void testRemove() {
+        lru = new SegmentedLruPageList(MAX_PAGES_CNT, region.address());
+
+        addToTail(0, false);
+        addToTail(1, true);
+        addToTail(2, false);
+        addToTail(3, true);
+        addToTail(4, false);
+        addToTail(5, true);
+
+        remove(0); // Head.
+        assertProbationarySegment(2, 4);
+        assertProtectedSegment(1, 3, 5);
+
+        remove(5); // Tail.
+        assertProbationarySegment(2, 4);
+        assertProtectedSegment(1, 3);
+
+        remove(1); // Protected segment head.
+        assertProbationarySegment(2, 4);
+        assertProtectedSegment(3);
+
+        remove(4); // Probationary segment tail.
+        assertProbationarySegment(2);
+        assertProtectedSegment(3);
+
+        remove(2); // Last probationary segment item.
+        assertProbationarySegment();
+        assertProtectedSegment(3);
+
+        remove(3); // Last potected segment and LRU item.
+        assertProbationarySegment();
+        assertProtectedSegment();
+
+        addToTail(2, false);
+        addToTail(3, true);
+
+        remove(3); // Last protected segment item.
+        assertProbationarySegment(2);
+        assertProtectedSegment();
+
+        remove(2); // Last probationary segment and LRU item.
+        assertProbationarySegment();
+        assertProtectedSegment();
+    }
+
+    /** */
+    @Test
+    public void testPoll() {
+        lru = new SegmentedLruPageList(MAX_PAGES_CNT, region.address());
+
+        assertEquals(NULL_IDX, poll());
+
+        addToTail(0, false);
+        addToTail(1, true);
+        addToTail(2, false);
+        addToTail(3, true);
+
+        assertEquals(0, poll());
+        assertEquals(2, poll());
+        assertEquals(1, poll());
+        assertEquals(3, poll());
+        assertEquals(NULL_IDX, poll());
+    }
+
+    /** */
+    @Test
+    public void testMoveToTail() {
+        lru = new SegmentedLruPageList(MAX_PAGES_CNT, region.address());
+
+        addToTail(0, false);
+        addToTail(1, true);
+        addToTail(2, false);
+        addToTail(3, true);
+
+        moveToTail(3);
+        assertProbationarySegment(0, 2);
+        assertProtectedSegment(1, 3);
+
+        moveToTail(2);
+        assertProbationarySegment(0);
+        assertProtectedSegment(1, 3, 2);
+
+        moveToTail(1);
+        assertProbationarySegment(0);
+        assertProtectedSegment(3, 2, 1);
+
+        moveToTail(0);
+        assertProbationarySegment();
+        assertProtectedSegment(3, 2, 1, 0);
+    }
+
+    /** */
+    @Test
+    public void testProtectedToProbationaryMigration() {
+        lru = new SegmentedLruPageList(6, region.address());
+
+        assertEquals(3, lru.protectedPagesLimit());
+
+        addToTail(0, true);
+        addToTail(1, true);
+        addToTail(2, true);
+
+        assertProbationarySegment();
+        assertProtectedSegment(0, 1, 2);
+
+        addToTail(3, true);
+        assertProbationarySegment(0);
+        assertProtectedSegment(1, 2, 3);
+
+        addToTail(4, true);
+        assertProbationarySegment(0, 1);
+        assertProtectedSegment(2, 3, 4);
+    }
+
+    /** */
+    private void addToTail(int pageIdx, boolean protectedPage) {
+        lru.addToTail(pageIdx, protectedPage);
+
+        checkInvariants();
+    }
+
+    /** */
+    private void remove(int pageIdx) {
+        lru.remove(pageIdx);
+
+        checkInvariants();
+    }
+
+    /** */
+    private int poll() {
+        int idx = lru.poll();
+
+        checkInvariants();
+
+        return idx;
+    }
+
+    /** */
+    private void moveToTail(int pageIdx) {
+        lru.moveToTail(pageIdx);
+
+        checkInvariants();
+    }
+
+    /** */
+    private void assertProbationarySegment(int... pageIdxs) {
+        assertTrue((lru.probTailIdx() != NULL_IDX) ^ F.isEmpty(pageIdxs));
+
+        int curIdx = lru.headIdx();
+
+        for (int pageIdx : pageIdxs) {
+            assertEquals(pageIdx, curIdx);
+
+            curIdx = (curIdx == lru.probTailIdx()) ? NULL_IDX : lru.next(curIdx);
+        }
+
+        if (!F.isEmpty(pageIdxs))
+            assertTrue(curIdx == NULL_IDX);
+    }
+
+    /** */
+    private void assertProtectedSegment(int... pageIdxs) {
+        int curIdx = lru.headIdx();
+
+        if (lru.probTailIdx() != NULL_IDX)
+            curIdx = lru.next(lru.probTailIdx());
+
+        assertTrue((curIdx != NULL_IDX) ^ F.isEmpty(pageIdxs));
+
+        for (int pageIdx : pageIdxs) {
+            assertEquals(pageIdx, curIdx);
+
+            curIdx = lru.next(curIdx);
+        }
+
+        assertEquals(pageIdxs.length, lru.protectedPagesCount());
+    }
+
+    /**
+     * Check LRU list invariants.
+     */
+    private void checkInvariants() {
+        int limit = MAX_PAGES_CNT + 1;
+
+        int curIdx = lru.headIdx();
+        int protectedCnt = 0;
+        boolean protectedPage = (lru.probTailIdx() == NULL_IDX);
+
+        if (lru.headIdx() == NULL_IDX || lru.tailIdx() == NULL_IDX) {
+            assertEquals(NULL_IDX, lru.headIdx());
+            assertEquals(NULL_IDX, lru.tailIdx());
+            assertEquals(NULL_IDX, lru.probTailIdx());
+            assertEquals(0, lru.protectedPagesCount());
+        }
+
+        while (curIdx != NULL_IDX && limit-- > 0) {
+            int prev = lru.prev(curIdx);
+            int next = lru.next(curIdx);
+
+            if (prev == NULL_IDX)
+                assertEquals(lru.headIdx(), curIdx);
+            else
+                assertEquals(curIdx, lru.next(prev));
+
+            if (next == NULL_IDX)
+                assertEquals(lru.tailIdx(), curIdx);
+            else
+                assertEquals(curIdx, lru.prev(next));
+
+            assertEquals(protectedPage, lru.protectedPage(curIdx));
+
+            if (protectedPage)
+                protectedCnt++;
+
+            if (curIdx == lru.probTailIdx())
+                protectedPage = true;
+
+            curIdx = next;
+        }
+
+        assertTrue(limit > 0);
+
+        assertEquals(protectedCnt, lru.protectedPagesCount());
+        assertTrue(protectedCnt <= lru.protectedPagesLimit());
+    }
+
+    /**
+     * Dump LRU list content.
+     */
+    private void dump() {
+        int limit = MAX_PAGES_CNT;
+
+        log.info(String.format("LRU list dump [headPtr=%d, probTailPtr=%d, tailPtr=%d, protectedCnt=%d]",
+            lru.headIdx(), lru.probTailIdx(), lru.tailIdx(), lru.protectedPagesCount()));
+
+        int curIdx = lru.headIdx();
+
+        while (curIdx != NULL_IDX && limit-- > 0) {
+            log.info(String.format("    Page %d [prev=%d, next=%d, protected=%b]%s", curIdx,
+                lru.prev(curIdx), lru.next(curIdx),
+                lru.protectedPage(curIdx), curIdx == lru.probTailIdx() ? " <- probationary list tail" : ""));
+
+            curIdx = lru.next(curIdx);
+        }
+
+        if (limit == 0)
+            log.info("...");
+    }
+}
diff --git a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteBasicTestSuite.java b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteBasicTestSuite.java
index 2fcfbe0..6331cae 100644
--- a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteBasicTestSuite.java
+++ b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteBasicTestSuite.java
@@ -80,7 +80,9 @@ import org.apache.ignite.internal.processors.cache.SetTxTimeoutOnPartitionMapExc
 import org.apache.ignite.internal.processors.cache.distributed.IgniteRejectConnectOnNodeStopTest;
 import org.apache.ignite.internal.processors.cache.distributed.dht.topology.EvictPartitionInLogTest;
 import org.apache.ignite.internal.processors.cache.persistence.defragmentation.LinkMapTest;
+import org.apache.ignite.internal.processors.cache.persistence.pagemem.ClockPageReplacementFlagsTest;
 import org.apache.ignite.internal.processors.cache.persistence.pagemem.PagePoolTest;
+import org.apache.ignite.internal.processors.cache.persistence.pagemem.SegmentedLruPageListTest;
 import org.apache.ignite.internal.processors.cache.query.continuous.DiscoveryDataDeserializationFailureHanderTest;
 import org.apache.ignite.internal.processors.cache.transactions.AtomicOperationsInTxTest;
 import org.apache.ignite.internal.processors.cache.transactions.TransactionIntegrityWithSystemWorkerDeathTest;
@@ -222,6 +224,8 @@ import org.junit.runners.Suite;
 
     // Basic DB data structures.
     PagePoolTest.class,
+    SegmentedLruPageListTest.class,
+    ClockPageReplacementFlagsTest.class,
     BPlusTreeSelfTest.class,
     BPlusTreeFakeReuseSelfTest.class,
     BPlusTreeReuseSelfTest.class,
diff --git a/modules/yardstick/config/benchmark-cache-pagereplacements.properties b/modules/yardstick/config/benchmark-cache-pagereplacements.properties
new file mode 100644
index 0000000..47f7497
--- /dev/null
+++ b/modules/yardstick/config/benchmark-cache-pagereplacements.properties
@@ -0,0 +1,124 @@
+
+# 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.
+
+now0=`date +'%H%M%S'`
+
+# JVM options.
+JVM_OPTS=${JVM_OPTS}" -DIGNITE_QUIET=false"
+
+# Uncomment to enable concurrent garbage collection (GC) if you encounter long GC pauses.
+JVM_OPTS=${JVM_OPTS}" \
+-Xloggc:./gc${now0}.log \
+-XX:+PrintGCDetails \
+-verbose:gc \
+-XX:+UseG1GC \
+-XX:+PrintGCDateStamps \
+-DIGNITE_CHECKPOINT_TRIGGER_ARCHIVE_SIZE_PERCENTAGE=100 \
+"
+
+DRIVER_JVM_OPTS=${DRIVER_JVM_OPTS}"-Xmx1024m"
+
+SERVER_JVM_OPTS=${SERVER_JVM_OPTS}"-Xms8g -Xmx8g"
+
+#Ignite version
+ver="RELEASE-"
+
+# List of default probes.
+# Add DStatProbe or VmStatProbe if your OS supports it (e.g. if running on Linux).
+BENCHMARK_DEFAULT_PROBES=ThroughputLatencyProbe,PercentileProbe,DStatProbe
+
+# Packages where the specified benchmark is searched by reflection mechanism.
+BENCHMARK_PACKAGES=org.yardstickframework,org.apache.ignite.yardstick
+
+# Flag which indicates to restart the servers before every benchmark execution.
+RESTART_SERVERS=true
+
+# Probe point writer class name.
+# BENCHMARK_WRITER=
+
+# The benchmark is applicable only for 1 server and 1 driver
+SERVER_HOSTS=127.0.0.1
+DRIVER_HOSTS=127.0.0.1
+
+# Remote username.
+# REMOTE_USER=
+
+# Number of nodes, used to wait for the specified number of nodes to start.
+nodesNum=$((`echo ${SERVER_HOSTS} | tr ',' '\n' | wc -l` + `echo ${DRIVER_HOSTS} | tr ',' '\n' | wc -l`))
+
+# Backups count.
+b=0
+
+# Warmup.
+w=30
+
+# Duration.
+d=200
+
+# Threads count.
+t=4
+
+# Sync mode.
+sm=PRIMARY_SYNC
+
+# Jobs.
+j=10
+
+# Parameters that should be the same across all the benchmarks launches.
+commonParams="\
+-cfg ${SCRIPT_DIR}/../config/ignite-localhost-pagereplacement-config.xml -nn ${nodesNum} -sn IgniteNode \
+-b ${b} -w ${w} -d ${d} -t ${t} -sm ${sm} -cwd -cl\
+"
+
+# With page replacement.
+repl="-DREPLACE_RATIO=2.0"
+
+# Without page replacement.
+norepl="-DREPLACE_RATIO=0.5"
+
+# Page replacement modes.
+rlru="-SIGNITE_PAGE_REPLACEMENT_MODE=RANDOM_LRU"
+slru="-SIGNITE_PAGE_REPLACEMENT_MODE=SEGMENTED_LRU"
+clck="-SIGNITE_PAGE_REPLACEMENT_MODE=CLOCK"
+
+# Benchmark operations.
+putBenchmark="-dn IgnitePutWithPageReplacementBenchmark"
+getBenchmark="-dn IgniteGetWithPageReplacementBenchmark"
+
+# Benchmark with background scans.
+bgScan="-DBACKGROUND_SCAN_INTERVAL=10000"
+
+# Run configuration which contains all benchmarks.
+CONFIGS="\
+${commonParams} ${norepl} ${rlru} ${putBenchmark} -ds ${ver}cache-put-no-pagereplacement-RLRU,\
+${commonParams} ${norepl} ${slru} ${putBenchmark} -ds ${ver}cache-put-no-pagereplacement-SLRU,\
+${commonParams} ${norepl} ${clck} ${putBenchmark} -ds ${ver}cache-put-no-pagereplacement-CLCK,\
+${commonParams} ${repl} ${rlru} ${putBenchmark} -ds ${ver}cache-put-pagereplacement-RLRU,\
+${commonParams} ${repl} ${slru} ${putBenchmark} -ds ${ver}cache-put-pagereplacement-SLRU,\
+${commonParams} ${repl} ${clck} ${putBenchmark} -ds ${ver}cache-put-pagereplacement-CLCK,\
+${commonParams} ${repl} ${rlru} ${putBenchmark} ${bgScan} -ds ${ver}cache-put-pagereplacement-RLRU-BG,\
+${commonParams} ${repl} ${slru} ${putBenchmark} ${bgScan} -ds ${ver}cache-put-pagereplacement-SLRU-BG,\
+${commonParams} ${repl} ${clck} ${putBenchmark} ${bgScan} -ds ${ver}cache-put-pagereplacement-CLCK-BG,\
+${commonParams} ${norepl} ${rlru} ${getBenchmark} -ds ${ver}cache-get-no-pagereplacement-RLRU,\
+${commonParams} ${norepl} ${slru} ${getBenchmark} -ds ${ver}cache-get-no-pagereplacement-SLRU,\
+${commonParams} ${norepl} ${clck} ${getBenchmark} -ds ${ver}cache-get-no-pagereplacement-CLCK,\
+${commonParams} ${repl} ${rlru} ${getBenchmark} -ds ${ver}cache-get-pagereplacement-RLRU,\
+${commonParams} ${repl} ${slru} ${getBenchmark} -ds ${ver}cache-get-pagereplacement-SLRU,\
+${commonParams} ${repl} ${clck} ${getBenchmark} -ds ${ver}cache-get-pagereplacement-CLCK,\
+${commonParams} ${repl} ${rlru} ${getBenchmark} ${bgScan} -ds ${ver}cache-get-pagereplacement-RLRU-BG,\
+${commonParams} ${repl} ${slru} ${getBenchmark} ${bgScan} -ds ${ver}cache-get-pagereplacement-SLRU-BG,\
+${commonParams} ${repl} ${clck} ${getBenchmark} ${bgScan} -ds ${ver}cache-get-pagereplacement-CLCK-BG,\
+"
diff --git a/modules/yardstick/config/benchmark-cache-pegereplacements.properties b/modules/yardstick/config/benchmark-cache-pegereplacements.properties
deleted file mode 100644
index 1da456c..0000000
--- a/modules/yardstick/config/benchmark-cache-pegereplacements.properties
+++ /dev/null
@@ -1,83 +0,0 @@
-
-# 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.
-
-now0=`date +'%H%M%S'`
-
-# JVM options.
-JVM_OPTS=${JVM_OPTS}" -DIGNITE_QUIET=false"
-
-# Uncomment to enable concurrent garbage collection (GC) if you encounter long GC pauses.
-JVM_OPTS=${JVM_OPTS}" \
--Xloggc:./gc${now0}.log \
--XX:+PrintGCDetails \
--verbose:gc \
--XX:+UseG1GC \
--XX:+PrintGCDateStamps \
-"
-
-DRIVER_JVM_OPTS=${DRIVER_JVM_OPTS}"-Xmx1024m"
-
-SERVER_JVM_OPTS=${SERVER_JVM_OPTS}"-Xms8g -Xmx8g"
-
-#Ignite version
-ver="RELEASE-"
-
-# List of default probes.
-# Add DStatProbe or VmStatProbe if your OS supports it (e.g. if running on Linux).
-BENCHMARK_DEFAULT_PROBES=ThroughputLatencyProbe,PercentileProbe,DStatProbe
-
-# Packages where the specified benchmark is searched by reflection mechanism.
-BENCHMARK_PACKAGES=org.yardstickframework,org.apache.ignite.yardstick
-
-# Flag which indicates to restart the servers before every benchmark execution.
-RESTART_SERVERS=true
-
-# Probe point writer class name.
-# BENCHMARK_WRITER=
-
-# The benchmark is applicable only for 1 server and 1 driver
-SERVER_HOSTS=127.0.0.1
-DRIVER_HOSTS=127.0.0.1
-
-# Remote username.
-# REMOTE_USER=
-
-# Number of nodes, used to wait for the specified number of nodes to start.
-nodesNum=$((`echo ${SERVER_HOSTS} | tr ',' '\n' | wc -l` + `echo ${DRIVER_HOSTS} | tr ',' '\n' | wc -l`))
-
-# Backups count.
-b=0
-
-# Warmup.
-w=30
-
-# Duration.
-d=200
-
-# Threads count.
-t=4
-
-# Sync mode.
-sm=PRIMARY_SYNC
-
-# Jobs.
-j=10
-
-# Run configuration which contains all benchmarks.
-# Note that each benchmark is set to run for 300 seconds (5 min) with warm-up set to 60 seconds (1 minute).
-CONFIGS="\
--cfg ${SCRIPT_DIR}/../config/ignite-localhost-persistence-config.xml -nn ${nodesNum} -b ${b} -w ${w} -d ${d} -t ${t} -sm ${sm} -dn IgnitePutGetWithPageReplacements -sn IgniteNode -ds ${ver}cache-pagereplacement-r1-${b}-backup -cl,\
-"
diff --git a/modules/yardstick/config/ignite-localhost-pagereplacement-config.xml b/modules/yardstick/config/ignite-localhost-pagereplacement-config.xml
new file mode 100644
index 0000000..b83b859
--- /dev/null
+++ b/modules/yardstick/config/ignite-localhost-pagereplacement-config.xml
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  ~ 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.
+  -->
+
+<!--
+    Ignite Spring configuration file to startup grid.
+-->
+<beans xmlns="http://www.springframework.org/schema/beans"
+    xmlns:util="http://www.springframework.org/schema/util"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="         http://www.springframework.org/schema/beans
+    http://www.springframework.org/schema/beans/spring-beans.xsd
+    http://www.springframework.org/schema/util
+    http://www.springframework.org/schema/util/spring-util.xsd">
+    <import resource="ignite-base-config.xml"/>
+    <bean id="grid.cfg" class="org.apache.ignite.configuration.IgniteConfiguration" parent="base-ignite.cfg">
+        <property name="localHost" value="127.0.0.1"/>
+
+        <property name="includeEventTypes">
+            <util:constant static-field="org.apache.ignite.events.EventType.EVT_PAGE_REPLACEMENT_STARTED"/>
+        </property>
+
+        <property name="discoverySpi">
+            <bean class="org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi">
+                <property name="ipFinder">
+                    <bean class="org.apache.ignite.spi.discovery.tcp.ipfinder.vm.TcpDiscoveryVmIpFinder">
+                        <property name="addresses">
+                            <list>
+                                <value>127.0.0.1:47500</value>
+                                <value>127.0.0.1:47501</value>
+                                <value>127.0.0.1:47502</value>
+                                <value>127.0.0.1:47503</value>
+                                <value>127.0.0.1:47504</value>
+                                <value>127.0.0.1:47505</value>
+                                <value>127.0.0.1:47506</value>
+                                <value>127.0.0.1:47507</value>
+                                <value>127.0.0.1:47508</value>
+                                <value>127.0.0.1:47509</value>
+                            </list>
+                        </property>
+                    </bean>
+                </property>
+            </bean>
+        </property>
+
+        <property name="dataStorageConfiguration" >
+            <bean class="org.apache.ignite.configuration.DataStorageConfiguration">
+                <property name="defaultDataRegionConfiguration">
+                    <bean class="org.apache.ignite.configuration.DataRegionConfiguration">
+			            <property name="maxSize" value="#{300 * 1024 * 1024}"/>
+                        <property name="persistenceEnabled" value="true"/>
+                        <property name="pageReplacementMode" value="#{systemProperties['IGNITE_PAGE_REPLACEMENT_MODE']}"/>
+                    </bean>
+                </property>
+            </bean>
+        </property>
+
+        <property name="communicationSpi">
+            <bean class="org.apache.ignite.spi.communication.tcp.TcpCommunicationSpi">
+                <property name="sharedMemoryPort" value="-1"/>
+            </bean>
+        </property>
+    </bean>
+</beans>
diff --git a/modules/yardstick/src/main/java/org/apache/ignite/yardstick/IgniteBenchmarkArguments.java b/modules/yardstick/src/main/java/org/apache/ignite/yardstick/IgniteBenchmarkArguments.java
index 8a2e947..854800a 100644
--- a/modules/yardstick/src/main/java/org/apache/ignite/yardstick/IgniteBenchmarkArguments.java
+++ b/modules/yardstick/src/main/java/org/apache/ignite/yardstick/IgniteBenchmarkArguments.java
@@ -306,6 +306,11 @@ public class IgniteBenchmarkArguments {
         description = "Allow add any dynamic parameters specific for some benchmarks")
     private Map<String, String> params = new HashMap<>();
 
+    /** Additional system prorperties. */
+    @DynamicParameter(names = {"-S", "--sysProp"},
+        description = "Allow add additinal dynamic system properties to benchmarks")
+    private Map<String, String> sysProps = new HashMap<>();
+
     /**
      * @return {@code True} if need set {@link DataStorageConfiguration}.
      */
@@ -789,6 +794,24 @@ public class IgniteBenchmarkArguments {
         return val != null ? Long.parseLong(val) : dflt;
     }
 
+    /**
+     * @param name Parameter name.
+     * @param dflt Default value.
+     * @return value.
+     */
+    public double getDoubleParameter(String name, double dflt) {
+        String val = params.get(name);
+
+        return val != null ? Double.parseDouble(val) : dflt;
+    }
+
+    /**
+     * @return Additional dynamic system properties.
+     */
+    public Map<String, String> systemProperties() {
+        return sysProps;
+    }
+
     /** {@inheritDoc} */
     @Override public String toString() {
         return GridToStringBuilder.toString(IgniteBenchmarkArguments.class, this);
diff --git a/modules/yardstick/src/main/java/org/apache/ignite/yardstick/IgniteNode.java b/modules/yardstick/src/main/java/org/apache/ignite/yardstick/IgniteNode.java
index 28467fe..591b76c 100644
--- a/modules/yardstick/src/main/java/org/apache/ignite/yardstick/IgniteNode.java
+++ b/modules/yardstick/src/main/java/org/apache/ignite/yardstick/IgniteNode.java
@@ -40,6 +40,7 @@ import org.apache.ignite.configuration.NearCacheConfiguration;
 import org.apache.ignite.configuration.TransactionConfiguration;
 import org.apache.ignite.configuration.WALMode;
 import org.apache.ignite.internal.util.IgniteUtils;
+import org.apache.ignite.internal.util.typedef.F;
 import org.apache.ignite.internal.util.typedef.internal.U;
 import org.apache.ignite.lang.IgniteBiTuple;
 import org.apache.ignite.spi.communication.tcp.TcpCommunicationSpi;
@@ -98,6 +99,11 @@ public class IgniteNode implements BenchmarkServer {
 
         BenchmarkUtils.jcommander(cfg.commandLineArguments(), args, "<ignite-node>");
 
+        if (!F.isEmpty(args.systemProperties())) {
+            for (Map.Entry<String, String> entry : args.systemProperties().entrySet())
+                System.setProperty(entry.getKey(), entry.getValue());
+        }
+
         if (args.clientNodesAfterId() >= 0 && cfg.memberId() > args.clientNodesAfterId())
             clientMode = true;
 
@@ -107,7 +113,7 @@ public class IgniteNode implements BenchmarkServer {
 
         assert c != null;
 
-        if (args.cleanWorkDirectory())
+        if (args.cleanWorkDirectory() && !clientMode)
             FileUtils.cleanDirectory(U.workDirectory(c.getWorkDirectory(), c.getIgniteHome()));
 
         ApplicationContext appCtx = tup.get2();
diff --git a/modules/yardstick/src/main/java/org/apache/ignite/yardstick/cache/IgnitePutGetWithPageReplacements.java b/modules/yardstick/src/main/java/org/apache/ignite/yardstick/cache/IgniteAbstractPageReplacementBenchmark.java
similarity index 62%
rename from modules/yardstick/src/main/java/org/apache/ignite/yardstick/cache/IgnitePutGetWithPageReplacements.java
rename to modules/yardstick/src/main/java/org/apache/ignite/yardstick/cache/IgniteAbstractPageReplacementBenchmark.java
index 2c6c972..9acc2b5 100644
--- a/modules/yardstick/src/main/java/org/apache/ignite/yardstick/cache/IgnitePutGetWithPageReplacements.java
+++ b/modules/yardstick/src/main/java/org/apache/ignite/yardstick/cache/IgniteAbstractPageReplacementBenchmark.java
@@ -21,9 +21,9 @@ import java.io.Serializable;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.UUID;
-import java.util.concurrent.ThreadLocalRandom;
 import java.util.concurrent.atomic.AtomicBoolean;
 import org.apache.ignite.IgniteCache;
+import org.apache.ignite.IgniteDataStreamer;
 import org.apache.ignite.cache.query.QueryCursor;
 import org.apache.ignite.cache.query.ScanQuery;
 import org.apache.ignite.cache.query.annotations.QuerySqlField;
@@ -42,17 +42,26 @@ import static org.apache.ignite.events.EventType.EVT_PAGE_REPLACEMENT_STARTED;
  * after proceed to fill two times more data.
  * Execute full scan.
  *
- * On test phase fill data belonging to only 1/2 of dataregion capacity, calculated on setUp phase.
+ * On test phase process data belonging to dataregion capacity (calculated on setUp phase) * REPLACE_RATIO parameter.
  *
  * NOTE: EVT_PAGE_REPLACEMENT_STARTED event need to be enabled on server side.
  */
-public class IgnitePutGetWithPageReplacements extends IgniteCacheAbstractBenchmark<Integer, Object> {
+public abstract class IgniteAbstractPageReplacementBenchmark extends IgniteCacheAbstractBenchmark<Integer, Object> {
     /** Cache name. */
     private static final String CACHE_NAME = "CacheWithReplacement";
 
+    /** Scan-cache name. */
+    private static final String SCAN_CACHE_NAME = "ScanCache";
+
     /** In mem reg capacity. */
     private volatile int replCntr = Integer.MAX_VALUE / 2;
 
+    /** Thread to perform periodical background scans. */
+    private volatile Thread backgroundScanThread;
+
+    /** Key range for given data region capacity and REPLACE_RATIO parameter. */
+    private volatile int range;
+
     /** {@inheritDoc} */
     @Override public void setUp(BenchmarkConfiguration cfg) throws Exception {
         super.setUp(cfg);
@@ -100,7 +109,45 @@ public class IgnitePutGetWithPageReplacements extends IgniteCacheAbstractBenchma
             }
         }
 
-        BenchmarkUtils.println("DataRegion fullfill complete. progress=" + progress + " replCntr=" + replCntr + ".");
+        BenchmarkUtils.println("Benchmark cache fullfill complete. progress=" + progress + " replCntr=" + replCntr + ".");
+
+        long backgroundScanInterval = args.getLongParameter("BACKGROUND_SCAN_INTERVAL", 0L);
+
+        if (backgroundScanInterval != 0) {
+            IgniteCache<Integer, Object> scanCache = ignite().getOrCreateCache(SCAN_CACHE_NAME);
+
+            try (IgniteDataStreamer<Integer, Object> streamer = ignite().dataStreamer(SCAN_CACHE_NAME)) {
+                for (int i = 0; i < replCntr; i++)
+                    streamer.addData(i, new TestValue(i));
+            }
+
+            BenchmarkUtils.println("Scan cache fullfill complete. Size=" + replCntr + ".");
+
+            backgroundScanThread = new Thread(() -> {
+                long iteration = 0;
+
+                while (ignite().cluster().state().active()) {
+                    iteration++;
+                    long size = 0;
+
+                    try (QueryCursor cursor = scanCache.query(new ScanQuery())) {
+                        for (Object o : cursor)
+                            size++;
+                    }
+
+                    BenchmarkUtils.println("Background scan iteration " + iteration + " finished, size=" + size);
+
+                    try {
+                        Thread.sleep(backgroundScanInterval);
+                    } catch (InterruptedException e) {
+                        return;
+                    }
+                }
+
+            });
+
+            backgroundScanThread.start();
+        }
 
         int cacheSize = 0;
 
@@ -110,36 +157,34 @@ public class IgnitePutGetWithPageReplacements extends IgniteCacheAbstractBenchma
         }
 
         BenchmarkUtils.println("cache size=" + cacheSize);
-    }
 
-    /** */
-    @Override protected IgniteCache<Integer, Object> cache() {
-        return ignite().getOrCreateCache(CACHE_NAME);
+        range = (int)(args.getDoubleParameter("REPLACE_RATIO", 1.0) * replCntr);
     }
 
     /** {@inheritDoc} */
-    @Override public boolean test(Map<Object, Object> map) throws Exception {
-        int portion = 100;
-
-        Map<Integer, TestValue> putMap = new HashMap<>(portion, 1.f);
+    @Override public void tearDown() throws Exception {
+        super.tearDown();
 
-        ThreadLocalRandom rnd = ThreadLocalRandom.current();
-
-        for (int i = 0; i < portion; i++) {
-            int val = rnd.nextInt(replCntr / 2);
-
-            putMap.put(val, new TestValue(val));
+        if (backgroundScanThread != null) {
+            backgroundScanThread.interrupt();
+            backgroundScanThread.join();
         }
+    }
 
-        cache().putAll(putMap);
+    /** */
+    @Override protected IgniteCache<Integer, Object> cache() {
+        return ignite().getOrCreateCache(CACHE_NAME);
+    }
 
-        return true;
+    /** */
+    protected int nextKey() {
+        return nextRandom(range);
     }
 
     /**
      * Class for test purpose.
      */
-    private static class TestValue implements Serializable {
+    protected static class TestValue implements Serializable {
         /** */
         private int id;
 
@@ -150,7 +195,7 @@ public class IgnitePutGetWithPageReplacements extends IgniteCacheAbstractBenchma
         /**
          * @param id ID.
          */
-        private TestValue(int id) {
+        protected TestValue(int id) {
             this.id = id;
         }
 
diff --git a/modules/yardstick/src/main/java/org/apache/ignite/yardstick/cache/IgniteGetWithPageReplacementBenchmark.java b/modules/yardstick/src/main/java/org/apache/ignite/yardstick/cache/IgniteGetWithPageReplacementBenchmark.java
new file mode 100644
index 0000000..d5adeb9
--- /dev/null
+++ b/modules/yardstick/src/main/java/org/apache/ignite/yardstick/cache/IgniteGetWithPageReplacementBenchmark.java
@@ -0,0 +1,41 @@
+/*
+ * 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.yardstick.cache;
+
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Benchmark get operation with page replacement.
+ */
+public class IgniteGetWithPageReplacementBenchmark extends IgniteAbstractPageReplacementBenchmark {
+    /** {@inheritDoc} */
+    @Override public boolean test(Map<Object, Object> map) throws Exception {
+        int portion = 100;
+
+        Set<Integer> getSet = new HashSet<>(portion, 1.f);
+
+        for (int i = 0; i < portion; i++)
+            getSet.add(nextKey());
+
+        cache().getAll(getSet);
+
+        return true;
+    }
+}
diff --git a/modules/yardstick/src/main/java/org/apache/ignite/yardstick/cache/IgnitePutWithPageReplacementBenchmark.java b/modules/yardstick/src/main/java/org/apache/ignite/yardstick/cache/IgnitePutWithPageReplacementBenchmark.java
new file mode 100644
index 0000000..e891200
--- /dev/null
+++ b/modules/yardstick/src/main/java/org/apache/ignite/yardstick/cache/IgnitePutWithPageReplacementBenchmark.java
@@ -0,0 +1,43 @@
+/*
+ * 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.yardstick.cache;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Benchmark put operation with page replacement.
+ */
+public class IgnitePutWithPageReplacementBenchmark extends IgniteAbstractPageReplacementBenchmark {
+    /** {@inheritDoc} */
+    @Override public boolean test(Map<Object, Object> map) throws Exception {
+        int portion = 100;
+
+        Map<Integer, TestValue> putMap = new HashMap<>(portion, 1.f);
+
+        for (int i = 0; i < portion; i++) {
+            int val = nextKey();
+
+            putMap.put(val, new TestValue(val));
+        }
+
+        cache().putAll(putMap);
+
+        return true;
+    }
+}