Skip to content

IllegalArgumentException in OffHeapStore #3051

Open

Description

@manaldush

Hi, I built cache with next operations:

store = new MVStore.Builder()
.cacheConcurrency(1)
.fileStore(new OffHeapStore())
.open();
cache = store.openMap("data");

I have 50 threads and in every thread I have thread local cache created with above commands. Then in every thread I run get/put commands with thread local cache and in the end exception occurred

java.lang.IllegalArgumentException
at java.nio.Buffer.position(Unknown Source)
at org.h2.mvstore.OffHeapStore.readFully(OffHeapStore.java:45)
at org.h2.mvstore.Chunk.readBufferForPage(Chunk.java:361)
at org.h2.mvstore.MVStore.readBufferForPage(MVStore.java:1214)
at org.h2.mvstore.MVStore.readPage(MVStore.java:2209)
at org.h2.mvstore.MVMap.readPage(MVMap.java:672)
at org.h2.mvstore.Page$NonLeaf.getChildPage(Page.java:1043)
at org.h2.mvstore.Page.get(Page.java:222)
at org.h2.mvstore.MVMap.get(MVMap.java:472)
at org.h2.mvstore.MVMap.get(MVMap.java:459)

    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <version>1.4.200</version>
    </dependency>

Activity

  1. andreitokar commented on Mar 5, 2021

    @andreitokar
    Contributor

    @manaldush

    I would help a lot, if you will post here a complete self-contained test case.
    My gut feeling is that your application uses MVMap as a drop-in replacement of the java Map, and it's missing MVStore.registerVersionUsage() / deregisterVersionUsage() calls.

  2. manaldush commented on Mar 6, 2021

    @manaldush
    Author

    @andreitokar
    Thank you, I am going to understand, can we use h2 as in-memory offheap map and compare its performance with another storages. We have few processing threads and we can use storage in two modes:

    1. As singleton shared over all threads
    2. Every single thread has its own thread local storage, and data is sharded over all storages.

    Now we are talking about second mode. In my test I've created 50 threads and in every thread I execute operation

    private void load(int characterId) {
        Character character = cache.get(characterId);
        if (character == null) {
            cache.put(characterId, generator.createNewCharacter());
        } else {
            generator.changeCharactersClan(character);
            cache.put(characterId, character);
        }
    }
    

    Get Character(object used as Entry in storages), if object was not found, create it, but if it was found, update it

    I've attached two files:

    H2NoTxCharacterCache.java is decorator over h2 storage, here you can find details of using h2 for test

    public class H2NoTxCharacterCache implements CharacterCache {
    private MVMap<Integer, String> cache;
    private MVStore store;
    private final Serializer serializer = new Serializer();

    public H2NoTxCharacterCache() {
        store = new MVStore.Builder()
                .cacheConcurrency(1)
                .fileStore(new OffHeapStore())
                .open();
        cache = store.openMap("data");
    }
    
    @Override
    public Character get(int id) {
        String value = cache.get(id);
        if (value == null) {
            return null;
        }
        return serializer.deserialize(value, Character.class);
    }
    
    @Override
    public void put(int id, Character character) {
        String value = serializer.serialize(character);
        cache.put(id, value);
    }
    
    @Override
    public void inTx(int key, Runnable runnable) {
        runnable.run();
    }
    
    @Override
    public void close() {
        store.close();
    }
    
    @Override
    public void dumpOffHeapMemory() {
    
    }
    

    }

    LoadGeneratingCachePerThreadBenchmark.java - test file, use JMH benchmark for load generation

    @State(Scope.Thread)
    @WarmUp(iterations = 30, time = 1, timeUnit = TimeUnit.SECONDS)
    @measurement(iterations = 50, time = 1, timeUnit = TimeUnit.SECONDS)
    @fork(value = 1, jvmArgsAppend = "-XX:MaxDirectMemorySize=10G")
    @threads(50)
    @BenchmarkMode({Mode.Throughput, Mode.AverageTime})
    @OutputTimeUnit(TimeUnit.MILLISECONDS)
    public class LoadGeneratingCachePerThreadBenchmark {

    private static AtomicInteger seedController = new AtomicInteger(1);
    @Param({"100000"})
    private volatile int size;
    private final CharacterGenerator generator = new CharacterGenerator(seedController.getAndIncrement());
    @Param({"h2"})
    private volatile String cacheType;
    private CharacterCache cache;
    
    @Setup
    public void prepare() {
       if (cacheType.equalsIgnoreCase("h2")) {
            cache = new H2NoTxCharacterCache();
        } else {
            throw new IllegalArgumentException();
        }
    }
    
    @TearDown
    public void release() {
        if (cache != null) {
            cache.close();
        }
    }
    
    @Benchmark
    public void run() {
        int characterId = generator.nextInt(size);
        cache.inTx(characterId, () -> {
            load(characterId);
        });
    }
    
    private void load(int characterId) {
        Character character = cache.get(characterId);
        if (character == null) {
            cache.put(characterId, generator.createNewCharacter());
        } else {
            generator.changeCharactersClan(character);
            cache.put(characterId, character);
        }
    }
    

    }

    Thanks for help

  3. andreitokar commented on Mar 7, 2021

    @andreitokar
    Contributor

    It is still far from a self-contained test case. But my gut feeling was correct. You do need to wrap access to MVMap into MVStore.registerVersionUsage() / deregisterVersionUsage() calls. You can wrap individual get()/put() or whole group of operations, like iteration. It's like transaction demarcation, and it may affect performance.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions