Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
a6f98ea
IGNITE-29032 Improvement of the pageEviction mechanism for in-memory
wernerdv Sep 3, 2026
b159894
address copilot comments
wernerdv Sep 4, 2026
22a93d4
stabilize tests
wernerdv Sep 7, 2026
4d6ca55
fix TOCTOU race
wernerdv Sep 7, 2026
430c51a
fix PDS tests
wernerdv Sep 7, 2026
622b749
fix broken tests
wernerdv Sep 8, 2026
ad83772
minor
wernerdv Sep 8, 2026
53a2c35
add benchmark
wernerdv Sep 8, 2026
fb5b61e
self-review
wernerdv Sep 8, 2026
965c9a8
address review comments
wernerdv Sep 11, 2026
a8f9e51
Merge branch 'master' into IGNITE-29032
wernerdv Sep 11, 2026
db9de4d
improvement
wernerdv Sep 11, 2026
3e810dc
minor
wernerdv Sep 14, 2026
13387dc
address review comments p2
wernerdv Sep 21, 2026
b0fda5a
refactor tests
wernerdv Sep 22, 2026
fcca5ab
reduce comments, cleanup
wernerdv Sep 22, 2026
4befc1a
small fix
wernerdv Sep 24, 2026
a814527
fix review comments
wernerdv Sep 25, 2026
a0b9226
Merge branch 'master' into IGNITE-29032
wernerdv Sep 28, 2026
4c87141
fix testPageEviction in multinode tests
wernerdv Sep 28, 2026
074185d
minor
wernerdv Sep 29, 2026
6227199
Merge branch 'master' into IGNITE-29032
wernerdv Oct 2, 2026
9304578
fix review comments
wernerdv Oct 2, 2026
98c759e
try to fix assertion error
wernerdv Oct 5, 2026
4a3339e
Merge branch 'master' into IGNITE-29032
wernerdv Oct 6, 2026
f17ca4f
fix possible deadlock for putAll/rebalance + repro for tracker assert…
wernerdv Oct 12, 2026
d40146f
move
wernerdv Oct 13, 2026
ba0af22
wip
wernerdv Oct 13, 2026
5a13e8d
atomic reserve pages
wernerdv Oct 14, 2026
4429129
reserve until entry lock
wernerdv Oct 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
/*
* 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.cache;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.ignite.Ignite;
import org.apache.ignite.IgniteCache;
import org.apache.ignite.IgniteDataStreamer;
import org.apache.ignite.Ignition;
import org.apache.ignite.configuration.CacheConfiguration;
import org.apache.ignite.configuration.DataPageEvictionMode;
import org.apache.ignite.configuration.DataRegionConfiguration;
import org.apache.ignite.configuration.DataStorageConfiguration;
import org.apache.ignite.configuration.IgniteConfiguration;
import org.apache.ignite.internal.benchmarks.jmh.runner.JmhIdeBenchmarkRunner;
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.Param;
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;

/**
* Measures the impact of size-aware page eviction on an in-memory data region.
* <p>
* {@link #putSmall()} — small values within a bounded key range that keeps the region below the eviction threshold
* (size-aware reserve fast path).
* <p>
* {@link #putLarge()} — large values against a pre-filled region near capacity (size-aware eviction loop).
*/
@State(Scope.Benchmark)
@Fork(1)
@Threads(4)
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Warmup(iterations = 3, time = 5)
@Measurement(iterations = 5, time = 10)
public class JmhPageEvictionBenchmark {
/** Default cache name. */
private static final String CACHE_NAME = "default";

/** Small value size (bytes): a single data page, far below the empty-pages pool. */
private static final int SMALL_VALUE_SIZE = 1024;

/** Large value size (bytes): larger than the empty-pages pool in page terms. */
private static final int LARGE_VALUE_SIZE = 2 * 1024 * 1024;

/** Pre-fill entries for the LARGE scenario; exceeds region capacity to pin it at the eviction threshold. */
private static final int PRE_FILL_ENTRIES = 400_000;

/** Bounded key range for {@link #putSmall()} to keep the region below the eviction threshold. */
private static final int SMALL_KEY_RANGE = 32_000;

/** Benchmark scenario: selects the value size and the pre-fill strategy. */
@Param({"SMALL", "LARGE"})
private String scenario;

/** Ignite cache. */
private IgniteCache<Integer, Object> cache;

/** Pre-allocated small value (reused to avoid allocation noise in the hot path). */
private final byte[] smallVal = new byte[SMALL_VALUE_SIZE];

/** Pre-allocated large value (reused to avoid allocation noise in the hot path). */
private final byte[] largeVal = new byte[LARGE_VALUE_SIZE];

/** Monotonic key source. */
private final AtomicInteger keyGen = new AtomicInteger();

/** Small value put within a bounded key range (fast path, no eviction). */
@Benchmark
public void putSmall() {
int key = keyGen.incrementAndGet() % SMALL_KEY_RANGE;

cache.put(key, smallVal);
}

/** Large value put against a nearly-full region (size-aware eviction loop). Single-threaded to avoid OOM. */
@Benchmark
@Threads(1)
public void putLarge() {
int key = keyGen.incrementAndGet();

cache.put(key, largeVal);
}

/** */
@Setup(Level.Trial)
public void setup() {
DataStorageConfiguration dsCfg = new DataStorageConfiguration()
.setDefaultDataRegionConfiguration(new DataRegionConfiguration()
.setPersistenceEnabled(false)
.setMaxSize(256 * 1024L * 1024L)
.setPageEvictionMode(DataPageEvictionMode.RANDOM_LRU));

IgniteConfiguration cfg = new IgniteConfiguration()
.setIgniteInstanceName("test")
.setLocalHost("127.0.0.1")
.setDataStorageConfiguration(dsCfg);

Ignite ignite = Ignition.start(cfg);

cache = ignite.getOrCreateCache(new CacheConfiguration<Integer, Object>(CACHE_NAME).setBackups(0));

if ("LARGE".equalsIgnoreCase(scenario)) {
try (IgniteDataStreamer<Integer, Object> ldr = ignite.dataStreamer(CACHE_NAME)) {
ldr.perNodeBufferSize(1024);

for (int i = 0; i < PRE_FILL_ENTRIES; i++)
ldr.addData(i, smallVal);
}

keyGen.set(PRE_FILL_ENTRIES);
}
}

/** */
@TearDown
public void tearDown() {
Ignition.stopAll(true);
}

/**
* Runs benchmark.
*
* @param args Ignored.
* @throws Exception If failed.
*/
public static void main(String[] args) throws Exception {
JmhIdeBenchmarkRunner.create()
.benchmarks(JmhPageEvictionBenchmark.class.getSimpleName())
.run();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,12 @@ public BinaryObjectImpl(BinaryContext ctx, byte[] arr, int start) {

/** {@inheritDoc} */
@Override public int valueBytesLength(CacheObjectValueContext ctx) throws IgniteCheckedException {
if (valBytes == null) {
assert arr != null;

valBytes = valueBytesFromArray(ctx);
}

return CacheObjectUtils.objectPutSize(valBytes.length);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@

import java.io.Serializable;
import org.apache.ignite.DataRegionMetrics;
import org.apache.ignite.internal.mem.IgniteOutOfMemoryException;
import org.apache.ignite.internal.util.typedef.internal.S;
import org.apache.ignite.mem.MemoryAllocator;
import org.apache.ignite.mxbean.MetricsMxBean;
Expand Down Expand Up @@ -346,9 +345,11 @@ public DataRegionConfiguration setEvictionThreshold(double evictionThreshold) {
* Specifies the minimal number of empty pages to be present in reuse lists for this data region.
* This parameter ensures that Ignite will be able to successfully evict old data entries when the size of
* (key, value) pair is slightly larger than page size / 2.
* Increase this parameter if cache can contain very big entries (total size of pages in this pool should be enough
* to contain largest cache entry).
* Increase this parameter if {@link IgniteOutOfMemoryException} occurred with enabled page eviction.
* Since size-aware eviction automatically frees additional pages when the inserted row is larger than this pool,
* it is no longer required to increase this parameter up to the size of the largest cache entry.
* It also acts as the concurrency-safety margin for size-aware eviction: the reserve keeps this many empty pages
* on top of each row's own requirement, so the insert path is safe for up to {@code emptyPagesPoolSize} concurrent
* writers.
*
* @return Minimum number of empty pages in reuse list.
*/
Expand All @@ -360,9 +361,11 @@ public int getEmptyPagesPoolSize() {
* Specifies the minimal number of empty pages to be present in reuse lists for this data region.
* This parameter ensures that Ignite will be able to successfully evict old data entries when the size of
* (key, value) pair is slightly larger than page size / 2.
* Increase this parameter if cache can contain very big entries (total size of pages in this pool should be enough
* to contain largest cache entry).
* Increase this parameter if {@link IgniteOutOfMemoryException} occurred with enabled page eviction.
* Since size-aware eviction automatically frees additional pages when the inserted row is larger than this pool,
* it is no longer required to increase this parameter up to the size of the largest cache entry.
* It also acts as the concurrency-safety margin for size-aware eviction: the reserve keeps this many empty pages
* on top of each row's own requirement, so the insert path is safe for up to {@code emptyPagesPoolSize} concurrent
* writers.
*
* @param emptyPagesPoolSize Empty pages pool size.
* @return {@code this} for chaining.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ public class PageMemoryNoStoreImpl implements PageMemory {
private static final int SEG_BITS = 4;

/** Number of bits required to store segment index. */
private static final int SEG_CNT = (1 << SEG_BITS);
public static final int SEG_CNT = (1 << SEG_BITS);

/** Number of bits left to store page index. */
private static final int IDX_BITS = PageIdUtils.PAGE_IDX_SIZE - SEG_BITS;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -665,6 +665,10 @@ public <K, V> GridCacheVersionedEntryEx<K, V> versionedEntry(final boolean keepB
* Sets new value if passed in version matches the current version
* (used for read-through only).
*
* <p>This overload reserves page space internally (before acquiring the entry lock). Use
* {@link #versionedValue(CacheObject, GridCacheVersion, GridCacheVersion, IgniteCacheExpiryPolicy,
* ReaderArguments, long)} when the caller has already reserved (or the entry is already locked).
*
* @param val New value.
* @param curVer Version to match or {@code null} if match is not required.
* @param newVer Version to set.
Expand All @@ -681,6 +685,29 @@ public EntryGetResult versionedValue(CacheObject val,
@Nullable ReaderArguments readerArgs)
throws IgniteCheckedException, GridCacheEntryRemovedException;

/**
* Sets new value if passed in version matches the current version (used for read-through only).
*
* @param val New value.
* @param curVer Version to match or {@code null} if match is not required.
* @param newVer Version to set.
* @param loadExpiryPlc Expiry policy if entry is loaded from store.
* @param readerArgs Reader will be added if not null.
* @param reserved Number of pages reserved by a prior call to {@code reserveEntrySpace} (before acquiring the
* entry lock), or {@code 0} if no reservation was made.
* @return Current version and value.
* @throws IgniteCheckedException If index could not be updated.
* @throws GridCacheEntryRemovedException If entry was removed.
*/
public EntryGetResult versionedValue(
CacheObject val,
@Nullable GridCacheVersion curVer,
@Nullable GridCacheVersion newVer,
@Nullable IgniteCacheExpiryPolicy loadExpiryPlc,
@Nullable ReaderArguments readerArgs,
long reserved
) throws IgniteCheckedException, GridCacheEntryRemovedException;

/**
* Checks if the candidate is either owner or pending.
*
Expand Down
Loading
Loading