Skip to content

Slow(ish) read/deserialization performance #133

@sheinbergon

Description

@sheinbergon

Hello

I've create a JMH benchmark to test Nitrite random read performance.
I insert a varying amount of entities into an in-memory only nitrite ObjectRepository and then query it using an indexed field with a varying degree of dispersion/randomness.

All entities are of a single types that implements Mappable

While searching the database using the index takes a few microseconds, which is great, deserializing the result set (a few dozens of entities) takes a few milliseconds. which is 3 orders of magnitude slower.

Is it possible to improve upon these numbers with nitrite?

Results (numbers are microseconds per operation (us/op) ):

Number of entities Also read data? Indexed field disperssion Iterations Results
100000 true 5000 12 8839.989
100000 true 10000 12 3845.652
100000 true 20000 12 2009.990
100000 false 5000 12 3.109
100000 false 10000 12 3.686
100000 false 20000 12 3.823
200000 true 5000 12 13159.064
200000 true 10000 12 6740.482
200000 true 20000 12 3755.540
200000 false 5000 12 3.219
200000 false 10000 12 3.181
200000 false 20000 12 3.467

Here's the benchmark code (uses JMH and Lombok annotations):

package org.sheinbergon;


import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import lombok.val;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.RandomUtils;
import org.dizitart.no2.Document;
import org.dizitart.no2.IndexOptions;
import org.dizitart.no2.IndexType;
import org.dizitart.no2.Nitrite;
import org.dizitart.no2.mapper.Mappable;
import org.dizitart.no2.mapper.NitriteMapper;
import org.dizitart.no2.objects.Id;
import org.dizitart.no2.objects.ObjectRepository;
import org.dizitart.no2.objects.filters.ObjectFilters;
import org.openjdk.jmh.annotations.*;

import java.math.BigDecimal;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@State(Scope.Benchmark)
public class NitriteBenchmark {

    private final static int FORKS = 2;
    private final static int WARMUPS = 2;
    private final static int ITERATIONS = 6;
    private final static int MILLISECONDS = 1000;

    private final static int RANDOM_STRING_MIN_LENGTH = 100;
    private final static int RANDOM_STRING_MAX_LENGTH = 200;

    private final static long INDEXED_INTEGER_FIELD_LOWER_BOUND = 0L;

    @Getter
    @Setter
    @Accessors(fluent = true, chain = true)
    private static class NMappable implements Mappable {

        @Getter
        @Setter
        @Accessors(fluent = true, chain = true)
        private static class IMappable implements Mappable {
            private BigDecimal innerNumber1;
            private BigDecimal innerNumber2;
            private BigDecimal innerNumber3;

            @Override
            public Document write(NitriteMapper mapper) {
                val d = new Document();
                d.put("innerNumber1", innerNumber1);
                d.put("innerNumber2", innerNumber2);
                d.put("innerNumber3", innerNumber3);
                return d;
            }

            @Override
            public void read(NitriteMapper mapper, Document document) {
                innerNumber1 = document.get("innerNumber1", BigDecimal.class);
                innerNumber2 = document.get("innerNumber2", BigDecimal.class);
                innerNumber3 = document.get("innerNumber3", BigDecimal.class);
            }
        }

        @Id
        private String text1;
        private String text2;
        private String text3;
        private BigDecimal decimal1;
        private Long integer1;
        private Boolean flag1;
        private IMappable inner;

        @Override
        public Document write(NitriteMapper mapper) {
            val d = new Document();
            d.put("text1", text1);
            d.put("text2", text2);
            d.put("text3", text3);
            d.put("decimal1", decimal1);
            d.put("integer1", integer1);
            d.put("flag1", flag1);
            d.put("inner", mapper.asDocument(inner));
            return d;
        }

        @Override
        public void read(NitriteMapper mapper, Document document) {
            text1 = document.get("text1", String.class);
            text2 = document.get("text2", String.class);
            text3 = document.get("text3", String.class);
            decimal1 = document.get("decimal1", BigDecimal.class);
            integer1 = document.get("integer1", Long.class);
            flag1 = document.get("flag1", Boolean.class);
            inner = mapper.asObject(document.get("inner", Document.class), IMappable.class);
        }
    }

    private Nitrite nitrite;
    private ObjectRepository<NMappable> repository;

    private List<NMappable> entities;

    @Param({"100000", "200000"})
    private int entityCount;

    @Param({"5000", "10000", "20000"})
    private int indexFieldDispersion;

    @Param({"true", "false"})
    private boolean fetchData;

    @Setup
    public void setup() {
        nitrite = Nitrite.builder().openOrCreate();
        repository = nitrite.getRepository(NMappable.class);
        entities = IntStream.range(0, entityCount)
                .mapToObj(index -> randomEntity())
                .collect(Collectors.toList());
        repository.createIndex("integer1", IndexOptions.indexOptions(IndexType.NonUnique));
        repository.insert(entities.toArray(NMappable[]::new));
    }

    @TearDown
    public void teardown() {
        repository.close();
    }

    @Benchmark
    @Fork(value = FORKS, jvmArgsAppend = {
            "--add-exports=java.base/jdk.internal.ref=ALL-UNNAMED",
            "-Xmx4096m",
            "-Xms2048m"})
    @Warmup(iterations = WARMUPS, timeUnit = TimeUnit.MILLISECONDS, time = MILLISECONDS)
    @Measurement(iterations = ITERATIONS, timeUnit = TimeUnit.MILLISECONDS, time = MILLISECONDS)
    public void benchmarkQueries() {
        val entity = entities.get(RandomUtils.nextInt(0, entityCount));
        val result = repository.find(ObjectFilters.eq("integer1", entity.integer1));
        if (fetchData) {
            result.forEach(m -> {
            });
        }
    }

    private NMappable randomEntity() {
        return new NMappable()
                .text1(RandomStringUtils.randomAlphanumeric(RANDOM_STRING_MIN_LENGTH, RANDOM_STRING_MAX_LENGTH))
                .text2(RandomStringUtils.randomAlphanumeric(RANDOM_STRING_MIN_LENGTH, RANDOM_STRING_MAX_LENGTH))
                .text3(RandomStringUtils.randomAlphanumeric(RANDOM_STRING_MIN_LENGTH, RANDOM_STRING_MAX_LENGTH))
                .decimal1(BigDecimal.valueOf(RandomUtils.nextDouble()))
                .integer1(RandomUtils.nextLong(INDEXED_INTEGER_FIELD_LOWER_BOUND, indexFieldDispersion))
                .flag1(RandomUtils.nextBoolean())
                .inner(new NMappable.IMappable()
                        .innerNumber1(BigDecimal.valueOf(RandomUtils.nextLong()))
                        .innerNumber2(BigDecimal.valueOf(RandomUtils.nextDouble()))
                        .innerNumber3(BigDecimal.valueOf(RandomUtils.nextDouble())));
    }

}

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

Status

Done

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions