From 2b25d65db77af482b8befd800776e8c706bc4914 Mon Sep 17 00:00:00 2001 From: Liran Funaro Date: Mon, 30 Nov 2020 12:42:09 +0200 Subject: [PATCH 01/33] Introduce oak-incremental-index --- .../oak-incremental-index/assembly.xml | 45 ++ .../oak-incremental-index/pom.xml | 287 ++++++++ .../incremental/oak/OakIncrementalIndex.java | 626 ++++++++++++++++++ .../oak/OakIncrementalIndexModule.java | 53 ++ .../oak/OakIncrementalIndexRow.java | 168 +++++ .../oak/OakIncrementalIndexSpec.java | 92 +++ .../incremental/oak/OakInputRowContext.java | 59 ++ .../druid/segment/incremental/oak/OakKey.java | 558 ++++++++++++++++ .../oak/OffheapIncrementalIndexSpec.java | 97 +++ ...rg.apache.druid.initialization.DruidModule | 16 + .../incremental/oak/OakBenchmarks.java | 121 ++++ .../incremental/oak/OakDummyInitTest.java | 44 ++ .../segment/incremental/oak/OakTestSuite.java | 49 ++ licenses.yaml | 10 + pom.xml | 1 + .../druid/segment/DoubleDimensionIndexer.java | 15 +- .../druid/segment/FloatDimensionIndexer.java | 15 +- .../druid/segment/LongDimensionIndexer.java | 15 +- .../druid/segment/StringDimensionIndexer.java | 138 ++-- .../segment/StringDimensionMergerV9.java | 2 +- .../segment/incremental/IncrementalIndex.java | 38 +- .../incremental/IncrementalIndexAdapter.java | 18 +- .../incremental/IncrementalIndexRow.java | 31 +- .../incremental/OffheapIncrementalIndex.java | 20 +- .../incremental/OnheapIncrementalIndex.java | 22 +- .../IncrementalIndexAdapterTest.java | 33 +- .../IncrementalIndexIngestionTest.java | 6 +- 27 files changed, 2435 insertions(+), 144 deletions(-) create mode 100644 extensions-contrib/oak-incremental-index/assembly.xml create mode 100644 extensions-contrib/oak-incremental-index/pom.xml create mode 100644 extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndex.java create mode 100644 extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndexModule.java create mode 100644 extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndexRow.java create mode 100644 extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndexSpec.java create mode 100644 extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakInputRowContext.java create mode 100644 extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakKey.java create mode 100644 extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OffheapIncrementalIndexSpec.java create mode 100644 extensions-contrib/oak-incremental-index/src/main/resources/META-INF/services/org.apache.druid.initialization.DruidModule create mode 100644 extensions-contrib/oak-incremental-index/src/test/java/org/apache/druid/segment/incremental/oak/OakBenchmarks.java create mode 100644 extensions-contrib/oak-incremental-index/src/test/java/org/apache/druid/segment/incremental/oak/OakDummyInitTest.java create mode 100644 extensions-contrib/oak-incremental-index/src/test/java/org/apache/druid/segment/incremental/oak/OakTestSuite.java diff --git a/extensions-contrib/oak-incremental-index/assembly.xml b/extensions-contrib/oak-incremental-index/assembly.xml new file mode 100644 index 000000000000..eb5c0ad27265 --- /dev/null +++ b/extensions-contrib/oak-incremental-index/assembly.xml @@ -0,0 +1,45 @@ + + + + + fat-benchmark + + jar + + false + + + / + true + true + test + + + + + ${project.build.directory}/test-classes + / + true + + + \ No newline at end of file diff --git a/extensions-contrib/oak-incremental-index/pom.xml b/extensions-contrib/oak-incremental-index/pom.xml new file mode 100644 index 000000000000..2c98904a7b9c --- /dev/null +++ b/extensions-contrib/oak-incremental-index/pom.xml @@ -0,0 +1,287 @@ + + + + + 4.0.0 + + org.apache.druid.extensions + oak-incremental-index + oak-incremental-index + Druid incremental-index based on Oak lib https://github.com/yahoo/Oak + + + org.apache.druid + druid + 0.21.0-SNAPSHOT + ../../pom.xml + + + + + com.yahoo.oak + oak + 0.2.3 + + + + org.apache.druid + druid-core + ${project.parent.version} + provided + + + org.apache.druid + druid-processing + ${project.parent.version} + provided + + + + com.google.code.findbugs + jsr305 + provided + + + com.google.guava + guava + provided + + + com.google.inject + guice + provided + + + commons-codec + commons-codec + provided + + + it.unimi.dsi + fastutil + provided + + + + com.fasterxml.jackson.core + jackson-annotations + provided + + + com.fasterxml.jackson.core + jackson-core + provided + + + com.fasterxml.jackson.core + jackson-databind + provided + + + com.fasterxml.jackson.datatype + jackson-datatype-guava + provided + + + com.fasterxml.jackson.datatype + jackson-datatype-joda + provided + + + com.fasterxml.jackson.dataformat + jackson-dataformat-smile + provided + + + com.fasterxml.jackson.jaxrs + jackson-jaxrs-json-provider + provided + + + com.fasterxml.jackson.jaxrs + jackson-jaxrs-smile-provider + provided + + + + + junit + junit + test + + + joda-time + joda-time + + + org.openjdk.jmh + jmh-core + ${jmh.version} + test + + + org.easymock + easymock + test + + + nl.jqno.equalsverifier + equalsverifier + test + + + org.apache.druid + druid-core + ${project.parent.version} + test-jar + test + + + org.apache.druid + druid-processing + ${project.parent.version} + test-jar + test + + + org.apache.druid + druid-server + ${project.parent.version} + test + test-jar + + + org.apache.druid + druid-sql + ${project.parent.version} + test-jar + test + + + org.apache.druid + druid-benchmarks + ${project.parent.version} + test-jar + test + + + + + + UTF-8 + 1.21 + 1.8 + oak-benchmarks + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + ${javac.target} + ${javac.target} + ${javac.target} + + + + org.apache.maven.plugins + maven-assembly-plugin + 2.6 + + + package + + single + + + ${uberjar.name} + false + assembly.xml + + + org.openjdk.jmh.Main + + + + + + + + org.owasp + dependency-check-maven + + true + + + + org.jacoco + jacoco-maven-plugin + + true + + + + + + + maven-clean-plugin + 2.5 + + + maven-deploy-plugin + 2.8.1 + + + maven-install-plugin + 2.5.1 + + + maven-jar-plugin + 2.4 + + + maven-javadoc-plugin + 2.9.1 + + + maven-resources-plugin + 2.6 + + + maven-site-plugin + 3.3 + + + maven-source-plugin + 2.2.1 + + + maven-surefire-plugin + + @{jacocoArgLine} + + + + + + diff --git a/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndex.java b/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndex.java new file mode 100644 index 000000000000..340e6c7903cc --- /dev/null +++ b/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndex.java @@ -0,0 +1,626 @@ +/* + * 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.druid.segment.incremental.oak; + + +import com.google.common.base.Supplier; +import com.google.common.collect.Iterators; +import com.google.common.collect.Maps; +import com.yahoo.oak.OakBuffer; +import com.yahoo.oak.OakMap; +import com.yahoo.oak.OakMapBuilder; +import com.yahoo.oak.OakScopedReadBuffer; +import com.yahoo.oak.OakScopedWriteBuffer; +import com.yahoo.oak.OakSerializer; +import com.yahoo.oak.OakUnsafeDirectBuffer; +import com.yahoo.oak.OakUnscopedBuffer; +import org.apache.druid.annotations.EverythingIsNonnullByDefault; +import org.apache.druid.data.input.InputRow; +import org.apache.druid.data.input.MapBasedRow; +import org.apache.druid.data.input.Row; +import org.apache.druid.java.util.common.StringUtils; +import org.apache.druid.java.util.common.logger.Logger; +import org.apache.druid.java.util.common.parsers.ParseException; +import org.apache.druid.query.aggregation.AggregatorFactory; +import org.apache.druid.query.aggregation.BufferAggregator; +import org.apache.druid.query.aggregation.PostAggregator; +import org.apache.druid.segment.ColumnSelectorFactory; +import org.apache.druid.segment.DimensionHandler; +import org.apache.druid.segment.DimensionIndexer; +import org.apache.druid.segment.incremental.AppendableIndexBuilder; +import org.apache.druid.segment.incremental.IncrementalIndex; +import org.apache.druid.segment.incremental.IncrementalIndexRow; +import org.apache.druid.segment.incremental.IncrementalIndexSchema; +import org.apache.druid.segment.incremental.IndexSizeExceededException; +import org.apache.druid.segment.incremental.OnheapIncrementalIndex; + +import javax.annotation.Nullable; +import javax.xml.ws.Holder; +import java.nio.ByteBuffer; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Function; + + +/** + * OakIncrementalIndex has two main attributes that are different from the other IncrementalIndex implementations: + * 1. It stores both **keys** and **values** off-heap (as opposed to the off-heap implementation that stores only + * the **values** off-heap). + * 2. It is based on OakMap (https://github.com/yahoo/Oak) instead of Java's ConcurrentSkipList. + * These two changes significantly reduce the number of heap-objects and thus decrease dramatically the GC's memory + * and performance overhead. + */ +@EverythingIsNonnullByDefault +public class OakIncrementalIndex extends IncrementalIndex implements IncrementalIndex.FactsHolder +{ + protected final int maxRowCount; + protected final long maxBytesInMemory; + private final boolean rollup; + + private final OakMap facts; + private final AtomicInteger rowIndexGenerator; + + @Nullable + private Map selectors; + + // Given a ByteBuffer and an offset inside the buffer, offset + aggOffsetInBuffer[i] + // would give a position in the buffer where the i^th aggregator's value is stored. + @Nullable + private int[] aggregatorOffsetInBuffer; + private int aggregatorsTotalSize; + + private static final Logger log = new Logger(OakIncrementalIndex.class); + + @Nullable + private String outOfRowsReason = null; + + public OakIncrementalIndex(IncrementalIndexSchema incrementalIndexSchema, + boolean deserializeComplexMetrics, + boolean concurrentEventAdd, + int maxRowCount, + long maxBytesInMemory, + long oakMaxMemoryCapacity, + int oakBlockSize, + int oakChunkMaxItems) + { + super(incrementalIndexSchema, deserializeComplexMetrics, concurrentEventAdd); + + assert selectors != null; + assert aggregatorOffsetInBuffer != null; + + this.maxRowCount = maxRowCount; + this.maxBytesInMemory = maxBytesInMemory <= 0 ? Long.MAX_VALUE : maxBytesInMemory; + + this.rowIndexGenerator = new AtomicInteger(0); + this.rollup = incrementalIndexSchema.isRollup(); + + final IncrementalIndexRow minRow = new IncrementalIndexRow( + incrementalIndexSchema.getMinTimestamp(), + OakIncrementalIndexRow.NO_DIMS, + dimensionDescsList, + IncrementalIndexRow.EMPTY_ROW_INDEX + ); + + this.facts = new OakMapBuilder<>( + new OakKey.Comparator(dimensionDescsList, this.rollup), + new OakKey.Serializer(dimensionDescsList, this.rowIndexGenerator), + new OakValueSerializer(), + minRow + ).setPreferredBlockSize(oakBlockSize) + .setChunkMaxItems(oakChunkMaxItems) + .setMemoryCapacity(oakMaxMemoryCapacity) + .build(); + } + + @Override + public FactsHolder getFacts() + { + return this; + } + + @Override + public boolean canAppendRow() + { + final boolean countCheck = getNumEntries().get() < maxRowCount; + // if maxBytesInMemory = -1, then ignore sizeCheck + final boolean sizeCheck = maxBytesInMemory <= 0 || getBytesInMemory().get() < maxBytesInMemory; + final boolean canAdd = countCheck && sizeCheck; + if (!countCheck && !sizeCheck) { + outOfRowsReason = StringUtils.format( + "Maximum number of rows [%d] and maximum size in bytes [%d] reached", + maxRowCount, + maxBytesInMemory + ); + } else if (!countCheck) { + outOfRowsReason = StringUtils.format("Maximum number of rows [%d] reached", maxRowCount); + } else if (!sizeCheck) { + outOfRowsReason = StringUtils.format("Maximum size in bytes [%d] reached", maxBytesInMemory); + } + + return canAdd; + } + + @Override + public String getOutOfRowsReason() + { + return outOfRowsReason; + } + + @Override + public void close() + { + super.close(); + + for (BufferAggregator agg : getAggs()) { + if (agg != null) { + agg.close(); + } + } + + if (selectors != null) { + selectors.clear(); + } + + clear(); + } + + @Override + protected AddToFactsResult addToFacts(InputRow row, + IncrementalIndexRow key, + ThreadLocal rowContainer, + Supplier rowSupplier, + boolean skipMaxRowsInMemoryCheck) throws IndexSizeExceededException + { + if (!skipMaxRowsInMemoryCheck) { + // We validate here as a sanity check that we did not exceed the row and memory limitations + // in previous insertions. + if (getNumEntries().get() > maxRowCount || (maxBytesInMemory > 0 && getBytesInMemory().get() > maxBytesInMemory)) { + throw new IndexSizeExceededException( + "Maximum number of rows [%d out of %d] or max size in bytes [%d out of %d] reached", + getNumEntries().get(), maxRowCount, + getBytesInMemory().get(), maxBytesInMemory + ); + } + } + + final OakInputRowContext ctx = new OakInputRowContext(rowContainer, row); + if (rollup) { + // In rollup mode, we let the key-serializer assign the row index. + // Upon lookup, the comparator ignores this special index value and only compares according to the key itself. + // The serializer is only called on insertion, so it will not increment the index if the key already exits. + key.setRowIndex(OakKey.Serializer.ASSIGN_ROW_INDEX_IF_ABSENT); + } else { + // In plain mode, we force a new row index. + // Upon lookup, since there is no key with this index, a new key will be inserted every time. + key.setRowIndex(rowIndexGenerator.getAndIncrement()); + } + + // This call is different from FactsHolder.putIfAbsent() because it also handles the aggregation + // in case the key already exits. + facts.zc().putIfAbsentComputeIfPresent(key, ctx, buffer -> aggregate(ctx, buffer)); + + int rowCount = facts.size(); + long memorySize = facts.memorySize(); + + getNumEntries().set(rowCount); + getBytesInMemory().set(memorySize); + + return new AddToFactsResult(rowCount, memorySize, ctx.parseExceptionMessages); + } + + @Override + public int getLastRowIndex() + { + return rowIndexGenerator.get() - 1; + } + + @Override + protected BufferAggregator[] getAggsForRow(int rowOffset) + { + // We should never get here because we override iterableWithPostAggregations + throw new UnsupportedOperationException(); + } + + @Override + protected Object getAggVal(BufferAggregator agg, int rowOffset, int aggPosition) + { + // We should never get here because we override iterableWithPostAggregations + // This implementation does not need an additional structure to keep rowOffset + throw new UnsupportedOperationException(); + } + + private int getOffsetInBuffer(int aggOffset, int aggIndex) + { + assert aggregatorOffsetInBuffer != null; + return aggOffset + aggregatorOffsetInBuffer[aggIndex]; + } + + private int getOffsetInBuffer(OakIncrementalIndexRow oakRow, int aggIndex) + { + return getOffsetInBuffer(oakRow.getAggregationsOffset(), aggIndex); + } + + @Override + protected float getMetricFloatValue(IncrementalIndexRow incrementalIndexRow, int aggIndex) + { + OakIncrementalIndexRow oakRow = (OakIncrementalIndexRow) incrementalIndexRow; + return getAggs()[aggIndex].getFloat(oakRow.getAggregationsBuffer(), getOffsetInBuffer(oakRow, aggIndex)); + } + + @Override + protected long getMetricLongValue(IncrementalIndexRow incrementalIndexRow, int aggIndex) + { + OakIncrementalIndexRow oakRow = (OakIncrementalIndexRow) incrementalIndexRow; + return getAggs()[aggIndex].getLong(oakRow.getAggregationsBuffer(), getOffsetInBuffer(oakRow, aggIndex)); + } + + @Override + protected Object getMetricObjectValue(IncrementalIndexRow incrementalIndexRow, int aggIndex) + { + OakIncrementalIndexRow oakRow = (OakIncrementalIndexRow) incrementalIndexRow; + return getAggs()[aggIndex].get(oakRow.getAggregationsBuffer(), getOffsetInBuffer(oakRow, aggIndex)); + } + + @Override + protected double getMetricDoubleValue(IncrementalIndexRow incrementalIndexRow, int aggIndex) + { + OakIncrementalIndexRow oakRow = (OakIncrementalIndexRow) incrementalIndexRow; + return getAggs()[aggIndex].getDouble(oakRow.getAggregationsBuffer(), getOffsetInBuffer(oakRow, aggIndex)); + } + + @Override + protected boolean isNull(IncrementalIndexRow incrementalIndexRow, int aggIndex) + { + OakIncrementalIndexRow oakRow = (OakIncrementalIndexRow) incrementalIndexRow; + return getAggs()[aggIndex].isNull(oakRow.getAggregationsBuffer(), getOffsetInBuffer(oakRow, aggIndex)); + } + + @Override + public Iterable iterableWithPostAggregations( + @Nullable final List postAggs, + final boolean descending + ) + { + final AggregatorFactory[] metrics = getMetricAggs(); + final BufferAggregator[] aggregators = getAggs(); + + // It might be possible to rewrite this function to return a serialized row. + Function, Row> transformer = entry -> { + OakUnsafeDirectBuffer keyOakBuff = (OakUnsafeDirectBuffer) entry.getKey(); + OakUnsafeDirectBuffer valueOakBuff = (OakUnsafeDirectBuffer) entry.getValue(); + long serializedKeyAddress = keyOakBuff.getAddress(); + + long timeStamp = OakKey.getTimestamp(serializedKeyAddress); + int dimsLength = OakKey.getDimsLength(serializedKeyAddress); + + Map theVals = Maps.newLinkedHashMap(); + for (int i = 0; i < dimsLength; ++i) { + Object dim = OakKey.getDim(serializedKeyAddress, i); + DimensionDesc dimensionDesc = dimensionDescsList.get(i); + if (dimensionDesc == null) { + continue; + } + String dimensionName = dimensionDesc.getName(); + DimensionHandler handler = dimensionDesc.getHandler(); + if (dim == null || handler.getLengthOfEncodedKeyComponent(dim) == 0) { + theVals.put(dimensionName, null); + continue; + } + final DimensionIndexer indexer = dimensionDesc.getIndexer(); + Object rowVals = indexer.convertUnsortedEncodedKeyComponentToActualList(dim); + theVals.put(dimensionName, rowVals); + } + + ByteBuffer valueBuff = valueOakBuff.getByteBuffer(); + int valueOffset = valueOakBuff.getOffset(); + for (int i = 0; i < aggregators.length; ++i) { + Object theVal = aggregators[i].get(valueBuff, valueOffset + aggregatorOffsetInBuffer[i]); + theVals.put(metrics[i].getName(), theVal); + } + + return new MapBasedRow(timeStamp, theVals); + }; + + return () -> transformIterator(descending, transformer); + } + + // Aggregator management: initialization and aggregation + + @Override + protected BufferAggregator[] initAggs(AggregatorFactory[] metrics, + Supplier rowSupplier, + boolean deserializeComplexMetrics, + boolean concurrentEventAdd) + { + this.selectors = new HashMap<>(); + this.aggregatorOffsetInBuffer = new int[metrics.length]; + + int curAggOffset = 0; + for (int i = 0; i < metrics.length; i++) { + aggregatorOffsetInBuffer[i] = curAggOffset; + curAggOffset += metrics[i].getMaxIntermediateSizeWithNulls(); + } + this.aggregatorsTotalSize = curAggOffset; + + for (AggregatorFactory agg : metrics) { + ColumnSelectorFactory columnSelectorFactory = makeColumnSelectorFactory( + agg, + rowSupplier, + deserializeComplexMetrics + ); + + this.selectors.put( + agg.getName(), + new OnheapIncrementalIndex.CachingColumnSelectorFactory(columnSelectorFactory, concurrentEventAdd) + ); + } + + return new BufferAggregator[metrics.length]; + } + + public void initAggValue(OakInputRowContext ctx, ByteBuffer aggBuffer, int aggOffset) + { + AggregatorFactory[] metrics = getMetricAggs(); + BufferAggregator[] aggregators = getAggs(); + assert selectors != null; + + if (aggregators.length > 0 && aggregators[aggregators.length - 1] == null) { + synchronized (this) { + if (aggregators[aggregators.length - 1] == null) { + // note: creation of Aggregators is done lazily when at least one row from input is available + // so that FilteredAggregators could be initialized correctly. + ctx.setRow(); + for (int i = 0; i < metrics.length; i++) { + final AggregatorFactory agg = metrics[i]; + if (aggregators[i] == null) { + aggregators[i] = agg.factorizeBuffered(selectors.get(agg.getName())); + } + } + ctx.clearRow(); + } + } + } + + for (int i = 0; i < metrics.length; i++) { + aggregators[i].init(aggBuffer, getOffsetInBuffer(aggOffset, i)); + } + + aggregate(ctx, aggBuffer, aggOffset); + } + + public void aggregate(OakInputRowContext ctx, OakBuffer buffer) + { + OakUnsafeDirectBuffer unsafeBuffer = (OakUnsafeDirectBuffer) buffer; + aggregate(ctx, unsafeBuffer.getByteBuffer(), unsafeBuffer.getOffset()); + } + + public void aggregate(OakInputRowContext ctx, ByteBuffer aggBuffer, int aggOffset) + { + final BufferAggregator[] aggregators = getAggs(); + + ctx.setRow(); + + for (int i = 0; i < aggregators.length; i++) { + final BufferAggregator agg = aggregators[i]; + + try { + agg.aggregate(aggBuffer, getOffsetInBuffer(aggOffset, i)); + } + catch (ParseException e) { + // "aggregate" can throw ParseExceptions if a selector expects something but gets something else. + log.debug(e, "Encountered parse error, skipping aggregator[%s].", getMetricAggs()[i].getName()); + ctx.addException(e); + } + } + + ctx.clearRow(); + } + + /** + * Responsible for the initialization of the aggregators of a new inserted row. + * It is activated when a new row is serialized before insertion to the facts map. + */ + class OakValueSerializer implements OakSerializer + { + @Override + public void serialize(OakInputRowContext ctx, OakScopedWriteBuffer buffer) + { + OakUnsafeDirectBuffer unsafeBuffer = (OakUnsafeDirectBuffer) buffer; + initAggValue(ctx, unsafeBuffer.getByteBuffer(), unsafeBuffer.getOffset()); + } + + @Override + public OakInputRowContext deserialize(OakScopedReadBuffer buffer) + { + // cannot deserialize without the IncrementalIndexRow + throw new UnsupportedOperationException(); + } + + @Override + public int calculateSize(OakInputRowContext row) + { + return aggregatorsTotalSize; + } + } + + // FactsHolder helper methods + + public Iterator transformIterator( + boolean descending, + Function, Row> transformer + ) + { + OakMap orderedFacts = descending ? facts.descendingMap() : facts; + return orderedFacts.zc().entrySet().stream().map(transformer).iterator(); + } + + /** + * Generate a new row object for each iterated item. + */ + private Iterator transformNonStreamIterator( + Iterator> iterator) + { + return Iterators.transform(iterator, entry -> + new OakIncrementalIndexRow(entry.getKey(), dimensionDescsList, entry.getValue())); + } + + /** + * Since the buffers in the stream iterators are reused, we don't need to create + * a new row object for each next() call. + * See {@code OakIncrementalIndexRow.reset()} for more information. + */ + private Iterator transformStreamIterator( + Iterator> iterator) + { + Holder row = new Holder<>(); + + return Iterators.transform(iterator, entry -> { + if (row.value == null) { + row.value = new OakIncrementalIndexRow(entry.getKey(), dimensionDescsList, entry.getValue()); + } else { + row.value.reset(); + } + return row.value; + }); + } + + // FactsHolder interface implementation + + @Override + public int getPriorIndex(IncrementalIndexRow key) + { + return 0; + } + + @Override + public long getMinTimeMillis() + { + return facts.firstKey().getTimestamp(); + } + + @Override + public long getMaxTimeMillis() + { + return facts.lastKey().getTimestamp(); + } + + @Override + public Iterator iterator(boolean descending) + { + // We should never get here because we override iterableWithPostAggregations + throw new UnsupportedOperationException(); + } + + @Override + public Iterable timeRangeIterable(boolean descending, long timeStart, long timeEnd) + { + return () -> { + IncrementalIndexRow from = null; + IncrementalIndexRow to = null; + if (timeStart > getMinTimeMillis()) { + from = new IncrementalIndexRow(timeStart, OakIncrementalIndexRow.NO_DIMS, dimensionDescsList, + IncrementalIndexRow.EMPTY_ROW_INDEX); + } + + if (timeEnd < getMaxTimeMillis()) { + to = new IncrementalIndexRow(timeEnd, OakIncrementalIndexRow.NO_DIMS, dimensionDescsList, + IncrementalIndexRow.EMPTY_ROW_INDEX); + } + + OakMap subMap = facts.subMap(from, true, to, false, descending); + return transformStreamIterator(subMap.zc().entryStreamSet().iterator()); + }; + } + + @Override + public Iterable keySet() + { + return () -> transformNonStreamIterator(facts.zc().entrySet().iterator()); + } + + @Override + public Iterable persistIterable() + { + return () -> transformStreamIterator(facts.zc().entryStreamSet().iterator()); + } + + @Override + public int putIfAbsent(IncrementalIndexRow key, int rowIndex) + { + throw new UnsupportedOperationException(); + } + + @Override + public void clear() + { + facts.close(); + } + + /** + * OakIncrementalIndex builder + */ + public static class Builder extends AppendableIndexBuilder + { + public static final long DEFAULT_OAK_MAX_MEMORY_CAPACITY = 32L * (1L << 30); // 32 GB + public static final int DEFAULT_OAK_BLOCK_SIZE = 8 * (1 << 20); // 8 MB + public static final int DEFAULT_OAK_CHUNK_MAX_ITEMS = 256; + + public long oakMaxMemoryCapacity = DEFAULT_OAK_MAX_MEMORY_CAPACITY; + public int oakBlockSize = DEFAULT_OAK_BLOCK_SIZE; + public int oakChunkMaxItems = DEFAULT_OAK_CHUNK_MAX_ITEMS; + + public Builder setOakMaxMemoryCapacity(long oakMaxMemoryCapacity) + { + this.oakMaxMemoryCapacity = oakMaxMemoryCapacity; + return this; + } + + public Builder setOakBlockSize(int oakBlockSize) + { + this.oakBlockSize = oakBlockSize; + return this; + } + + public Builder setOakChunkMaxItems(int oakChunkMaxItems) + { + this.oakChunkMaxItems = oakChunkMaxItems; + return this; + } + + @Override + protected OakIncrementalIndex buildInner() + { + return new OakIncrementalIndex( + Objects.requireNonNull(incrementalIndexSchema, "incrementalIndexSchema is null"), + deserializeComplexMetrics, + concurrentEventAdd, + maxRowCount, + maxBytesInMemory, + oakMaxMemoryCapacity, + oakBlockSize, + oakChunkMaxItems + ); + } + } +} diff --git a/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndexModule.java b/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndexModule.java new file mode 100644 index 000000000000..299d6b3b1a9d --- /dev/null +++ b/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndexModule.java @@ -0,0 +1,53 @@ +/* + * 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.druid.segment.incremental.oak; + +import com.fasterxml.jackson.databind.Module; +import com.fasterxml.jackson.databind.jsontype.NamedType; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.google.inject.Binder; +import org.apache.druid.initialization.DruidModule; +import org.apache.druid.java.util.common.logger.Logger; + +import java.util.Collections; +import java.util.List; + +public class OakIncrementalIndexModule implements DruidModule +{ + private static final Logger log = new Logger(OakIncrementalIndexModule.class); + + public static final Module JACKSON_MODULE = new SimpleModule("OakIncrementalIndexModule") + .registerSubtypes( + new NamedType(OakIncrementalIndexSpec.class, OakIncrementalIndexSpec.TYPE), + new NamedType(OffheapIncrementalIndexSpec.class, OffheapIncrementalIndexSpec.TYPE) + ); + + @Override + public void configure(Binder binder) + { + log.info("Test"); + } + + @Override + public List getJacksonModules() + { + return Collections.singletonList(JACKSON_MODULE); + } +} diff --git a/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndexRow.java b/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndexRow.java new file mode 100644 index 000000000000..2689b00a0d86 --- /dev/null +++ b/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndexRow.java @@ -0,0 +1,168 @@ +/* + * 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.druid.segment.incremental.oak; + +import com.yahoo.oak.OakUnsafeDirectBuffer; +import com.yahoo.oak.OakUnscopedBuffer; +import org.apache.druid.segment.data.IndexedInts; +import org.apache.druid.segment.incremental.IncrementalIndex.DimensionDesc; +import org.apache.druid.segment.incremental.IncrementalIndexRow; + +import javax.annotation.Nullable; +import java.nio.ByteBuffer; +import java.util.List; + +public class OakIncrementalIndexRow extends IncrementalIndexRow +{ + public static final Object[] NO_DIMS = new Object[]{}; + + private final OakUnsafeDirectBuffer oakDimensions; + private long dimensions; + private final OakUnsafeDirectBuffer oakAggregations; + @Nullable + private ByteBuffer aggregationsBuffer; + private int aggregationsOffset; + private int dimsLength; + @Nullable + private OakKey.StringDim stringDim; + + public OakIncrementalIndexRow(OakUnscopedBuffer dimensions, + List dimensionDescsList, + OakUnscopedBuffer aggregations) + { + super(0, NO_DIMS, dimensionDescsList, IncrementalIndexRow.EMPTY_ROW_INDEX); + this.oakDimensions = (OakUnsafeDirectBuffer) dimensions; + this.oakAggregations = (OakUnsafeDirectBuffer) aggregations; + this.dimensions = oakDimensions.getAddress(); + this.aggregationsBuffer = null; + this.aggregationsOffset = 0; + this.dimsLength = -1; // lazy initialization + this.stringDim = null; + } + + /** + * The key/value of the row is received as OakUnscopedBuffer. + * When iterating through the index items, we use Oak's stream iterators. + * In such iterators, the key/value OakUnscopedBuffer objects are reused for each next() call to avoid + * redundant object instantiation. + * So whenever we iterate through the index, we don't have to recreate the OakIncrementalIndexRow + * object. We can just reset it. + */ + public void reset() + { + dimsLength = -1; + dimensions = oakDimensions.getAddress(); + aggregationsBuffer = null; + aggregationsOffset = 0; + if (stringDim != null) { + stringDim.reset(dimensions); + } + } + + private void updateAggregationsBuffer() + { + if (aggregationsBuffer == null) { + aggregationsBuffer = oakAggregations.getByteBuffer(); + aggregationsOffset = oakAggregations.getOffset(); + } + } + + public ByteBuffer getAggregationsBuffer() + { + updateAggregationsBuffer(); + return aggregationsBuffer; + } + + public int getAggregationsOffset() + { + updateAggregationsBuffer(); + return aggregationsOffset; + } + + @Override + public long getTimestamp() + { + return OakKey.getTimestamp(dimensions); + } + + @Override + @Nullable + public Object getDim(int dimIndex) + { + if (isDimOutOfBounds(dimIndex)) { + return null; + } + return OakKey.getDim(dimensions, dimIndex); + } + + @Override + public int getDimsLength() + { + // Read length only once + if (dimsLength < 0) { + dimsLength = OakKey.getDimsLength(dimensions); + } + return dimsLength; + } + + /** + * Allows faster null validation because it does not need to deserialize the key. + */ + @Override + public boolean isDimNull(int dimIndex) + { + return isDimOutOfBounds(dimIndex) || OakKey.isDimNull(dimensions, dimIndex); + } + + /** + * Allows faster access to a string dimension because it uses lazy evaluation (no need for deserialization). + * The caller must validate that this dimension is not null. + */ + @Override + public IndexedInts getStringDim(final int dimIndex) + { + if (stringDim == null) { + stringDim = new OakKey.StringDim(dimensions); + } + + if (stringDim.getDimIndex() != dimIndex) { + stringDim.setDimIndex(dimIndex); + } + + return stringDim; + } + + @Override + public int getRowIndex() + { + return OakKey.getRowIndex(dimensions); + } + + @Override + public void setRowIndex(int rowIndex) + { + throw new UnsupportedOperationException(); + } + + public boolean isDimOutOfBounds(int dimIndex) + { + return dimIndex < 0 || dimIndex >= getDimsLength(); + } +} diff --git a/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndexSpec.java b/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndexSpec.java new file mode 100644 index 000000000000..e49468668cf9 --- /dev/null +++ b/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndexSpec.java @@ -0,0 +1,92 @@ +/* + * 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.druid.segment.incremental.oak; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.apache.druid.segment.incremental.AppendableIndexSpec; +import org.apache.druid.utils.JvmUtils; + +import javax.annotation.Nullable; + +/** + * Since the off-heap incremental index is not yet supported in production ingestion, we define its spec here only + * for testing purposes. + */ +public class OakIncrementalIndexSpec implements AppendableIndexSpec +{ + public static final String TYPE = "oak"; + + final long oakMaxMemoryCapacity; + final int oakBlockSize; + final int oakChunkMaxItems; + + @JsonCreator + public OakIncrementalIndexSpec( + final @JsonProperty("oakMaxMemoryCapacity") @Nullable Long oakMaxMemoryCapacity, + final @JsonProperty("oakBlockSize") @Nullable Integer oakBlockSize, + final @JsonProperty("oakChunkMaxItems") @Nullable Integer oakChunkMaxItems + ) + { + this.oakMaxMemoryCapacity = oakMaxMemoryCapacity != null && oakMaxMemoryCapacity > 0 ? oakMaxMemoryCapacity : + OakIncrementalIndex.Builder.DEFAULT_OAK_MAX_MEMORY_CAPACITY; + this.oakBlockSize = oakBlockSize != null && oakBlockSize > 0 ? oakBlockSize : + OakIncrementalIndex.Builder.DEFAULT_OAK_BLOCK_SIZE; + this.oakChunkMaxItems = oakChunkMaxItems != null && oakChunkMaxItems > 0 ? oakChunkMaxItems : + OakIncrementalIndex.Builder.DEFAULT_OAK_CHUNK_MAX_ITEMS; + } + + @JsonProperty + public long getOakMaxMemoryCapacity() + { + return oakMaxMemoryCapacity; + } + + @JsonProperty + public int getOakBlockSize() + { + return oakBlockSize; + } + + @JsonProperty + public int getOakChunkMaxItems() + { + return oakChunkMaxItems; + } + + @Override + public OakIncrementalIndex.Builder builder() + { + return new OakIncrementalIndex.Builder() + .setOakMaxMemoryCapacity(oakMaxMemoryCapacity) + .setOakBlockSize(oakBlockSize) + .setOakChunkMaxItems(oakChunkMaxItems); + } + + @Override + public long getDefaultMaxBytesInMemory() + { + // In the realtime node, the entire JVM's direct memory is utilized for ingestion and persist operations. + // But maxBytesInMemory only refers to the active index size and not to the index being flushed to disk and the + // persist buffer. + // To account for that, we set default to 1/2 of the max jvm's direct memory. + return JvmUtils.getRuntimeInfo().getDirectMemorySizeBytes() / 2; + } +} diff --git a/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakInputRowContext.java b/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakInputRowContext.java new file mode 100644 index 000000000000..b62b8b732438 --- /dev/null +++ b/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakInputRowContext.java @@ -0,0 +1,59 @@ +/* + * 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.druid.segment.incremental.oak; + +import org.apache.druid.data.input.InputRow; +import org.apache.druid.data.input.Row; +import org.apache.druid.java.util.common.parsers.ParseException; + +import java.util.ArrayList; +import java.util.List; + +/** + * This class acts as a wrapper to bundle InputRow together with its row container. + * It also collects the parse exceptions along the way. + */ +class OakInputRowContext +{ + final ThreadLocal rowContainer; + final InputRow row; + final List parseExceptionMessages = new ArrayList<>(); + + OakInputRowContext(ThreadLocal rowContainer, Row row) + { + this.rowContainer = rowContainer; + this.row = (InputRow) row; + } + + void setRow() + { + rowContainer.set(row); + } + + void clearRow() + { + rowContainer.set(null); + } + + void addException(ParseException e) + { + parseExceptionMessages.add(e.getMessage()); + } +} diff --git a/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakKey.java b/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakKey.java new file mode 100644 index 000000000000..379029b6c1c7 --- /dev/null +++ b/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakKey.java @@ -0,0 +1,558 @@ +/* + * 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.druid.segment.incremental.oak; + +import com.google.common.primitives.Ints; +import com.google.common.primitives.Longs; +import com.yahoo.oak.OakBuffer; +import com.yahoo.oak.OakComparator; +import com.yahoo.oak.OakScopedReadBuffer; +import com.yahoo.oak.OakScopedWriteBuffer; +import com.yahoo.oak.OakSerializer; +import com.yahoo.oak.OakUnsafeDirectBuffer; +import org.apache.druid.query.monomorphicprocessing.RuntimeShapeInspector; +import org.apache.druid.segment.DimensionIndexer; +import org.apache.druid.segment.column.ColumnCapabilities; +import org.apache.druid.segment.column.ValueType; +import org.apache.druid.segment.data.IndexedInts; +import org.apache.druid.segment.incremental.IncrementalIndex; +import org.apache.druid.segment.incremental.IncrementalIndexRow; +import sun.misc.Unsafe; + +import javax.annotation.Nullable; +import java.lang.reflect.Constructor; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.IntStream; + +/** + * Responsible for the serialization, deserialization, and comparison of keys. + * + * It stores the key in an off-heap buffer in the following structure: + * - Global metadata (buffer offset + _): + * +0: timestamp (long) + * +8: dims length (int) + * +12: row index (int) + * (total of 16 bytes) + * - Followed by the dimensions one after the other (buffer offset + 16 + dimIdx*12 + _): + * +0: value type (int) + * +4: data (int/long/float/double) + * (12 bytes per dimension) + * Note: For string dimension (int array), the data includes: + * +4: the offset of the int array in the buffer (int) + * +8: the length of the int array (int) + * - The string dimensions arrays are stored after all the dims' data (buffer offset + 16 + dimsLength*12 + _). + * + * Note: the specified offsets are true in most cases, but other JVM implementations may have + * different offsets, depending on the size of the primitives in bytes. + * The offset calculation below is robust to JVM implementation changes. + */ +public final class OakKey +{ + // The off-heap buffer offsets (buffer offset + _) + static final int TIME_STAMP_OFFSET = 0; + static final int DIMS_LENGTH_OFFSET = TIME_STAMP_OFFSET + Long.BYTES; + static final int ROW_INDEX_OFFSET = DIMS_LENGTH_OFFSET + Integer.BYTES; + static final int DIMS_OFFSET = ROW_INDEX_OFFSET + Integer.BYTES; + static final int DIM_VALUE_TYPE_OFFSET = 0; + static final int DIM_DATA_OFFSET = DIM_VALUE_TYPE_OFFSET + Integer.BYTES; + static final int STRING_DIM_ARRAY_POS_OFFSET = DIM_DATA_OFFSET; + static final int STRING_DIM_ARRAY_LENGTH_OFFSET = STRING_DIM_ARRAY_POS_OFFSET + Integer.BYTES; + static final int SIZE_PER_DIM = DIM_DATA_OFFSET + Collections.max(Arrays.asList( + // Common data types + Integer.BYTES, + Long.BYTES, + Float.BYTES, + Double.BYTES, + // String dimension data type + Integer.BYTES * 2 + )); + + // Dimension types + static final ValueType[] VALUE_ORDINAL_TYPES = ValueType.values(); + // Marks a null dimension + static final int NULL_DIM = -1; + + // Used for direct access to the key's buffer + static final Unsafe UNSAFE; + static final long INT_ARRAY_OFFSET; + + // static constructor - access and create a new instance of Unsafe + static { + try { + Constructor unsafeConstructor = Unsafe.class.getDeclaredConstructor(); + unsafeConstructor.setAccessible(true); + UNSAFE = unsafeConstructor.newInstance(); + INT_ARRAY_OFFSET = UNSAFE.arrayBaseOffset(int[].class); + } + catch (Exception ex) { + throw new RuntimeException(ex); + } + } + + private OakKey() + { + } + + static long getKeyAddress(OakBuffer buffer) + { + return ((OakUnsafeDirectBuffer) buffer).getAddress(); + } + + static long getTimestamp(long address) + { + return UNSAFE.getLong(address + TIME_STAMP_OFFSET); + } + + static int getRowIndex(long address) + { + return UNSAFE.getInt(address + ROW_INDEX_OFFSET); + } + + static int getDimsLength(long address) + { + return UNSAFE.getInt(address + DIMS_LENGTH_OFFSET); + } + + static int getDimOffsetInBuffer(int dimIndex) + { + return DIMS_OFFSET + (dimIndex * SIZE_PER_DIM); + } + + static boolean isValueTypeNull(int dimValueTypeID) + { + return dimValueTypeID < 0 || dimValueTypeID >= VALUE_ORDINAL_TYPES.length; + } + + static boolean isDimNull(long address, int dimIndex) + { + long dimAddress = address + getDimOffsetInBuffer(dimIndex); + return isValueTypeNull(UNSAFE.getInt(dimAddress + DIM_VALUE_TYPE_OFFSET)); + } + + @Nullable + static Object getDim(long address, int dimIndex) + { + long dimAddress = address + getDimOffsetInBuffer(dimIndex); + int dimValueTypeID = UNSAFE.getInt(dimAddress + DIM_VALUE_TYPE_OFFSET); + + if (isValueTypeNull(dimValueTypeID)) { + return null; + } + + switch (VALUE_ORDINAL_TYPES[dimValueTypeID]) { + case DOUBLE: + return UNSAFE.getDouble(dimAddress + DIM_DATA_OFFSET); + case FLOAT: + return UNSAFE.getFloat(dimAddress + DIM_DATA_OFFSET); + case LONG: + return UNSAFE.getLong(dimAddress + DIM_DATA_OFFSET); + case STRING: + int arrayPos = UNSAFE.getInt(dimAddress + STRING_DIM_ARRAY_POS_OFFSET); + int arraySize = UNSAFE.getInt(dimAddress + STRING_DIM_ARRAY_LENGTH_OFFSET); + int[] array = new int[arraySize]; + UNSAFE.copyMemory(null, address + arrayPos, array, INT_ARRAY_OFFSET, ((long) arraySize) * Integer.BYTES); + return array; + default: + return null; + } + } + + static Object[] getAllDims(long address) + { + int dimsLength = getDimsLength(address); + return IntStream.range(0, dimsLength).mapToObj(dimIndex -> getDim(address, dimIndex)).toArray(); + } + + /** + * StringDim purpose is to generate a lazy-evaluation version of a string dimension instead of the array of integers + * that is returned by getDim(int index). + */ + public static class StringDim implements IndexedInts + { + long dimensionsAddress; + int dimIndex; + boolean initialized; + int arraySize; + long arrayAddress; + + public StringDim(long dimensionsAddress) + { + this.dimensionsAddress = dimensionsAddress; + dimIndex = -1; + initialized = false; + } + + public void reset(long dimensions) + { + this.dimensionsAddress = dimensions; + dimIndex = -1; + initialized = false; + } + + public void setDimIndex(final int dimIndex) + { + this.dimIndex = dimIndex; + initialized = false; + } + + private void init() + { + if (initialized) { + return; + } + + long dimAddress = this.dimensionsAddress + getDimOffsetInBuffer(dimIndex); + arrayAddress = dimensionsAddress + UNSAFE.getInt(dimAddress + STRING_DIM_ARRAY_POS_OFFSET); + arraySize = UNSAFE.getInt(dimAddress + STRING_DIM_ARRAY_LENGTH_OFFSET); + initialized = true; + } + + public int getDimIndex() + { + init(); + return dimIndex; + } + + @Override + public int size() + { + init(); + return arraySize; + } + + @Override + public int get(int index) + { + init(); + return UNSAFE.getInt(arrayAddress + ((long) index) * Integer.BYTES); + } + + @Override + public void inspectRuntimeShape(RuntimeShapeInspector inspector) + { + // nothing to inspect + } + } + + public static class Serializer implements OakSerializer + { + public static final int ASSIGN_ROW_INDEX_IF_ABSENT = Integer.MIN_VALUE; + + private final List dimensionDescsList; + private final AtomicInteger rowIndexGenerator; + + public Serializer(List dimensionDescsList, AtomicInteger rowIndexGenerator) + { + this.dimensionDescsList = dimensionDescsList; + this.rowIndexGenerator = rowIndexGenerator; + } + + @Nullable + private ValueType getDimValueType(int dimIndex) + { + IncrementalIndex.DimensionDesc dimensionDesc = dimensionDescsList.get(dimIndex); + if (dimensionDesc == null) { + return null; + } + ColumnCapabilities capabilities = dimensionDesc.getCapabilities(); + return capabilities.getType(); + } + + @Override + public void serialize(IncrementalIndexRow incrementalIndexRow, OakScopedWriteBuffer buffer) + { + long address = getKeyAddress(buffer); + + long timestamp = incrementalIndexRow.getTimestamp(); + int dimsLength = incrementalIndexRow.getDimsLength(); + int rowIndex = incrementalIndexRow.getRowIndex(); + if (rowIndex == ASSIGN_ROW_INDEX_IF_ABSENT) { + rowIndex = rowIndexGenerator.getAndIncrement(); + incrementalIndexRow.setRowIndex(rowIndex); + } + UNSAFE.putLong(address + TIME_STAMP_OFFSET, timestamp); + UNSAFE.putInt(address + DIMS_LENGTH_OFFSET, dimsLength); + UNSAFE.putInt(address + ROW_INDEX_OFFSET, rowIndex); + + long dimsAddress = address + DIMS_OFFSET; + // the index for writing the int arrays of the string-dim (after all the dims' data) + int stringDimArraysPos = getDimOffsetInBuffer(dimsLength); + + for (int dimIndex = 0; dimIndex < dimsLength; dimIndex++) { + Object dim = incrementalIndexRow.getDim(dimIndex); + ValueType dimValueType = dim != null ? getDimValueType(dimIndex) : null; + boolean isDimHaveValue = dimValueType != null; + + int dimValueTypeID = isDimHaveValue ? dimValueType.ordinal() : NULL_DIM; + UNSAFE.putInt(dimsAddress + DIM_VALUE_TYPE_OFFSET, dimValueTypeID); + + if (isDimHaveValue) { + switch (dimValueType) { + case FLOAT: + UNSAFE.putFloat(dimsAddress + DIM_DATA_OFFSET, (Float) dim); + break; + case DOUBLE: + UNSAFE.putDouble(dimsAddress + DIM_DATA_OFFSET, (Double) dim); + break; + case LONG: + UNSAFE.putLong(dimsAddress + DIM_DATA_OFFSET, (Long) dim); + break; + case STRING: + int[] arr = (int[]) dim; + int length = arr.length; + UNSAFE.putInt(dimsAddress + STRING_DIM_ARRAY_POS_OFFSET, stringDimArraysPos); + UNSAFE.putInt(dimsAddress + STRING_DIM_ARRAY_LENGTH_OFFSET, length); + + int lengthBytes = length * Integer.BYTES; + UNSAFE.copyMemory(arr, INT_ARRAY_OFFSET, null, address + stringDimArraysPos, lengthBytes); + stringDimArraysPos += lengthBytes; + break; + } + } + + dimsAddress += SIZE_PER_DIM; + } + } + + @Override + public IncrementalIndexRow deserialize(OakScopedReadBuffer buffer) + { + long address = getKeyAddress(buffer); + return new IncrementalIndexRow( + getTimestamp(address), + getAllDims(address), + dimensionDescsList, + getRowIndex(address) + ); + } + + @Override + public int calculateSize(IncrementalIndexRow incrementalIndexRow) + { + int dimsLength = incrementalIndexRow.getDimsLength(); + int sizeInBytes = getDimOffsetInBuffer(dimsLength); + + // When the dimensionDesc's capabilities are of type ValueType.STRING, + // the object in timeAndDims.dims is of type int[]. + // In this case, we need to know the array size before allocating the ByteBuffer. + for (int i = 0; i < dimsLength; i++) { + if (getDimValueType(i) != ValueType.STRING) { + continue; + } + + Object dim = incrementalIndexRow.getDim(i); + if (dim != null) { + sizeInBytes += Integer.BYTES * ((int[]) dim).length; + } + } + + return sizeInBytes; + } + } + + public static class Comparator implements OakComparator + { + private final List dimensionDescs; + private final boolean rollup; + + public Comparator(List dimensionDescs, boolean rollup) + { + this.dimensionDescs = dimensionDescs; + this.rollup = rollup; + } + + @Override + public int compareKeys(IncrementalIndexRow lhs, IncrementalIndexRow rhs) + { + int retVal = Longs.compare(lhs.getTimestamp(), rhs.getTimestamp()); + if (retVal != 0) { + return retVal; + } + + int lhsDimsLength = lhs.getDimsLength(); + int rhsDimsLength = rhs.getDimsLength(); + int numComparisons = Math.min(lhsDimsLength, rhsDimsLength); + + int index = 0; + while (retVal == 0 && index < numComparisons) { + final Object lhsIdxs = lhs.getDim(index); + final Object rhsIdxs = rhs.getDim(index); + + if (lhsIdxs == null) { + if (rhsIdxs == null) { + ++index; + continue; + } + return -1; + } + + if (rhsIdxs == null) { + return 1; + } + + final DimensionIndexer indexer = dimensionDescs.get(index).getIndexer(); + retVal = indexer.compareUnsortedEncodedKeyComponents(lhsIdxs, rhsIdxs); + ++index; + } + + if (retVal == 0) { + int lengthDiff = Ints.compare(lhsDimsLength, rhsDimsLength); + if (lengthDiff != 0) { + IncrementalIndexRow largerRow = lengthDiff > 0 ? lhs : rhs; + retVal = allNull(largerRow, numComparisons) ? 0 : lengthDiff; + } + } + + return retVal == 0 ? rowIndexCompare(lhs.getRowIndex(), rhs.getRowIndex()) : retVal; + } + + @Override + public int compareSerializedKeys(OakScopedReadBuffer lhsBuffer, OakScopedReadBuffer rhsBuffer) + { + long lhs = getKeyAddress(lhsBuffer); + long rhs = getKeyAddress(rhsBuffer); + + int retVal = Longs.compare(getTimestamp(lhs), getTimestamp(rhs)); + if (retVal != 0) { + return retVal; + } + + int lhsDimsLength = getDimsLength(lhs); + int rhsDimsLength = getDimsLength(rhs); + int numComparisons = Math.min(lhsDimsLength, rhsDimsLength); + + int index = 0; + while (retVal == 0 && index < numComparisons) { + final Object lhsIdxs = getDim(lhs, index); + final Object rhsIdxs = getDim(rhs, index); + + if (lhsIdxs == null) { + if (rhsIdxs == null) { + ++index; + continue; + } + return -1; + } + + if (rhsIdxs == null) { + return 1; + } + + final DimensionIndexer indexer = dimensionDescs.get(index).getIndexer(); + retVal = indexer.compareUnsortedEncodedKeyComponents(lhsIdxs, rhsIdxs); + ++index; + } + + if (retVal == 0) { + int lengthDiff = Ints.compare(lhsDimsLength, rhsDimsLength); + if (lengthDiff != 0) { + long largerRowAddress = lengthDiff > 0 ? lhs : rhs; + retVal = allNull(largerRowAddress, numComparisons) ? 0 : lengthDiff; + } + } + + return retVal == 0 ? rowIndexCompare(getRowIndex(lhs), getRowIndex(rhs)) : retVal; + } + + @Override + public int compareKeyAndSerializedKey(IncrementalIndexRow lhs, OakScopedReadBuffer rhsBuffer) + { + long rhs = getKeyAddress(rhsBuffer); + + int retVal = Longs.compare(lhs.getTimestamp(), getTimestamp(rhs)); + if (retVal != 0) { + return retVal; + } + + int lhsDimsLength = lhs.getDimsLength(); + int rhsDimsLength = getDimsLength(rhs); + int numComparisons = Math.min(lhsDimsLength, rhsDimsLength); + + int index = 0; + while (retVal == 0 && index < numComparisons) { + final Object lhsIdxs = lhs.getDim(index); + final Object rhsIdxs = getDim(rhs, index); + + if (lhsIdxs == null) { + if (rhsIdxs == null) { + ++index; + continue; + } + return -1; + } + + if (rhsIdxs == null) { + return 1; + } + + final DimensionIndexer indexer = dimensionDescs.get(index).getIndexer(); + retVal = indexer.compareUnsortedEncodedKeyComponents(lhsIdxs, rhsIdxs); + ++index; + } + + if (retVal == 0) { + int lengthDiff = Ints.compare(lhsDimsLength, rhsDimsLength); + if (lengthDiff != 0) { + boolean isAllNull = lengthDiff > 0 ? allNull(lhs, numComparisons) : allNull(rhs, numComparisons); + retVal = isAllNull ? 0 : lengthDiff; + } + } + + return retVal == 0 ? rowIndexCompare(lhs.getRowIndex(), getRowIndex(rhs)) : retVal; + } + + private int rowIndexCompare(int lsIndex, int rsIndex) + { + if (!rollup || lsIndex == IncrementalIndexRow.EMPTY_ROW_INDEX || rsIndex == IncrementalIndexRow.EMPTY_ROW_INDEX) { + // If we are not in a rollup mode (plain mode), then keys should never be equal. + // In addition, if one of the keys has no index row (EMPTY_ROW_INDEX) it means it is a lower or upper bound key, + // so we must compared it. + return Integer.compare(lsIndex, rsIndex); + } else { + return 0; + } + } + + private static boolean allNull(IncrementalIndexRow row, int startPosition) + { + int dimLength = row.getDimsLength(); + for (int i = startPosition; i < dimLength; i++) { + if (!row.isDimNull(i)) { + return false; + } + } + return true; + } + + private static boolean allNull(long rowAddress, int startPosition) + { + int dimLength = getDimsLength(rowAddress); + for (int i = startPosition; i < dimLength; i++) { + if (!isDimNull(rowAddress, i)) { + return false; + } + } + return true; + } + } +} diff --git a/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OffheapIncrementalIndexSpec.java b/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OffheapIncrementalIndexSpec.java new file mode 100644 index 000000000000..d549c5a0088d --- /dev/null +++ b/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OffheapIncrementalIndexSpec.java @@ -0,0 +1,97 @@ +/* + * 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.druid.segment.incremental.oak; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.common.base.Supplier; +import org.apache.druid.collections.StupidPool; +import org.apache.druid.segment.incremental.AppendableIndexBuilder; +import org.apache.druid.segment.incremental.AppendableIndexSpec; +import org.apache.druid.segment.incremental.OffheapIncrementalIndex; +import org.apache.druid.utils.JvmUtils; + +import javax.annotation.Nullable; +import java.nio.ByteBuffer; + +/** + * This extension also bundles the option to use the off-heap index for ingestion, that is not supported by default. + */ +public class OffheapIncrementalIndexSpec implements AppendableIndexSpec, Supplier +{ + public static final String TYPE = "offheap"; + static final int DEFAULT_BUFFER_SIZE = 1 << 23; + static final int DEFAULT_CACHE_SIZE = 1 << 30; + + final int bufferSize; + final int cacheSize; + + final StupidPool bufferPool; + + @JsonCreator + public OffheapIncrementalIndexSpec( + final @JsonProperty("bufferSize") @Nullable Integer bufferSize, + final @JsonProperty("cacheSize") @Nullable Integer cacheSize + ) + { + this.bufferSize = bufferSize != null && bufferSize > 0 ? bufferSize : DEFAULT_BUFFER_SIZE; + this.cacheSize = cacheSize != null && cacheSize > this.bufferSize ? cacheSize : DEFAULT_CACHE_SIZE; + this.bufferPool = new StupidPool<>( + "Off-heap incremental-index buffer pool", + this, + 0, + this.cacheSize / this.bufferSize + ); + } + + @JsonProperty + public int getBufferSize() + { + return bufferSize; + } + + @JsonProperty + public int getCacheSize() + { + return cacheSize; + } + + @Override + public ByteBuffer get() + { + return ByteBuffer.allocateDirect(bufferSize); + } + + @Override + public AppendableIndexBuilder builder() + { + return new OffheapIncrementalIndex.Builder().setBufferPool(bufferPool); + } + + @Override + public long getDefaultMaxBytesInMemory() + { + // In the realtime node, the entire JVM's direct memory is utilized for ingestion and persist operations. + // But maxBytesInMemory only refers to the active index size and not to the index being flushed to disk and the + // persist buffer. + // To account for that, we set default to 1/2 of the max jvm's direct memory. + return JvmUtils.getRuntimeInfo().getDirectMemorySizeBytes() / 2; + } +} diff --git a/extensions-contrib/oak-incremental-index/src/main/resources/META-INF/services/org.apache.druid.initialization.DruidModule b/extensions-contrib/oak-incremental-index/src/main/resources/META-INF/services/org.apache.druid.initialization.DruidModule new file mode 100644 index 000000000000..fe90ebe7c969 --- /dev/null +++ b/extensions-contrib/oak-incremental-index/src/main/resources/META-INF/services/org.apache.druid.initialization.DruidModule @@ -0,0 +1,16 @@ +# 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. + +org.apache.druid.segment.incremental.oak.OakIncrementalIndexModule diff --git a/extensions-contrib/oak-incremental-index/src/test/java/org/apache/druid/segment/incremental/oak/OakBenchmarks.java b/extensions-contrib/oak-incremental-index/src/test/java/org/apache/druid/segment/incremental/oak/OakBenchmarks.java new file mode 100644 index 000000000000..fa3fb79c1c75 --- /dev/null +++ b/extensions-contrib/oak-incremental-index/src/test/java/org/apache/druid/segment/incremental/oak/OakBenchmarks.java @@ -0,0 +1,121 @@ +/* + * 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.druid.segment.incremental.oak; + +import org.apache.druid.segment.incremental.IncrementalIndexCreator; +import org.openjdk.jmh.runner.Runner; +import org.openjdk.jmh.runner.RunnerException; +import org.openjdk.jmh.runner.options.Options; +import org.openjdk.jmh.runner.options.OptionsBuilder; +import org.openjdk.jmh.runner.options.TimeValue; + +/** + * This class wraps Oak relevant benchmarks and adds additional static initialization + * to add Oak to the available incremental indexes. + */ +public class OakBenchmarks +{ + public static void main(String[] args) throws RunnerException + { + final String thisClass = OakBenchmarks.class.getName(); + + Options opt = new OptionsBuilder() + .include(thisClass + ".IncrementalIndexReadBenchmark.read$") + .warmupIterations(0) + .measurementIterations(1) + .measurementTime(TimeValue.NONE) + .forks(1) + .threads(1) + .param("indexType", "oak") + .param("rollup", "true") + .param("rowsPerSegment", "1000") + .build(); + + new Runner(opt).run(); + } + + public static void initModule() + { + IncrementalIndexCreator.JSON_MAPPER.registerModule(OakIncrementalIndexModule.JACKSON_MODULE); + } + + public static class IncrementalIndexReadBenchmark extends org.apache.druid.benchmark.indexing.IncrementalIndexReadBenchmark + { + static { + OakBenchmarks.initModule(); + } + } + + public static class IndexIngestionBenchmark extends org.apache.druid.benchmark.indexing.IndexIngestionBenchmark + { + static { + OakBenchmarks.initModule(); + } + } + + public static class IndexPersistBenchmark extends org.apache.druid.benchmark.indexing.IndexPersistBenchmark + { + static { + OakBenchmarks.initModule(); + } + } + + public static class GroupByBenchmark extends org.apache.druid.benchmark.query.GroupByBenchmark + { + static { + OakBenchmarks.initModule(); + } + } + + public static class ScanBenchmark extends org.apache.druid.benchmark.query.ScanBenchmark + { + static { + OakBenchmarks.initModule(); + } + } + + public static class SearchBenchmark extends org.apache.druid.benchmark.query.SearchBenchmark + { + static { + OakBenchmarks.initModule(); + } + } + + public static class TimeseriesBenchmark extends org.apache.druid.benchmark.query.TimeseriesBenchmark + { + static { + OakBenchmarks.initModule(); + } + } + + public static class TopNBenchmark extends org.apache.druid.benchmark.query.TopNBenchmark + { + static { + OakBenchmarks.initModule(); + } + } + + public static class FilteredAggregatorBenchmark extends org.apache.druid.benchmark.FilteredAggregatorBenchmark + { + static { + OakBenchmarks.initModule(); + } + } +} diff --git a/extensions-contrib/oak-incremental-index/src/test/java/org/apache/druid/segment/incremental/oak/OakDummyInitTest.java b/extensions-contrib/oak-incremental-index/src/test/java/org/apache/druid/segment/incremental/oak/OakDummyInitTest.java new file mode 100644 index 000000000000..3d5a738e188d --- /dev/null +++ b/extensions-contrib/oak-incremental-index/src/test/java/org/apache/druid/segment/incremental/oak/OakDummyInitTest.java @@ -0,0 +1,44 @@ +/* + * 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.druid.segment.incremental.oak; + +import com.google.common.collect.ImmutableList; +import org.apache.druid.segment.incremental.IncrementalIndexCreator; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.Collection; + +/** + * This test class is a hack to initialize IncrementalIndexCreator to include OakIncrementalIndexSpec. + * It is needed because all @Parameterized.Parameters methods are called before any other tests' code segments. + * Even before @BeforeClass annotated methods. + */ +@RunWith(Parameterized.class) +public class OakDummyInitTest +{ + @Parameterized.Parameters + public static Collection constructorFeeder() + { + // Add Oak to the available incremental indexes + IncrementalIndexCreator.addIndexSpec(OakIncrementalIndexSpec.class, "oak"); + return ImmutableList.of(); + } +} diff --git a/extensions-contrib/oak-incremental-index/src/test/java/org/apache/druid/segment/incremental/oak/OakTestSuite.java b/extensions-contrib/oak-incremental-index/src/test/java/org/apache/druid/segment/incremental/oak/OakTestSuite.java new file mode 100644 index 000000000000..1949604e5f32 --- /dev/null +++ b/extensions-contrib/oak-incremental-index/src/test/java/org/apache/druid/segment/incremental/oak/OakTestSuite.java @@ -0,0 +1,49 @@ +/* + * 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.druid.segment.incremental.oak; + +import org.apache.druid.segment.incremental.IncrementalIndexAdapterTest; +import org.apache.druid.segment.incremental.IncrementalIndexIngestionTest; +import org.apache.druid.segment.incremental.IncrementalIndexMultiValueSpecTest; +import org.apache.druid.segment.incremental.IncrementalIndexRowCompTest; +import org.apache.druid.segment.incremental.IncrementalIndexRowSizeTest; +import org.apache.druid.segment.incremental.IncrementalIndexStorageAdapterTest; +import org.apache.druid.segment.incremental.IncrementalIndexTest; +import org.junit.runner.RunWith; +import org.junit.runners.Suite; + +/** + * Runs all relavent tests of the incremental index on all available implementations (including Oak). + */ +@RunWith(Suite.class) +@Suite.SuiteClasses({ + OakDummyInitTest.class, + org.apache.druid.segment.data.IncrementalIndexTest.class, + IncrementalIndexTest.class, + IncrementalIndexStorageAdapterTest.class, + IncrementalIndexRowSizeTest.class, + IncrementalIndexRowCompTest.class, + IncrementalIndexMultiValueSpecTest.class, + IncrementalIndexIngestionTest.class, + IncrementalIndexAdapterTest.class, +}) +public class OakTestSuite +{ +} diff --git a/licenses.yaml b/licenses.yaml index 73b6f9624900..6e86d3e71c64 100644 --- a/licenses.yaml +++ b/licenses.yaml @@ -4853,6 +4853,16 @@ notices: --- +name: oak +license_category: binary +version: 0.2.3 +module: druid-processing +license_name: Apache License version 2.0 +libraries: + - com.yahoo.oak: oak + +--- + # Web console modules start name: "@babel/runtime" license_category: binary diff --git a/pom.xml b/pom.xml index 3401cb979587..bcd67421b1d4 100644 --- a/pom.xml +++ b/pom.xml @@ -198,6 +198,7 @@ extensions-contrib/influxdb-emitter extensions-contrib/gce-extensions extensions-contrib/aliyun-oss-extensions + extensions-contrib/oak-incremental-index distribution diff --git a/processing/src/main/java/org/apache/druid/segment/DoubleDimensionIndexer.java b/processing/src/main/java/org/apache/druid/segment/DoubleDimensionIndexer.java index beb58df6208b..049941382764 100644 --- a/processing/src/main/java/org/apache/druid/segment/DoubleDimensionIndexer.java +++ b/processing/src/main/java/org/apache/druid/segment/DoubleDimensionIndexer.java @@ -132,20 +132,19 @@ class IndexerDoubleColumnSelector implements DoubleColumnSelector @Override public boolean isNull() { - final Object[] dims = currEntry.get().getDims(); - return hasNulls && (dimIndex >= dims.length || dims[dimIndex] == null); + return hasNulls && currEntry.get().isDimNull(dimIndex); } @Override public double getDouble() { - final Object[] dims = currEntry.get().getDims(); + final Object dim = currEntry.get().getDim(dimIndex); - if (dimIndex >= dims.length || dims[dimIndex] == null) { + if (dim == null) { assert NullHandling.replaceWithDefault(); return 0.0; } - return (Double) dims[dimIndex]; + return (Double) dim; } @SuppressWarnings("deprecation") @@ -153,12 +152,12 @@ public double getDouble() @Override public Double getObject() { - final Object[] dims = currEntry.get().getDims(); + final Object dim = currEntry.get().getDim(dimIndex); - if (dimIndex >= dims.length || dims[dimIndex] == null) { + if (dim == null) { return NullHandling.defaultDoubleValue(); } - return (Double) dims[dimIndex]; + return (Double) dim; } @Override diff --git a/processing/src/main/java/org/apache/druid/segment/FloatDimensionIndexer.java b/processing/src/main/java/org/apache/druid/segment/FloatDimensionIndexer.java index 8d86315d7358..52b15b583c91 100644 --- a/processing/src/main/java/org/apache/druid/segment/FloatDimensionIndexer.java +++ b/processing/src/main/java/org/apache/druid/segment/FloatDimensionIndexer.java @@ -133,21 +133,20 @@ class IndexerFloatColumnSelector implements FloatColumnSelector @Override public boolean isNull() { - final Object[] dims = currEntry.get().getDims(); - return hasNulls && (dimIndex >= dims.length || dims[dimIndex] == null); + return hasNulls && currEntry.get().isDimNull(dimIndex); } @Override public float getFloat() { - final Object[] dims = currEntry.get().getDims(); + final Object dim = currEntry.get().getDim(dimIndex); - if (dimIndex >= dims.length || dims[dimIndex] == null) { + if (dim == null) { assert NullHandling.replaceWithDefault(); return 0.0f; } - return (Float) dims[dimIndex]; + return (Float) dim; } @SuppressWarnings("deprecation") @@ -155,13 +154,13 @@ public float getFloat() @Override public Float getObject() { - final Object[] dims = currEntry.get().getDims(); + final Object dim = currEntry.get().getDim(dimIndex); - if (dimIndex >= dims.length || dims[dimIndex] == null) { + if (dim == null) { return NullHandling.defaultFloatValue(); } - return (Float) dims[dimIndex]; + return (Float) dim; } @Override diff --git a/processing/src/main/java/org/apache/druid/segment/LongDimensionIndexer.java b/processing/src/main/java/org/apache/druid/segment/LongDimensionIndexer.java index d9534bf77a5b..a46a6d4aabed 100644 --- a/processing/src/main/java/org/apache/druid/segment/LongDimensionIndexer.java +++ b/processing/src/main/java/org/apache/druid/segment/LongDimensionIndexer.java @@ -134,21 +134,20 @@ class IndexerLongColumnSelector implements LongColumnSelector @Override public boolean isNull() { - final Object[] dims = currEntry.get().getDims(); - return hasNulls && (dimIndex >= dims.length || dims[dimIndex] == null); + return hasNulls && currEntry.get().isDimNull(dimIndex); } @Override public long getLong() { - final Object[] dims = currEntry.get().getDims(); + final Object dim = currEntry.get().getDim(dimIndex); - if (dimIndex >= dims.length || dims[dimIndex] == null) { + if (dim == null) { assert NullHandling.replaceWithDefault(); return 0; } - return (Long) dims[dimIndex]; + return (Long) dim; } @SuppressWarnings("deprecation") @@ -156,13 +155,13 @@ public long getLong() @Override public Long getObject() { - final Object[] dims = currEntry.get().getDims(); + final Object dim = currEntry.get().getDim(dimIndex); - if (dimIndex >= dims.length || dims[dimIndex] == null) { + if (dim == null) { return NullHandling.defaultLongValue(); } - return (Long) dims[dimIndex]; + return (Long) dim; } @Override diff --git a/processing/src/main/java/org/apache/druid/segment/StringDimensionIndexer.java b/processing/src/main/java/org/apache/druid/segment/StringDimensionIndexer.java index 808a8c301fef..b120e88216ed 100644 --- a/processing/src/main/java/org/apache/druid/segment/StringDimensionIndexer.java +++ b/processing/src/main/java/org/apache/druid/segment/StringDimensionIndexer.java @@ -539,53 +539,69 @@ class IndexerDimensionSelector implements DimensionSelector, IdLookup @MonotonicNonNull private int[] nullIdIntArray; - @Override - public IndexedInts getRow() + /** + * Tries to fetch the IndexedInts from the row using getStringDim() if possible. + * Otherwise, it will fetch the int array using getDim() and will convert it to IndexedInts. + * If the dim is null or with zero length, the value is considered null. + * It may be null or empty due to currEntry's rowIndex being smaller than the row's rowIndex in which this + * dim first appears. + * + * @return IndexedInts instance, or null if the dim is null. + */ + @Nullable + private IndexedInts getRowOrNull() { - final Object[] dims = currEntry.get().getDims(); + IncrementalIndexRow key = currEntry.get(); - int[] indices; - if (dimIndex < dims.length) { - indices = (int[]) dims[dimIndex]; - } else { - indices = null; + if (key.isDimNull(dimIndex)) { + return null; } - int[] row = null; - int rowSize = 0; + IndexedInts ret = key.getStringDim(dimIndex); + if (ret != null) { + // Use if the incremental index row supports lazy indexed int and it is not empty. + return ret.size() > 0 ? ret : null; + } + + int[] indices = (int[]) key.getDim(dimIndex); - // usually due to currEntry's rowIndex is smaller than the row's rowIndex in which this dim first appears if (indices == null || indices.length == 0) { - if (hasMultipleValues) { - row = IntArrays.EMPTY_ARRAY; - rowSize = 0; - } else { - final int nullId = getEncodedValue(null, false); - if (nullId >= 0 && nullId < maxId) { - // null was added to the dictionary before this selector was created; return its ID. - if (nullIdIntArray == null) { - nullIdIntArray = new int[]{nullId}; - } - row = nullIdIntArray; - rowSize = 1; - } else { - // null doesn't exist in the dictionary; return an empty array. - // Choose to use ArrayBasedIndexedInts later, instead of special "empty" IndexedInts, for monomorphism - row = IntArrays.EMPTY_ARRAY; - rowSize = 0; - } - } + return null; } - if (row == null && indices != null && indices.length > 0) { - row = indices; - rowSize = indices.length; + indexedInts.setValues(indices, indices.length); + return indexedInts; + } + + private IndexedInts getDefaultIndexedInts() + { + if (hasMultipleValues) { + indexedInts.setValues(IntArrays.EMPTY_ARRAY, 0); + } else { + final int nullId = getEncodedValue(null, false); + if (nullId >= 0 && nullId < maxId) { + // null was added to the dictionary before this selector was created; return its ID. + if (nullIdIntArray == null) { + nullIdIntArray = new int[]{nullId}; + } + indexedInts.setValues(nullIdIntArray, 1); + } else { + // null doesn't exist in the dictionary; return an empty array. + // Choose to use ArrayBasedIndexedInts later, instead of special "empty" IndexedInts, for monomorphism + indexedInts.setValues(IntArrays.EMPTY_ARRAY, 0); + } } - indexedInts.setValues(row, rowSize); return indexedInts; } + @Override + public IndexedInts getRow() + { + IndexedInts ret = getRowOrNull(); + return ret != null ? ret : getDefaultIndexedInts(); + } + @Override public ValueMatcher makeValueMatcher(final String value) { @@ -597,18 +613,14 @@ public ValueMatcher makeValueMatcher(final String value) @Override public boolean matches() { - Object[] dims = currEntry.get().getDims(); - if (dimIndex >= dims.length) { - return value == null; - } - - int[] dimsInt = (int[]) dims[dimIndex]; - if (dimsInt == null || dimsInt.length == 0) { + IndexedInts dimsInt = getRowOrNull(); + if (dimsInt == null) { return value == null; } - for (int id : dimsInt) { - if (id == valueId) { + int size = dimsInt.size(); + for (int i = 0; i < size; i++) { + if (dimsInt.get(i) == valueId) { return true; } } @@ -643,17 +655,14 @@ public ValueMatcher makeValueMatcher(final Predicate predicate) @Override public boolean matches() { - Object[] dims = currEntry.get().getDims(); - if (dimIndex >= dims.length) { - return matchNull; - } - - int[] dimsInt = (int[]) dims[dimIndex]; - if (dimsInt == null || dimsInt.length == 0) { + IndexedInts dimsInt = getRowOrNull(); + if (dimsInt == null) { return matchNull; } - for (int id : dimsInt) { + int size = dimsInt.size(); + for (int i = 0; i < size; i++) { + int id = dimsInt.get(i); if (checkedIds.get(id)) { if (matchingIds.get(id)) { return true; @@ -738,12 +747,12 @@ public Object getObject() return null; } - Object[] dims = key.getDims(); - if (dimIndex >= dims.length) { + Object dim = key.getDim(dimIndex); + if (dim == null) { return null; } - return convertUnsortedEncodedKeyComponentToActualList((int[]) dims[dimIndex]); + return convertUnsortedEncodedKeyComponentToActualList((int[]) dim); } @SuppressWarnings("deprecation") @@ -895,6 +904,27 @@ public void fillBitmapsFromUnsortedEncodedKeyComponent( } } + public void fillBitmapsFromUnsortedEncodedKeyComponent( + IndexedInts key, + int rowNum, + MutableBitmap[] bitmapIndexes, + BitmapFactory factory + ) + { + if (!hasBitmapIndexes) { + throw new UnsupportedOperationException("This column does not include bitmap indexes"); + } + + final int size = key.size(); + for (int i = 0; i < size; i++) { + int dimValIdx = key.get(i); + if (bitmapIndexes[dimValIdx] == null) { + bitmapIndexes[dimValIdx] = factory.makeEmptyMutableBitmap(); + } + bitmapIndexes[dimValIdx].add(rowNum); + } + } + private SortedDimensionDictionary sortedLookup() { return sortedLookup == null ? sortedLookup = dimLookup.sort() : sortedLookup; diff --git a/processing/src/main/java/org/apache/druid/segment/StringDimensionMergerV9.java b/processing/src/main/java/org/apache/druid/segment/StringDimensionMergerV9.java index abb4637dc7b8..fa171e4bc75e 100644 --- a/processing/src/main/java/org/apache/druid/segment/StringDimensionMergerV9.java +++ b/processing/src/main/java/org/apache/druid/segment/StringDimensionMergerV9.java @@ -365,7 +365,7 @@ public void processMergedRow(ColumnValueSelector selector) throws IOException if (encodedValueSerializer instanceof ColumnarMultiIntsSerializer) { ((ColumnarMultiIntsSerializer) encodedValueSerializer).addValues(row); } else { - int value = row.size() == 0 ? 0 : row.get(0); + int value = rowSize == 0 ? 0 : row.get(0); ((SingleValueColumnarIntsSerializer) encodedValueSerializer).addValue(value); } rowCount++; diff --git a/processing/src/main/java/org/apache/druid/segment/incremental/IncrementalIndex.java b/processing/src/main/java/org/apache/druid/segment/incremental/IncrementalIndex.java index 5667267839f5..f0bee21b54c8 100644 --- a/processing/src/main/java/org/apache/druid/segment/incremental/IncrementalIndex.java +++ b/processing/src/main/java/org/apache/druid/segment/incremental/IncrementalIndex.java @@ -225,7 +225,7 @@ public ColumnCapabilities getColumnCapabilities(String columnName) private final Map metricDescs; private final Map dimensionDescs; - private final List dimensionDescsList; + protected final List dimensionDescsList; // dimension capabilities are provided by the indexers private final Map timeAndMetricsColumnCapabilities; private final AtomicInteger numEntries = new AtomicInteger(); @@ -349,15 +349,15 @@ protected abstract AddToFactsResult addToFacts( protected abstract Object getAggVal(AggregatorType agg, int rowOffset, int aggPosition); - protected abstract float getMetricFloatValue(int rowOffset, int aggOffset); + protected abstract float getMetricFloatValue(IncrementalIndexRow incrementalIndexRow, int aggOffset); - protected abstract long getMetricLongValue(int rowOffset, int aggOffset); + protected abstract long getMetricLongValue(IncrementalIndexRow incrementalIndexRow, int aggOffset); - protected abstract Object getMetricObjectValue(int rowOffset, int aggOffset); + protected abstract Object getMetricObjectValue(IncrementalIndexRow incrementalIndexRow, int aggOffset); - protected abstract double getMetricDoubleValue(int rowOffset, int aggOffset); + protected abstract double getMetricDoubleValue(IncrementalIndexRow incrementalIndexRow, int aggOffset); - protected abstract boolean isNull(int rowOffset, int aggOffset); + protected abstract boolean isNull(IncrementalIndexRow incrementalIndexRow, int aggOffset); static class IncrementalIndexRowResult { @@ -381,7 +381,7 @@ List getParseExceptionMessages() } } - static class AddToFactsResult + public static class AddToFactsResult { private final int rowCount; private final long bytesInMemory; @@ -665,7 +665,7 @@ boolean getDeserializeComplexMetrics() return deserializeComplexMetrics; } - AtomicInteger getNumEntries() + protected AtomicInteger getNumEntries() { return numEntries; } @@ -898,11 +898,11 @@ public Iterable iterableWithPostAggregations( incrementalIndexRow -> { final int rowOffset = incrementalIndexRow.getRowIndex(); - Object[] theDims = incrementalIndexRow.getDims(); + int dimLength = incrementalIndexRow.getDimsLength(); Map theVals = Maps.newLinkedHashMap(); - for (int i = 0; i < theDims.length; ++i) { - Object dim = theDims[i]; + for (int i = 0; i < dimLength; ++i) { + Object dim = incrementalIndexRow.getDim(i); DimensionDesc dimensionDesc = dimensions.get(i); if (dimensionDesc == null) { continue; @@ -1111,7 +1111,7 @@ private static boolean allNull(Object[] dims, int startPosition) return true; } - interface FactsHolder + public interface FactsHolder { /** * @return the previous rowIndex associated with the specified key, or @@ -1385,7 +1385,7 @@ public LongMetricColumnSelector(IncrementalIndexRowHolder currEntry, int metricI public long getLong() { assert NullHandling.replaceWithDefault() || !isNull(); - return getMetricLongValue(currEntry.get().getRowIndex(), metricIndex); + return getMetricLongValue(currEntry.get(), metricIndex); } @Override @@ -1397,7 +1397,7 @@ public void inspectRuntimeShape(RuntimeShapeInspector inspector) @Override public boolean isNull() { - return IncrementalIndex.this.isNull(currEntry.get().getRowIndex(), metricIndex); + return IncrementalIndex.this.isNull(currEntry.get(), metricIndex); } } @@ -1422,7 +1422,7 @@ public ObjectMetricColumnSelector( @Override public Object getObject() { - return getMetricObjectValue(currEntry.get().getRowIndex(), metricIndex); + return getMetricObjectValue(currEntry.get(), metricIndex); } @Override @@ -1453,7 +1453,7 @@ public FloatMetricColumnSelector(IncrementalIndexRowHolder currEntry, int metric public float getFloat() { assert NullHandling.replaceWithDefault() || !isNull(); - return getMetricFloatValue(currEntry.get().getRowIndex(), metricIndex); + return getMetricFloatValue(currEntry.get(), metricIndex); } @Override @@ -1465,7 +1465,7 @@ public void inspectRuntimeShape(RuntimeShapeInspector inspector) @Override public boolean isNull() { - return IncrementalIndex.this.isNull(currEntry.get().getRowIndex(), metricIndex); + return IncrementalIndex.this.isNull(currEntry.get(), metricIndex); } } @@ -1484,13 +1484,13 @@ public DoubleMetricColumnSelector(IncrementalIndexRowHolder currEntry, int metri public double getDouble() { assert NullHandling.replaceWithDefault() || !isNull(); - return getMetricDoubleValue(currEntry.get().getRowIndex(), metricIndex); + return getMetricDoubleValue(currEntry.get(), metricIndex); } @Override public boolean isNull() { - return IncrementalIndex.this.isNull(currEntry.get().getRowIndex(), metricIndex); + return IncrementalIndex.this.isNull(currEntry.get(), metricIndex); } @Override diff --git a/processing/src/main/java/org/apache/druid/segment/incremental/IncrementalIndexAdapter.java b/processing/src/main/java/org/apache/druid/segment/incremental/IncrementalIndexAdapter.java index b896c9ec9272..84973c13a0a6 100644 --- a/processing/src/main/java/org/apache/druid/segment/incremental/IncrementalIndexAdapter.java +++ b/processing/src/main/java/org/apache/druid/segment/incremental/IncrementalIndexAdapter.java @@ -26,10 +26,12 @@ import org.apache.druid.segment.IndexableAdapter; import org.apache.druid.segment.IntIteratorUtils; import org.apache.druid.segment.Metadata; +import org.apache.druid.segment.StringDimensionIndexer; import org.apache.druid.segment.TransformableRowIterator; import org.apache.druid.segment.column.ColumnCapabilities; import org.apache.druid.segment.data.BitmapValues; import org.apache.druid.segment.data.CloseableIndexed; +import org.apache.druid.segment.data.IndexedInts; import org.joda.time.Interval; import javax.annotation.Nullable; @@ -95,14 +97,13 @@ private void processRows( { int rowNum = 0; for (IncrementalIndexRow row : index.getFacts().persistIterable()) { - final Object[] dims = row.getDims(); - for (IncrementalIndex.DimensionDesc dimension : dimensions) { final int dimIndex = dimension.getIndex(); DimensionAccessor accessor = accessors.get(dimension.getName()); // Add 'null' to the dimension's dictionary. - if (dimIndex >= dims.length || dims[dimIndex] == null) { + // No need to check "dimIndex >= row.getDimsLength()" because isDimNull() verifies that. + if (row.isDimNull(dimIndex)) { accessor.indexer.processRowValsToUnsortedEncodedKeyComponent(null, true); continue; } @@ -111,7 +112,16 @@ private void processRows( if (capabilities.hasBitmapIndexes()) { final MutableBitmap[] bitmapIndexes = accessor.invertedIndexes; final DimensionIndexer indexer = accessor.indexer; - indexer.fillBitmapsFromUnsortedEncodedKeyComponent(dims[dimIndex], rowNum, bitmapIndexes, bitmapFactory); + + // It is possible that the current indexer is StringDimensionIndexer and that the row + // supports fetching the IndexedInts object directly, which has better performance. + IndexedInts s = indexer instanceof StringDimensionIndexer ? row.getStringDim(dimIndex) : null; + + if (s != null) { + ((StringDimensionIndexer) indexer).fillBitmapsFromUnsortedEncodedKeyComponent(s, rowNum, bitmapIndexes, bitmapFactory); + } else { + indexer.fillBitmapsFromUnsortedEncodedKeyComponent(row.getDim(dimIndex), rowNum, bitmapIndexes, bitmapFactory); + } } } ++rowNum; diff --git a/processing/src/main/java/org/apache/druid/segment/incremental/IncrementalIndexRow.java b/processing/src/main/java/org/apache/druid/segment/incremental/IncrementalIndexRow.java index 987ee5f8bf7c..fb1eece6e46a 100644 --- a/processing/src/main/java/org/apache/druid/segment/incremental/IncrementalIndexRow.java +++ b/processing/src/main/java/org/apache/druid/segment/incremental/IncrementalIndexRow.java @@ -23,6 +23,7 @@ import com.google.common.collect.Lists; import org.apache.druid.java.util.common.DateTimes; import org.apache.druid.segment.DimensionIndexer; +import org.apache.druid.segment.data.IndexedInts; import javax.annotation.Nullable; import java.lang.reflect.Array; @@ -30,7 +31,7 @@ import java.util.Collections; import java.util.List; -public final class IncrementalIndexRow +public class IncrementalIndexRow { public static final int EMPTY_ROW_INDEX = -1; @@ -56,7 +57,7 @@ public final class IncrementalIndexRow this(timestamp, dims, dimensionDescsList, EMPTY_ROW_INDEX); } - IncrementalIndexRow( + public IncrementalIndexRow( long timestamp, Object[] dims, List dimensionDescsList, @@ -97,9 +98,29 @@ public long getTimestamp() return timestamp; } - public Object[] getDims() + @Nullable + public Object getDim(int index) + { + if (index >= dims.length) { + return null; + } + return dims[index]; + } + + public int getDimsLength() + { + return dims.length; + } + + public boolean isDimNull(int index) + { + return (index >= dims.length) || (dims[index] == null); + } + + @Nullable + public IndexedInts getStringDim(final int dimIndex) { - return dims; + return null; } public int getRowIndex() @@ -107,7 +128,7 @@ public int getRowIndex() return rowIndex; } - void setRowIndex(int rowIndex) + public void setRowIndex(int rowIndex) { this.rowIndex = rowIndex; } diff --git a/processing/src/main/java/org/apache/druid/segment/incremental/OffheapIncrementalIndex.java b/processing/src/main/java/org/apache/druid/segment/incremental/OffheapIncrementalIndex.java index a74f94fdb827..3ca9b2716354 100644 --- a/processing/src/main/java/org/apache/druid/segment/incremental/OffheapIncrementalIndex.java +++ b/processing/src/main/java/org/apache/druid/segment/incremental/OffheapIncrementalIndex.java @@ -286,46 +286,46 @@ protected Object getAggVal(BufferAggregator agg, int rowOffset, int aggPosition) } @Override - public float getMetricFloatValue(int rowOffset, int aggOffset) + public float getMetricFloatValue(IncrementalIndexRow incrementalIndexRow, int aggOffset) { BufferAggregator agg = getAggs()[aggOffset]; - int[] indexAndOffset = indexAndOffsets.get(rowOffset); + int[] indexAndOffset = indexAndOffsets.get(incrementalIndexRow.getRowIndex()); ByteBuffer bb = aggBuffers.get(indexAndOffset[0]).get(); return agg.getFloat(bb, indexAndOffset[1] + aggOffsetInBuffer[aggOffset]); } @Override - public long getMetricLongValue(int rowOffset, int aggOffset) + public long getMetricLongValue(IncrementalIndexRow incrementalIndexRow, int aggOffset) { BufferAggregator agg = getAggs()[aggOffset]; - int[] indexAndOffset = indexAndOffsets.get(rowOffset); + int[] indexAndOffset = indexAndOffsets.get(incrementalIndexRow.getRowIndex()); ByteBuffer bb = aggBuffers.get(indexAndOffset[0]).get(); return agg.getLong(bb, indexAndOffset[1] + aggOffsetInBuffer[aggOffset]); } @Override - public Object getMetricObjectValue(int rowOffset, int aggOffset) + public Object getMetricObjectValue(IncrementalIndexRow incrementalIndexRow, int aggOffset) { BufferAggregator agg = getAggs()[aggOffset]; - int[] indexAndOffset = indexAndOffsets.get(rowOffset); + int[] indexAndOffset = indexAndOffsets.get(incrementalIndexRow.getRowIndex()); ByteBuffer bb = aggBuffers.get(indexAndOffset[0]).get(); return agg.get(bb, indexAndOffset[1] + aggOffsetInBuffer[aggOffset]); } @Override - public double getMetricDoubleValue(int rowOffset, int aggOffset) + public double getMetricDoubleValue(IncrementalIndexRow incrementalIndexRow, int aggOffset) { BufferAggregator agg = getAggs()[aggOffset]; - int[] indexAndOffset = indexAndOffsets.get(rowOffset); + int[] indexAndOffset = indexAndOffsets.get(incrementalIndexRow.getRowIndex()); ByteBuffer bb = aggBuffers.get(indexAndOffset[0]).get(); return agg.getDouble(bb, indexAndOffset[1] + aggOffsetInBuffer[aggOffset]); } @Override - public boolean isNull(int rowOffset, int aggOffset) + public boolean isNull(IncrementalIndexRow incrementalIndexRow, int aggOffset) { BufferAggregator agg = getAggs()[aggOffset]; - int[] indexAndOffset = indexAndOffsets.get(rowOffset); + int[] indexAndOffset = indexAndOffsets.get(incrementalIndexRow.getRowIndex()); ByteBuffer bb = aggBuffers.get(indexAndOffset[0]).get(); return agg.isNull(bb, indexAndOffset[1] + aggOffsetInBuffer[aggOffset]); } diff --git a/processing/src/main/java/org/apache/druid/segment/incremental/OnheapIncrementalIndex.java b/processing/src/main/java/org/apache/druid/segment/incremental/OnheapIncrementalIndex.java index a72124a21533..ab975836265e 100644 --- a/processing/src/main/java/org/apache/druid/segment/incremental/OnheapIncrementalIndex.java +++ b/processing/src/main/java/org/apache/druid/segment/incremental/OnheapIncrementalIndex.java @@ -340,33 +340,33 @@ protected Object getAggVal(Aggregator agg, int rowOffset, int aggPosition) } @Override - public float getMetricFloatValue(int rowOffset, int aggOffset) + public float getMetricFloatValue(IncrementalIndexRow incrementalIndexRow, int aggOffset) { - return concurrentGet(rowOffset)[aggOffset].getFloat(); + return concurrentGet(incrementalIndexRow.getRowIndex())[aggOffset].getFloat(); } @Override - public long getMetricLongValue(int rowOffset, int aggOffset) + public long getMetricLongValue(IncrementalIndexRow incrementalIndexRow, int aggOffset) { - return concurrentGet(rowOffset)[aggOffset].getLong(); + return concurrentGet(incrementalIndexRow.getRowIndex())[aggOffset].getLong(); } @Override - public Object getMetricObjectValue(int rowOffset, int aggOffset) + public Object getMetricObjectValue(IncrementalIndexRow incrementalIndexRow, int aggOffset) { - return concurrentGet(rowOffset)[aggOffset].get(); + return concurrentGet(incrementalIndexRow.getRowIndex())[aggOffset].get(); } @Override - protected double getMetricDoubleValue(int rowOffset, int aggOffset) + protected double getMetricDoubleValue(IncrementalIndexRow incrementalIndexRow, int aggOffset) { - return concurrentGet(rowOffset)[aggOffset].getDouble(); + return concurrentGet(incrementalIndexRow.getRowIndex())[aggOffset].getDouble(); } @Override - public boolean isNull(int rowOffset, int aggOffset) + public boolean isNull(IncrementalIndexRow incrementalIndexRow, int aggOffset) { - return concurrentGet(rowOffset)[aggOffset].isNull(); + return concurrentGet(incrementalIndexRow.getRowIndex())[aggOffset].isNull(); } /** @@ -390,7 +390,7 @@ public void close() * heap space. In general the selectorFactory need not to thread-safe. If required, set concurrentEventAdd to true to * use concurrent hash map instead of vanilla hash map for thread-safe operations. */ - static class CachingColumnSelectorFactory implements ColumnSelectorFactory + public static class CachingColumnSelectorFactory implements ColumnSelectorFactory { private final Map> columnSelectorMap; private final ColumnSelectorFactory delegate; diff --git a/processing/src/test/java/org/apache/druid/segment/incremental/IncrementalIndexAdapterTest.java b/processing/src/test/java/org/apache/druid/segment/incremental/IncrementalIndexAdapterTest.java index e9c6139e3311..afcfedbfc69f 100644 --- a/processing/src/test/java/org/apache/druid/segment/incremental/IncrementalIndexAdapterTest.java +++ b/processing/src/test/java/org/apache/druid/segment/incremental/IncrementalIndexAdapterTest.java @@ -40,7 +40,9 @@ import org.junit.runners.Parameterized; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; +import java.util.HashMap; import java.util.List; import java.util.function.Function; @@ -127,14 +129,26 @@ public void testGetRowsIterableNoRollup() throws Exception IncrementalIndexTest.populateIndex(timestamp, toPersist1); IncrementalIndexTest.populateIndex(timestamp, toPersist1); - - ArrayList dim1Vals = new ArrayList<>(); + // facts.keySet() return the rows in the order they are stored internally. + // In plain mode, OnheapInrementalIndex and OffheapIncrementalIndex sort their rows internally by timestamp then by + // index (the order they were inserted). + // But facts.keySet() does not require this order. Other implementations, e.g. OakIncrementalIndex, might sort their + // rows in their native order (as it would be expected by facts.persistIterable()). + // To mitigate this, we validate the row index without expecting a specific order. + HashMap dim1Vals = new HashMap<>(); + HashMap dim2Vals = new HashMap<>(); for (IncrementalIndexRow row : toPersist1.getFacts().keySet()) { - dim1Vals.add(((int[]) row.getDims()[0])[0]); + dim1Vals.put(row.getRowIndex(), ((int[]) row.getDim(0))[0]); + dim2Vals.put(row.getRowIndex(), ((int[]) row.getDim(1))[0]); } - ArrayList dim2Vals = new ArrayList<>(); - for (IncrementalIndexRow row : toPersist1.getFacts().keySet()) { - dim2Vals.add(((int[]) row.getDims()[1])[0]); + + Assert.assertEquals(6, dim1Vals.size()); + Assert.assertEquals(6, dim2Vals.size()); + + List expected = Arrays.asList(0, 1, 0, 1, 0, 1); + for (int i = 0; i < 6; i++) { + Assert.assertEquals(expected.get(i), dim1Vals.get(i)); + Assert.assertEquals(expected.get(i), dim2Vals.get(i)); } final IndexableAdapter incrementalAdapter = new IncrementalIndexAdapter( @@ -184,13 +198,6 @@ public void testGetRowsIterableNoRollup() throws Exception Assert.assertEquals(6, rowStrings.size()); for (int i = 0; i < 6; i++) { - if (i % 2 == 0) { - Assert.assertEquals(0, (long) dim1Vals.get(i)); - Assert.assertEquals(0, (long) dim2Vals.get(i)); - } else { - Assert.assertEquals(1, (long) dim1Vals.get(i)); - Assert.assertEquals(1, (long) dim2Vals.get(i)); - } Assert.assertEquals(getExpected.apply(i), rowStrings.get(i)); } } diff --git a/processing/src/test/java/org/apache/druid/segment/incremental/IncrementalIndexIngestionTest.java b/processing/src/test/java/org/apache/druid/segment/incremental/IncrementalIndexIngestionTest.java index 5886f7ca4006..8797b2851b5f 100644 --- a/processing/src/test/java/org/apache/druid/segment/incremental/IncrementalIndexIngestionTest.java +++ b/processing/src/test/java/org/apache/druid/segment/incremental/IncrementalIndexIngestionTest.java @@ -114,7 +114,7 @@ public void run() { while (!Thread.interrupted()) { for (IncrementalIndexRow row : index.getFacts().keySet()) { - if (index.getMetricLongValue(row.getRowIndex(), 0) != 1) { + if (index.getMetricLongValue(row, 0) != 1) { checkFailedCount.addAndGet(1); } } @@ -204,11 +204,11 @@ public void run() long jsSum = 0; for (IncrementalIndexRow row : indexExpr.getFacts().keySet()) { - exprSum += indexExpr.getMetricLongValue(row.getRowIndex(), 0); + exprSum += indexExpr.getMetricLongValue(row, 0); } for (IncrementalIndexRow row : indexJs.getFacts().keySet()) { - jsSum += indexJs.getMetricLongValue(row.getRowIndex(), 0); + jsSum += indexJs.getMetricLongValue(row, 0); } Assert.assertEquals(exprSum, jsSum); From ccef021e1afa3f8073c6044bf4cb40fde6ac796a Mon Sep 17 00:00:00 2001 From: Liran Funaro Date: Wed, 13 Jan 2021 10:10:07 +0200 Subject: [PATCH 02/33] Remove unused dependency --- extensions-contrib/oak-incremental-index/pom.xml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/extensions-contrib/oak-incremental-index/pom.xml b/extensions-contrib/oak-incremental-index/pom.xml index 2c98904a7b9c..53ff2ab10b32 100644 --- a/extensions-contrib/oak-incremental-index/pom.xml +++ b/extensions-contrib/oak-incremental-index/pom.xml @@ -126,10 +126,6 @@ junit test - - joda-time - joda-time - org.openjdk.jmh jmh-core From c343fc98b06bf489e3911f019b2390dcd9b37569 Mon Sep 17 00:00:00 2001 From: Liran Funaro Date: Wed, 13 Jan 2021 10:19:21 +0200 Subject: [PATCH 03/33] Remove use of Holder --- .../segment/incremental/oak/OakIncrementalIndex.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndex.java b/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndex.java index 340e6c7903cc..19a537bbccbf 100644 --- a/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndex.java +++ b/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndex.java @@ -52,7 +52,6 @@ import org.apache.druid.segment.incremental.OnheapIncrementalIndex; import javax.annotation.Nullable; -import javax.xml.ws.Holder; import java.nio.ByteBuffer; import java.util.HashMap; import java.util.Iterator; @@ -493,15 +492,15 @@ private Iterator transformNonStreamIterator( private Iterator transformStreamIterator( Iterator> iterator) { - Holder row = new Holder<>(); + final OakIncrementalIndexRow[] rowHolder = new OakIncrementalIndexRow[1]; return Iterators.transform(iterator, entry -> { - if (row.value == null) { - row.value = new OakIncrementalIndexRow(entry.getKey(), dimensionDescsList, entry.getValue()); + if (rowHolder[0] == null) { + rowHolder[0] = new OakIncrementalIndexRow(entry.getKey(), dimensionDescsList, entry.getValue()); } else { - row.value.reset(); + rowHolder[0].reset(); } - return row.value; + return rowHolder[0]; }); } From 5bfcd97ff725805fa97f5fa8be0f795d05f2a52c Mon Sep 17 00:00:00 2001 From: Liran Funaro Date: Wed, 13 Jan 2021 10:23:39 +0200 Subject: [PATCH 04/33] Remove main class from OakBenchmarks --- .../incremental/oak/OakBenchmarks.java | 24 ------------------- 1 file changed, 24 deletions(-) diff --git a/extensions-contrib/oak-incremental-index/src/test/java/org/apache/druid/segment/incremental/oak/OakBenchmarks.java b/extensions-contrib/oak-incremental-index/src/test/java/org/apache/druid/segment/incremental/oak/OakBenchmarks.java index fa3fb79c1c75..66decde4d066 100644 --- a/extensions-contrib/oak-incremental-index/src/test/java/org/apache/druid/segment/incremental/oak/OakBenchmarks.java +++ b/extensions-contrib/oak-incremental-index/src/test/java/org/apache/druid/segment/incremental/oak/OakBenchmarks.java @@ -20,11 +20,6 @@ package org.apache.druid.segment.incremental.oak; import org.apache.druid.segment.incremental.IncrementalIndexCreator; -import org.openjdk.jmh.runner.Runner; -import org.openjdk.jmh.runner.RunnerException; -import org.openjdk.jmh.runner.options.Options; -import org.openjdk.jmh.runner.options.OptionsBuilder; -import org.openjdk.jmh.runner.options.TimeValue; /** * This class wraps Oak relevant benchmarks and adds additional static initialization @@ -32,25 +27,6 @@ */ public class OakBenchmarks { - public static void main(String[] args) throws RunnerException - { - final String thisClass = OakBenchmarks.class.getName(); - - Options opt = new OptionsBuilder() - .include(thisClass + ".IncrementalIndexReadBenchmark.read$") - .warmupIterations(0) - .measurementIterations(1) - .measurementTime(TimeValue.NONE) - .forks(1) - .threads(1) - .param("indexType", "oak") - .param("rollup", "true") - .param("rowsPerSegment", "1000") - .build(); - - new Runner(opt).run(); - } - public static void initModule() { IncrementalIndexCreator.JSON_MAPPER.registerModule(OakIncrementalIndexModule.JACKSON_MODULE); From e8d45043f50ae82eff7cd7f8f62397472e8dd73f Mon Sep 17 00:00:00 2001 From: Liran Funaro Date: Wed, 20 Jan 2021 12:34:02 +0200 Subject: [PATCH 05/33] Ignore OakBenchmarks for junit tests --- .../org/apache/druid/segment/incremental/oak/OakBenchmarks.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/extensions-contrib/oak-incremental-index/src/test/java/org/apache/druid/segment/incremental/oak/OakBenchmarks.java b/extensions-contrib/oak-incremental-index/src/test/java/org/apache/druid/segment/incremental/oak/OakBenchmarks.java index 66decde4d066..33c0759f20bf 100644 --- a/extensions-contrib/oak-incremental-index/src/test/java/org/apache/druid/segment/incremental/oak/OakBenchmarks.java +++ b/extensions-contrib/oak-incremental-index/src/test/java/org/apache/druid/segment/incremental/oak/OakBenchmarks.java @@ -20,11 +20,13 @@ package org.apache.druid.segment.incremental.oak; import org.apache.druid.segment.incremental.IncrementalIndexCreator; +import org.junit.Ignore; /** * This class wraps Oak relevant benchmarks and adds additional static initialization * to add Oak to the available incremental indexes. */ +@Ignore public class OakBenchmarks { public static void initModule() From 8f373130781940299f23d42d032c0d457be3607e Mon Sep 17 00:00:00 2001 From: Liran Funaro Date: Wed, 20 Jan 2021 14:38:37 +0200 Subject: [PATCH 06/33] Update version --- extensions-contrib/oak-incremental-index/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions-contrib/oak-incremental-index/pom.xml b/extensions-contrib/oak-incremental-index/pom.xml index 53ff2ab10b32..8cab2a9d72f2 100644 --- a/extensions-contrib/oak-incremental-index/pom.xml +++ b/extensions-contrib/oak-incremental-index/pom.xml @@ -29,7 +29,7 @@ org.apache.druid druid - 0.21.0-SNAPSHOT + 0.22.0-SNAPSHOT ../../pom.xml From ccb0deb69fc2946faf6d170e9f194fd3bdc891ff Mon Sep 17 00:00:00 2001 From: Liran Funaro Date: Thu, 21 Jan 2021 10:49:49 +0200 Subject: [PATCH 07/33] Move benchmarks to its module and clean up maven dependencies --- benchmarks/pom.xml | 6 + .../FilteredAggregatorBenchmark.java | 7 +- .../IncrementalIndexReadBenchmark.java | 5 +- .../indexing/IndexIngestionBenchmark.java | 5 +- .../indexing/IndexPersistBenchmark.java | 5 +- .../benchmark/query/GroupByBenchmark.java | 5 +- .../druid/benchmark/query/ScanBenchmark.java | 5 +- .../benchmark/query/SearchBenchmark.java | 5 +- .../benchmark/query/TimeseriesBenchmark.java | 5 +- .../druid/benchmark/query/TopNBenchmark.java | 5 +- .../oak-incremental-index/assembly.xml | 45 ----- .../oak-incremental-index/pom.xml | 186 ------------------ .../incremental/oak/OakBenchmarks.java | 99 ---------- 13 files changed, 43 insertions(+), 340 deletions(-) delete mode 100644 extensions-contrib/oak-incremental-index/assembly.xml delete mode 100644 extensions-contrib/oak-incremental-index/src/test/java/org/apache/druid/segment/incremental/oak/OakBenchmarks.java diff --git a/benchmarks/pom.xml b/benchmarks/pom.xml index 10fc13443e7f..6a018f70da6f 100644 --- a/benchmarks/pom.xml +++ b/benchmarks/pom.xml @@ -185,6 +185,12 @@ ${project.parent.version} test + + org.apache.druid.extensions + oak-incremental-index + ${project.parent.version} + test + diff --git a/benchmarks/src/test/java/org/apache/druid/benchmark/FilteredAggregatorBenchmark.java b/benchmarks/src/test/java/org/apache/druid/benchmark/FilteredAggregatorBenchmark.java index fab083118130..9ed3ce8d831c 100644 --- a/benchmarks/src/test/java/org/apache/druid/benchmark/FilteredAggregatorBenchmark.java +++ b/benchmarks/src/test/java/org/apache/druid/benchmark/FilteredAggregatorBenchmark.java @@ -73,6 +73,7 @@ import org.apache.druid.segment.incremental.IncrementalIndex; import org.apache.druid.segment.incremental.IncrementalIndexCreator; import org.apache.druid.segment.incremental.OnheapIncrementalIndex; +import org.apache.druid.segment.incremental.oak.OakIncrementalIndexModule; import org.apache.druid.segment.serde.ComplexMetrics; import org.apache.druid.segment.writeout.OffHeapMemorySegmentWriteOutMediumFactory; import org.apache.druid.timeline.SegmentId; @@ -106,6 +107,8 @@ public class FilteredAggregatorBenchmark { static { NullHandling.initializeForTests(); + // Register OakIncrementalIndex + IncrementalIndexCreator.JSON_MAPPER.registerModule(OakIncrementalIndexModule.JACKSON_MODULE); } @Param({"75000"}) @@ -204,7 +207,7 @@ public void setup() @State(Scope.Benchmark) public static class IncrementalIndexState { - @Param({"onheap", "offheap"}) + @Param({"onheap", "offheap", "oak"}) private String indexType; IncrementalIndex incIndex; @@ -234,7 +237,7 @@ public void tearDown() @State(Scope.Benchmark) public static class IncrementalIndexIngestState { - @Param({"onheap", "offheap"}) + @Param({"onheap", "offheap", "oak"}) private String indexType; IncrementalIndex incIndex; diff --git a/benchmarks/src/test/java/org/apache/druid/benchmark/indexing/IncrementalIndexReadBenchmark.java b/benchmarks/src/test/java/org/apache/druid/benchmark/indexing/IncrementalIndexReadBenchmark.java index c07c34f10d7e..40e24e1a2269 100644 --- a/benchmarks/src/test/java/org/apache/druid/benchmark/indexing/IncrementalIndexReadBenchmark.java +++ b/benchmarks/src/test/java/org/apache/druid/benchmark/indexing/IncrementalIndexReadBenchmark.java @@ -47,6 +47,7 @@ import org.apache.druid.segment.incremental.IncrementalIndexCreator; import org.apache.druid.segment.incremental.IncrementalIndexSchema; import org.apache.druid.segment.incremental.IncrementalIndexStorageAdapter; +import org.apache.druid.segment.incremental.oak.OakIncrementalIndexModule; import org.apache.druid.segment.serde.ComplexMetrics; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; @@ -84,7 +85,7 @@ public class IncrementalIndexReadBenchmark @Param({"true", "false"}) private boolean rollup; - @Param({"onheap", "offheap"}) + @Param({"onheap", "offheap", "oak"}) private String indexType; private static final Logger log = new Logger(IncrementalIndexReadBenchmark.class); @@ -92,6 +93,8 @@ public class IncrementalIndexReadBenchmark static { NullHandling.initializeForTests(); + // Register OakIncrementalIndex + IncrementalIndexCreator.JSON_MAPPER.registerModule(OakIncrementalIndexModule.JACKSON_MODULE); } private AppendableIndexSpec appendableIndexSpec; diff --git a/benchmarks/src/test/java/org/apache/druid/benchmark/indexing/IndexIngestionBenchmark.java b/benchmarks/src/test/java/org/apache/druid/benchmark/indexing/IndexIngestionBenchmark.java index 33819ef4ad51..492657762444 100644 --- a/benchmarks/src/test/java/org/apache/druid/benchmark/indexing/IndexIngestionBenchmark.java +++ b/benchmarks/src/test/java/org/apache/druid/benchmark/indexing/IndexIngestionBenchmark.java @@ -31,6 +31,7 @@ import org.apache.druid.segment.incremental.IncrementalIndex; import org.apache.druid.segment.incremental.IncrementalIndexCreator; import org.apache.druid.segment.incremental.IncrementalIndexSchema; +import org.apache.druid.segment.incremental.oak.OakIncrementalIndexModule; import org.apache.druid.segment.serde.ComplexMetrics; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; @@ -68,7 +69,7 @@ public class IndexIngestionBenchmark @Param({"none", "moderate", "high"}) private String rollupOpportunity; - @Param({"onheap", "offheap"}) + @Param({"onheap", "offheap", "oak"}) private String indexType; private static final Logger log = new Logger(IndexIngestionBenchmark.class); @@ -76,6 +77,8 @@ public class IndexIngestionBenchmark static { NullHandling.initializeForTests(); + // Register OakIncrementalIndex + IncrementalIndexCreator.JSON_MAPPER.registerModule(OakIncrementalIndexModule.JACKSON_MODULE); } private AppendableIndexSpec appendableIndexSpec; diff --git a/benchmarks/src/test/java/org/apache/druid/benchmark/indexing/IndexPersistBenchmark.java b/benchmarks/src/test/java/org/apache/druid/benchmark/indexing/IndexPersistBenchmark.java index 3679adaac7bd..074d31f53a79 100644 --- a/benchmarks/src/test/java/org/apache/druid/benchmark/indexing/IndexPersistBenchmark.java +++ b/benchmarks/src/test/java/org/apache/druid/benchmark/indexing/IndexPersistBenchmark.java @@ -37,6 +37,7 @@ import org.apache.druid.segment.incremental.IncrementalIndex; import org.apache.druid.segment.incremental.IncrementalIndexCreator; import org.apache.druid.segment.incremental.IncrementalIndexSchema; +import org.apache.druid.segment.incremental.oak.OakIncrementalIndexModule; import org.apache.druid.segment.serde.ComplexMetrics; import org.apache.druid.segment.writeout.OffHeapMemorySegmentWriteOutMediumFactory; import org.openjdk.jmh.annotations.Benchmark; @@ -73,6 +74,8 @@ public class IndexPersistBenchmark static { NullHandling.initializeForTests(); + // Register OakIncrementalIndex + IncrementalIndexCreator.JSON_MAPPER.registerModule(OakIncrementalIndexModule.JACKSON_MODULE); JSON_MAPPER = new DefaultObjectMapper(); INDEX_IO = new IndexIO( JSON_MAPPER, @@ -93,7 +96,7 @@ public class IndexPersistBenchmark @Param({"none", "moderate", "high"}) private String rollupOpportunity; - @Param({"onheap", "offheap"}) + @Param({"onheap", "offheap", "oak"}) private String indexType; private AppendableIndexSpec appendableIndexSpec; diff --git a/benchmarks/src/test/java/org/apache/druid/benchmark/query/GroupByBenchmark.java b/benchmarks/src/test/java/org/apache/druid/benchmark/query/GroupByBenchmark.java index b9808c9cad69..dbae63218f0e 100644 --- a/benchmarks/src/test/java/org/apache/druid/benchmark/query/GroupByBenchmark.java +++ b/benchmarks/src/test/java/org/apache/druid/benchmark/query/GroupByBenchmark.java @@ -90,6 +90,7 @@ import org.apache.druid.segment.incremental.IncrementalIndexCreator; import org.apache.druid.segment.incremental.IncrementalIndexSchema; import org.apache.druid.segment.incremental.OnheapIncrementalIndex; +import org.apache.druid.segment.incremental.oak.OakIncrementalIndexModule; import org.apache.druid.segment.serde.ComplexMetrics; import org.apache.druid.segment.writeout.OffHeapMemorySegmentWriteOutMediumFactory; import org.apache.druid.timeline.SegmentId; @@ -154,6 +155,8 @@ public class GroupByBenchmark static { NullHandling.initializeForTests(); + // Register OakIncrementalIndex + IncrementalIndexCreator.JSON_MAPPER.registerModule(OakIncrementalIndexModule.JACKSON_MODULE); } private AppendableIndexSpec appendableIndexSpec; @@ -533,7 +536,7 @@ public String getFormatString() @State(Scope.Benchmark) public static class IncrementalIndexState { - @Param({"onheap", "offheap"}) + @Param({"onheap", "offheap", "oak"}) private String indexType; IncrementalIndex incIndex; diff --git a/benchmarks/src/test/java/org/apache/druid/benchmark/query/ScanBenchmark.java b/benchmarks/src/test/java/org/apache/druid/benchmark/query/ScanBenchmark.java index 10c31b5cbe4b..bc5a7f0838b2 100644 --- a/benchmarks/src/test/java/org/apache/druid/benchmark/query/ScanBenchmark.java +++ b/benchmarks/src/test/java/org/apache/druid/benchmark/query/ScanBenchmark.java @@ -71,6 +71,7 @@ import org.apache.druid.segment.incremental.IncrementalIndex; import org.apache.druid.segment.incremental.IncrementalIndexCreator; import org.apache.druid.segment.incremental.OnheapIncrementalIndex; +import org.apache.druid.segment.incremental.oak.OakIncrementalIndexModule; import org.apache.druid.segment.serde.ComplexMetrics; import org.apache.druid.segment.writeout.OffHeapMemorySegmentWriteOutMediumFactory; import org.apache.druid.timeline.SegmentId; @@ -125,6 +126,8 @@ public class ScanBenchmark static { NullHandling.initializeForTests(); + // Register OakIncrementalIndex + IncrementalIndexCreator.JSON_MAPPER.registerModule(OakIncrementalIndexModule.JACKSON_MODULE); } private AppendableIndexSpec appendableIndexSpec; @@ -281,7 +284,7 @@ public void setup() @State(Scope.Benchmark) public static class IncrementalIndexState { - @Param({"onheap", "offheap"}) + @Param({"onheap", "offheap", "oak"}) private String indexType; IncrementalIndex incIndex; diff --git a/benchmarks/src/test/java/org/apache/druid/benchmark/query/SearchBenchmark.java b/benchmarks/src/test/java/org/apache/druid/benchmark/query/SearchBenchmark.java index 2060591be6f7..af4291333b6f 100644 --- a/benchmarks/src/test/java/org/apache/druid/benchmark/query/SearchBenchmark.java +++ b/benchmarks/src/test/java/org/apache/druid/benchmark/query/SearchBenchmark.java @@ -78,6 +78,7 @@ import org.apache.druid.segment.incremental.IncrementalIndex; import org.apache.druid.segment.incremental.IncrementalIndexCreator; import org.apache.druid.segment.incremental.OnheapIncrementalIndex; +import org.apache.druid.segment.incremental.oak.OakIncrementalIndexModule; import org.apache.druid.segment.serde.ComplexMetrics; import org.apache.druid.segment.writeout.OffHeapMemorySegmentWriteOutMediumFactory; import org.apache.druid.timeline.SegmentId; @@ -128,6 +129,8 @@ public class SearchBenchmark static { NullHandling.initializeForTests(); + // Register OakIncrementalIndex + IncrementalIndexCreator.JSON_MAPPER.registerModule(OakIncrementalIndexModule.JACKSON_MODULE); } private AppendableIndexSpec appendableIndexSpec; @@ -351,7 +354,7 @@ public void setup() @State(Scope.Benchmark) public static class IncrementalIndexState { - @Param({"onheap", "offheap"}) + @Param({"onheap", "offheap", "oak"}) private String indexType; IncrementalIndex incIndex; diff --git a/benchmarks/src/test/java/org/apache/druid/benchmark/query/TimeseriesBenchmark.java b/benchmarks/src/test/java/org/apache/druid/benchmark/query/TimeseriesBenchmark.java index 98b3dd51fc9d..b1ffd1b2ceb2 100644 --- a/benchmarks/src/test/java/org/apache/druid/benchmark/query/TimeseriesBenchmark.java +++ b/benchmarks/src/test/java/org/apache/druid/benchmark/query/TimeseriesBenchmark.java @@ -72,6 +72,7 @@ import org.apache.druid.segment.incremental.IncrementalIndex; import org.apache.druid.segment.incremental.IncrementalIndexCreator; import org.apache.druid.segment.incremental.OnheapIncrementalIndex; +import org.apache.druid.segment.incremental.oak.OakIncrementalIndexModule; import org.apache.druid.segment.serde.ComplexMetrics; import org.apache.druid.segment.writeout.OffHeapMemorySegmentWriteOutMediumFactory; import org.apache.druid.timeline.SegmentId; @@ -122,6 +123,8 @@ public class TimeseriesBenchmark static { NullHandling.initializeForTests(); + // Register OakIncrementalIndex + IncrementalIndexCreator.JSON_MAPPER.registerModule(OakIncrementalIndexModule.JACKSON_MODULE); } private AppendableIndexSpec appendableIndexSpec; @@ -276,7 +279,7 @@ public void setup() @State(Scope.Benchmark) public static class IncrementalIndexState { - @Param({"onheap", "offheap"}) + @Param({"onheap", "offheap", "oak"}) private String indexType; IncrementalIndex incIndex; diff --git a/benchmarks/src/test/java/org/apache/druid/benchmark/query/TopNBenchmark.java b/benchmarks/src/test/java/org/apache/druid/benchmark/query/TopNBenchmark.java index 6587024583f1..a47e66e1f7bb 100644 --- a/benchmarks/src/test/java/org/apache/druid/benchmark/query/TopNBenchmark.java +++ b/benchmarks/src/test/java/org/apache/druid/benchmark/query/TopNBenchmark.java @@ -69,6 +69,7 @@ import org.apache.druid.segment.incremental.IncrementalIndex; import org.apache.druid.segment.incremental.IncrementalIndexCreator; import org.apache.druid.segment.incremental.OnheapIncrementalIndex; +import org.apache.druid.segment.incremental.oak.OakIncrementalIndexModule; import org.apache.druid.segment.serde.ComplexMetrics; import org.apache.druid.segment.writeout.OffHeapMemorySegmentWriteOutMediumFactory; import org.apache.druid.timeline.SegmentId; @@ -119,6 +120,8 @@ public class TopNBenchmark static { NullHandling.initializeForTests(); + // Register OakIncrementalIndex + IncrementalIndexCreator.JSON_MAPPER.registerModule(OakIncrementalIndexModule.JACKSON_MODULE); } private AppendableIndexSpec appendableIndexSpec; @@ -254,7 +257,7 @@ public void setup() @State(Scope.Benchmark) public static class IncrementalIndexState { - @Param({"onheap", "offheap"}) + @Param({"onheap", "offheap", "oak"}) private String indexType; IncrementalIndex incIndex; diff --git a/extensions-contrib/oak-incremental-index/assembly.xml b/extensions-contrib/oak-incremental-index/assembly.xml deleted file mode 100644 index eb5c0ad27265..000000000000 --- a/extensions-contrib/oak-incremental-index/assembly.xml +++ /dev/null @@ -1,45 +0,0 @@ - - - - - fat-benchmark - - jar - - false - - - / - true - true - test - - - - - ${project.build.directory}/test-classes - / - true - - - \ No newline at end of file diff --git a/extensions-contrib/oak-incremental-index/pom.xml b/extensions-contrib/oak-incremental-index/pom.xml index 8cab2a9d72f2..086b269253e4 100644 --- a/extensions-contrib/oak-incremental-index/pom.xml +++ b/extensions-contrib/oak-incremental-index/pom.xml @@ -68,57 +68,17 @@ guice provided - - commons-codec - commons-codec - provided - - - it.unimi.dsi - fastutil - provided - com.fasterxml.jackson.core jackson-annotations provided - - com.fasterxml.jackson.core - jackson-core - provided - com.fasterxml.jackson.core jackson-databind provided - - com.fasterxml.jackson.datatype - jackson-datatype-guava - provided - - - com.fasterxml.jackson.datatype - jackson-datatype-joda - provided - - - com.fasterxml.jackson.dataformat - jackson-dataformat-smile - provided - - - com.fasterxml.jackson.jaxrs - jackson-jaxrs-json-provider - provided - - - com.fasterxml.jackson.jaxrs - jackson-jaxrs-smile-provider - provided - @@ -126,29 +86,6 @@ junit test - - org.openjdk.jmh - jmh-core - ${jmh.version} - test - - - org.easymock - easymock - test - - - nl.jqno.equalsverifier - equalsverifier - test - - - org.apache.druid - druid-core - ${project.parent.version} - test-jar - test - org.apache.druid druid-processing @@ -156,128 +93,5 @@ test-jar test - - org.apache.druid - druid-server - ${project.parent.version} - test - test-jar - - - org.apache.druid - druid-sql - ${project.parent.version} - test-jar - test - - - org.apache.druid - druid-benchmarks - ${project.parent.version} - test-jar - test - - - - - UTF-8 - 1.21 - 1.8 - oak-benchmarks - - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.1 - - ${javac.target} - ${javac.target} - ${javac.target} - - - - org.apache.maven.plugins - maven-assembly-plugin - 2.6 - - - package - - single - - - ${uberjar.name} - false - assembly.xml - - - org.openjdk.jmh.Main - - - - - - - - org.owasp - dependency-check-maven - - true - - - - org.jacoco - jacoco-maven-plugin - - true - - - - - - - maven-clean-plugin - 2.5 - - - maven-deploy-plugin - 2.8.1 - - - maven-install-plugin - 2.5.1 - - - maven-jar-plugin - 2.4 - - - maven-javadoc-plugin - 2.9.1 - - - maven-resources-plugin - 2.6 - - - maven-site-plugin - 3.3 - - - maven-source-plugin - 2.2.1 - - - maven-surefire-plugin - - @{jacocoArgLine} - - - - - diff --git a/extensions-contrib/oak-incremental-index/src/test/java/org/apache/druid/segment/incremental/oak/OakBenchmarks.java b/extensions-contrib/oak-incremental-index/src/test/java/org/apache/druid/segment/incremental/oak/OakBenchmarks.java deleted file mode 100644 index 33c0759f20bf..000000000000 --- a/extensions-contrib/oak-incremental-index/src/test/java/org/apache/druid/segment/incremental/oak/OakBenchmarks.java +++ /dev/null @@ -1,99 +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. - */ - -package org.apache.druid.segment.incremental.oak; - -import org.apache.druid.segment.incremental.IncrementalIndexCreator; -import org.junit.Ignore; - -/** - * This class wraps Oak relevant benchmarks and adds additional static initialization - * to add Oak to the available incremental indexes. - */ -@Ignore -public class OakBenchmarks -{ - public static void initModule() - { - IncrementalIndexCreator.JSON_MAPPER.registerModule(OakIncrementalIndexModule.JACKSON_MODULE); - } - - public static class IncrementalIndexReadBenchmark extends org.apache.druid.benchmark.indexing.IncrementalIndexReadBenchmark - { - static { - OakBenchmarks.initModule(); - } - } - - public static class IndexIngestionBenchmark extends org.apache.druid.benchmark.indexing.IndexIngestionBenchmark - { - static { - OakBenchmarks.initModule(); - } - } - - public static class IndexPersistBenchmark extends org.apache.druid.benchmark.indexing.IndexPersistBenchmark - { - static { - OakBenchmarks.initModule(); - } - } - - public static class GroupByBenchmark extends org.apache.druid.benchmark.query.GroupByBenchmark - { - static { - OakBenchmarks.initModule(); - } - } - - public static class ScanBenchmark extends org.apache.druid.benchmark.query.ScanBenchmark - { - static { - OakBenchmarks.initModule(); - } - } - - public static class SearchBenchmark extends org.apache.druid.benchmark.query.SearchBenchmark - { - static { - OakBenchmarks.initModule(); - } - } - - public static class TimeseriesBenchmark extends org.apache.druid.benchmark.query.TimeseriesBenchmark - { - static { - OakBenchmarks.initModule(); - } - } - - public static class TopNBenchmark extends org.apache.druid.benchmark.query.TopNBenchmark - { - static { - OakBenchmarks.initModule(); - } - } - - public static class FilteredAggregatorBenchmark extends org.apache.druid.benchmark.FilteredAggregatorBenchmark - { - static { - OakBenchmarks.initModule(); - } - } -} From bf0d936a57c1351fa50467a7a527301d887a5a9f Mon Sep 17 00:00:00 2001 From: Liran Funaro Date: Thu, 21 Jan 2021 19:01:00 +0200 Subject: [PATCH 08/33] Fix test suite to run with maven --- .../oak-incremental-index/pom.xml | 26 +++++++++++++++++++ .../oak/{OakTestSuite.java => TestSuite.java} | 5 ++-- 2 files changed, 28 insertions(+), 3 deletions(-) rename extensions-contrib/oak-incremental-index/src/test/java/org/apache/druid/segment/incremental/oak/{OakTestSuite.java => TestSuite.java} (93%) diff --git a/extensions-contrib/oak-incremental-index/pom.xml b/extensions-contrib/oak-incremental-index/pom.xml index 086b269253e4..11d8b64a7bc6 100644 --- a/extensions-contrib/oak-incremental-index/pom.xml +++ b/extensions-contrib/oak-incremental-index/pom.xml @@ -86,6 +86,18 @@ junit test + + org.easymock + easymock + test + + + org.apache.druid + druid-core + ${project.parent.version} + test-jar + test + org.apache.druid druid-processing @@ -93,5 +105,19 @@ test-jar test + + org.apache.druid + druid-server + ${project.parent.version} + test + test-jar + + + org.apache.druid + druid-sql + ${project.parent.version} + test-jar + test + diff --git a/extensions-contrib/oak-incremental-index/src/test/java/org/apache/druid/segment/incremental/oak/OakTestSuite.java b/extensions-contrib/oak-incremental-index/src/test/java/org/apache/druid/segment/incremental/oak/TestSuite.java similarity index 93% rename from extensions-contrib/oak-incremental-index/src/test/java/org/apache/druid/segment/incremental/oak/OakTestSuite.java rename to extensions-contrib/oak-incremental-index/src/test/java/org/apache/druid/segment/incremental/oak/TestSuite.java index 1949604e5f32..1573474fd6d1 100644 --- a/extensions-contrib/oak-incremental-index/src/test/java/org/apache/druid/segment/incremental/oak/OakTestSuite.java +++ b/extensions-contrib/oak-incremental-index/src/test/java/org/apache/druid/segment/incremental/oak/TestSuite.java @@ -25,7 +25,6 @@ import org.apache.druid.segment.incremental.IncrementalIndexRowCompTest; import org.apache.druid.segment.incremental.IncrementalIndexRowSizeTest; import org.apache.druid.segment.incremental.IncrementalIndexStorageAdapterTest; -import org.apache.druid.segment.incremental.IncrementalIndexTest; import org.junit.runner.RunWith; import org.junit.runners.Suite; @@ -36,7 +35,7 @@ @Suite.SuiteClasses({ OakDummyInitTest.class, org.apache.druid.segment.data.IncrementalIndexTest.class, - IncrementalIndexTest.class, + org.apache.druid.segment.incremental.IncrementalIndexTest.class, IncrementalIndexStorageAdapterTest.class, IncrementalIndexRowSizeTest.class, IncrementalIndexRowCompTest.class, @@ -44,6 +43,6 @@ IncrementalIndexIngestionTest.class, IncrementalIndexAdapterTest.class, }) -public class OakTestSuite +public class TestSuite { } From 82809978de4e141c0629e7f942f135515c4e2090 Mon Sep 17 00:00:00 2001 From: Liran Funaro Date: Thu, 11 Feb 2021 09:32:36 +0200 Subject: [PATCH 09/33] Avoid using unsafe API directly --- .../oak-incremental-index/pom.xml | 2 +- .../druid/segment/incremental/oak/OakKey.java | 70 +++++++------------ 2 files changed, 26 insertions(+), 46 deletions(-) diff --git a/extensions-contrib/oak-incremental-index/pom.xml b/extensions-contrib/oak-incremental-index/pom.xml index 11d8b64a7bc6..95d07629d3e8 100644 --- a/extensions-contrib/oak-incremental-index/pom.xml +++ b/extensions-contrib/oak-incremental-index/pom.xml @@ -37,7 +37,7 @@ com.yahoo.oak oak - 0.2.3 + 0.2.4-SNAPSHOT diff --git a/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakKey.java b/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakKey.java index 379029b6c1c7..ec19a322cb64 100644 --- a/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakKey.java +++ b/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakKey.java @@ -27,6 +27,7 @@ import com.yahoo.oak.OakScopedWriteBuffer; import com.yahoo.oak.OakSerializer; import com.yahoo.oak.OakUnsafeDirectBuffer; +import com.yahoo.oak.UnsafeUtils; import org.apache.druid.query.monomorphicprocessing.RuntimeShapeInspector; import org.apache.druid.segment.DimensionIndexer; import org.apache.druid.segment.column.ColumnCapabilities; @@ -34,10 +35,8 @@ import org.apache.druid.segment.data.IndexedInts; import org.apache.druid.segment.incremental.IncrementalIndex; import org.apache.druid.segment.incremental.IncrementalIndexRow; -import sun.misc.Unsafe; import javax.annotation.Nullable; -import java.lang.reflect.Constructor; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -92,23 +91,6 @@ public final class OakKey // Marks a null dimension static final int NULL_DIM = -1; - // Used for direct access to the key's buffer - static final Unsafe UNSAFE; - static final long INT_ARRAY_OFFSET; - - // static constructor - access and create a new instance of Unsafe - static { - try { - Constructor unsafeConstructor = Unsafe.class.getDeclaredConstructor(); - unsafeConstructor.setAccessible(true); - UNSAFE = unsafeConstructor.newInstance(); - INT_ARRAY_OFFSET = UNSAFE.arrayBaseOffset(int[].class); - } - catch (Exception ex) { - throw new RuntimeException(ex); - } - } - private OakKey() { } @@ -120,17 +102,17 @@ static long getKeyAddress(OakBuffer buffer) static long getTimestamp(long address) { - return UNSAFE.getLong(address + TIME_STAMP_OFFSET); + return UnsafeUtils.getLong(address + TIME_STAMP_OFFSET); } static int getRowIndex(long address) { - return UNSAFE.getInt(address + ROW_INDEX_OFFSET); + return UnsafeUtils.getInt(address + ROW_INDEX_OFFSET); } static int getDimsLength(long address) { - return UNSAFE.getInt(address + DIMS_LENGTH_OFFSET); + return UnsafeUtils.getInt(address + DIMS_LENGTH_OFFSET); } static int getDimOffsetInBuffer(int dimIndex) @@ -146,14 +128,14 @@ static boolean isValueTypeNull(int dimValueTypeID) static boolean isDimNull(long address, int dimIndex) { long dimAddress = address + getDimOffsetInBuffer(dimIndex); - return isValueTypeNull(UNSAFE.getInt(dimAddress + DIM_VALUE_TYPE_OFFSET)); + return isValueTypeNull(UnsafeUtils.getInt(dimAddress + DIM_VALUE_TYPE_OFFSET)); } @Nullable static Object getDim(long address, int dimIndex) { long dimAddress = address + getDimOffsetInBuffer(dimIndex); - int dimValueTypeID = UNSAFE.getInt(dimAddress + DIM_VALUE_TYPE_OFFSET); + int dimValueTypeID = UnsafeUtils.getInt(dimAddress + DIM_VALUE_TYPE_OFFSET); if (isValueTypeNull(dimValueTypeID)) { return null; @@ -161,16 +143,16 @@ static Object getDim(long address, int dimIndex) switch (VALUE_ORDINAL_TYPES[dimValueTypeID]) { case DOUBLE: - return UNSAFE.getDouble(dimAddress + DIM_DATA_OFFSET); + return UnsafeUtils.getDouble(dimAddress + DIM_DATA_OFFSET); case FLOAT: - return UNSAFE.getFloat(dimAddress + DIM_DATA_OFFSET); + return UnsafeUtils.getFloat(dimAddress + DIM_DATA_OFFSET); case LONG: - return UNSAFE.getLong(dimAddress + DIM_DATA_OFFSET); + return UnsafeUtils.getLong(dimAddress + DIM_DATA_OFFSET); case STRING: - int arrayPos = UNSAFE.getInt(dimAddress + STRING_DIM_ARRAY_POS_OFFSET); - int arraySize = UNSAFE.getInt(dimAddress + STRING_DIM_ARRAY_LENGTH_OFFSET); + int arrayPos = UnsafeUtils.getInt(dimAddress + STRING_DIM_ARRAY_POS_OFFSET); + int arraySize = UnsafeUtils.getInt(dimAddress + STRING_DIM_ARRAY_LENGTH_OFFSET); int[] array = new int[arraySize]; - UNSAFE.copyMemory(null, address + arrayPos, array, INT_ARRAY_OFFSET, ((long) arraySize) * Integer.BYTES); + UnsafeUtils.copyToArray(address + arrayPos, array, arraySize); return array; default: return null; @@ -222,8 +204,8 @@ private void init() } long dimAddress = this.dimensionsAddress + getDimOffsetInBuffer(dimIndex); - arrayAddress = dimensionsAddress + UNSAFE.getInt(dimAddress + STRING_DIM_ARRAY_POS_OFFSET); - arraySize = UNSAFE.getInt(dimAddress + STRING_DIM_ARRAY_LENGTH_OFFSET); + arrayAddress = dimensionsAddress + UnsafeUtils.getInt(dimAddress + STRING_DIM_ARRAY_POS_OFFSET); + arraySize = UnsafeUtils.getInt(dimAddress + STRING_DIM_ARRAY_LENGTH_OFFSET); initialized = true; } @@ -244,7 +226,7 @@ public int size() public int get(int index) { init(); - return UNSAFE.getInt(arrayAddress + ((long) index) * Integer.BYTES); + return UnsafeUtils.getInt(arrayAddress + ((long) index) * Integer.BYTES); } @Override @@ -290,9 +272,9 @@ public void serialize(IncrementalIndexRow incrementalIndexRow, OakScopedWriteBuf rowIndex = rowIndexGenerator.getAndIncrement(); incrementalIndexRow.setRowIndex(rowIndex); } - UNSAFE.putLong(address + TIME_STAMP_OFFSET, timestamp); - UNSAFE.putInt(address + DIMS_LENGTH_OFFSET, dimsLength); - UNSAFE.putInt(address + ROW_INDEX_OFFSET, rowIndex); + UnsafeUtils.putLong(address + TIME_STAMP_OFFSET, timestamp); + UnsafeUtils.putInt(address + DIMS_LENGTH_OFFSET, dimsLength); + UnsafeUtils.putInt(address + ROW_INDEX_OFFSET, rowIndex); long dimsAddress = address + DIMS_OFFSET; // the index for writing the int arrays of the string-dim (after all the dims' data) @@ -304,27 +286,25 @@ public void serialize(IncrementalIndexRow incrementalIndexRow, OakScopedWriteBuf boolean isDimHaveValue = dimValueType != null; int dimValueTypeID = isDimHaveValue ? dimValueType.ordinal() : NULL_DIM; - UNSAFE.putInt(dimsAddress + DIM_VALUE_TYPE_OFFSET, dimValueTypeID); + UnsafeUtils.putInt(dimsAddress + DIM_VALUE_TYPE_OFFSET, dimValueTypeID); if (isDimHaveValue) { switch (dimValueType) { case FLOAT: - UNSAFE.putFloat(dimsAddress + DIM_DATA_OFFSET, (Float) dim); + UnsafeUtils.putFloat(dimsAddress + DIM_DATA_OFFSET, (Float) dim); break; case DOUBLE: - UNSAFE.putDouble(dimsAddress + DIM_DATA_OFFSET, (Double) dim); + UnsafeUtils.putDouble(dimsAddress + DIM_DATA_OFFSET, (Double) dim); break; case LONG: - UNSAFE.putLong(dimsAddress + DIM_DATA_OFFSET, (Long) dim); + UnsafeUtils.putLong(dimsAddress + DIM_DATA_OFFSET, (Long) dim); break; case STRING: int[] arr = (int[]) dim; int length = arr.length; - UNSAFE.putInt(dimsAddress + STRING_DIM_ARRAY_POS_OFFSET, stringDimArraysPos); - UNSAFE.putInt(dimsAddress + STRING_DIM_ARRAY_LENGTH_OFFSET, length); - - int lengthBytes = length * Integer.BYTES; - UNSAFE.copyMemory(arr, INT_ARRAY_OFFSET, null, address + stringDimArraysPos, lengthBytes); + UnsafeUtils.putInt(dimsAddress + STRING_DIM_ARRAY_POS_OFFSET, stringDimArraysPos); + UnsafeUtils.putInt(dimsAddress + STRING_DIM_ARRAY_LENGTH_OFFSET, length); + long lengthBytes = UnsafeUtils.copyFromArray(arr, address + stringDimArraysPos, length); stringDimArraysPos += lengthBytes; break; } From 91d7dcd054a2232710cc6c6b3571bb4a4a525372 Mon Sep 17 00:00:00 2001 From: Liran Funaro Date: Sun, 28 Feb 2021 15:21:15 +0200 Subject: [PATCH 10/33] Use Oak-0.2.3.1 --- extensions-contrib/oak-incremental-index/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions-contrib/oak-incremental-index/pom.xml b/extensions-contrib/oak-incremental-index/pom.xml index 95d07629d3e8..f21920ddb21c 100644 --- a/extensions-contrib/oak-incremental-index/pom.xml +++ b/extensions-contrib/oak-incremental-index/pom.xml @@ -37,7 +37,7 @@ com.yahoo.oak oak - 0.2.4-SNAPSHOT + 0.2.3.1 From 93166434a85d2b96a13a1ec99b1b4d8732b10e50 Mon Sep 17 00:00:00 2001 From: Liran Funaro Date: Sun, 28 Feb 2021 17:14:52 +0200 Subject: [PATCH 11/33] Fix intellij error --- .../org/apache/druid/segment/incremental/oak/TestSuite.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/extensions-contrib/oak-incremental-index/src/test/java/org/apache/druid/segment/incremental/oak/TestSuite.java b/extensions-contrib/oak-incremental-index/src/test/java/org/apache/druid/segment/incremental/oak/TestSuite.java index 1573474fd6d1..db40688aa9fb 100644 --- a/extensions-contrib/oak-incremental-index/src/test/java/org/apache/druid/segment/incremental/oak/TestSuite.java +++ b/extensions-contrib/oak-incremental-index/src/test/java/org/apache/druid/segment/incremental/oak/TestSuite.java @@ -19,6 +19,7 @@ package org.apache.druid.segment.incremental.oak; +import org.apache.druid.segment.data.IncrementalIndexTest; import org.apache.druid.segment.incremental.IncrementalIndexAdapterTest; import org.apache.druid.segment.incremental.IncrementalIndexIngestionTest; import org.apache.druid.segment.incremental.IncrementalIndexMultiValueSpecTest; @@ -34,7 +35,7 @@ @RunWith(Suite.class) @Suite.SuiteClasses({ OakDummyInitTest.class, - org.apache.druid.segment.data.IncrementalIndexTest.class, + IncrementalIndexTest.class, org.apache.druid.segment.incremental.IncrementalIndexTest.class, IncrementalIndexStorageAdapterTest.class, IncrementalIndexRowSizeTest.class, From 8a683abee227256d8c99b406b718e7551ec7640d Mon Sep 17 00:00:00 2001 From: Liran Funaro Date: Wed, 31 Mar 2021 10:11:34 +0300 Subject: [PATCH 12/33] Fix Intellij issue --- .../java/org/apache/druid/segment/incremental/oak/OakKey.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakKey.java b/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakKey.java index ec19a322cb64..9ab026752886 100644 --- a/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakKey.java +++ b/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakKey.java @@ -305,7 +305,7 @@ public void serialize(IncrementalIndexRow incrementalIndexRow, OakScopedWriteBuf UnsafeUtils.putInt(dimsAddress + STRING_DIM_ARRAY_POS_OFFSET, stringDimArraysPos); UnsafeUtils.putInt(dimsAddress + STRING_DIM_ARRAY_LENGTH_OFFSET, length); long lengthBytes = UnsafeUtils.copyFromArray(arr, address + stringDimArraysPos, length); - stringDimArraysPos += lengthBytes; + stringDimArraysPos += (int) lengthBytes; break; } } From ddfcda7eacdda7ca76e5a22d06daaf5a9ef90dc1 Mon Sep 17 00:00:00 2001 From: Liran Funaro Date: Tue, 11 May 2021 17:47:31 +0300 Subject: [PATCH 13/33] Address PR issues --- .../incremental/oak/OakIncrementalIndex.java | 19 +++++-- .../oak/OakIncrementalIndexModule.java | 4 -- .../segment/incremental/IncrementalIndex.java | 51 +++++++++++++++++ .../incremental/OffheapIncrementalIndex.java | 2 +- .../incremental/OnheapIncrementalIndex.java | 55 ------------------- 5 files changed, 67 insertions(+), 64 deletions(-) diff --git a/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndex.java b/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndex.java index 19a537bbccbf..fe0b460bef59 100644 --- a/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndex.java +++ b/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndex.java @@ -49,7 +49,6 @@ import org.apache.druid.segment.incremental.IncrementalIndexRow; import org.apache.druid.segment.incremental.IncrementalIndexSchema; import org.apache.druid.segment.incremental.IndexSizeExceededException; -import org.apache.druid.segment.incremental.OnheapIncrementalIndex; import javax.annotation.Nullable; import java.nio.ByteBuffer; @@ -371,7 +370,7 @@ protected BufferAggregator[] initAggs(AggregatorFactory[] metrics, this.selectors.put( agg.getName(), - new OnheapIncrementalIndex.CachingColumnSelectorFactory(columnSelectorFactory, concurrentEventAdd) + new CachingColumnSelectorFactory(columnSelectorFactory, concurrentEventAdd) ); } @@ -581,8 +580,20 @@ public void clear() */ public static class Builder extends AppendableIndexBuilder { - public static final long DEFAULT_OAK_MAX_MEMORY_CAPACITY = 32L * (1L << 30); // 32 GB - public static final int DEFAULT_OAK_BLOCK_SIZE = 8 * (1 << 20); // 8 MB + // OakMap stores its data in off-heap memory blocks. Larger blocks consolidates allocations + // and reduce its overhead, but has the potential to waste more memory if the block is not fully utilized. + // The default is 8 MiB as it has a good balance between performance and memory usage in tested + // batch ingestion scenario. + public static final int DEFAULT_OAK_BLOCK_SIZE = 8 * (1 << 20); + + // OakMap has a predefined maximal capacity, that allows it to minimize the internal data-structure + // for maintaining off-heap memory blocks. + // The default is arbirtrary large number (32 GiB) that won't limit the users. + public static final long DEFAULT_OAK_MAX_MEMORY_CAPACITY = 32L * (1L << 30); + + // OakMap internal data-structure maintains its entries in small chunks. Larger chunks reduces the number of + // on-heap objects, but might incure more overhead when balancing the entries between the chunks. + // The default is 256 as it showed best performance in tested batch ingestion scenario. public static final int DEFAULT_OAK_CHUNK_MAX_ITEMS = 256; public long oakMaxMemoryCapacity = DEFAULT_OAK_MAX_MEMORY_CAPACITY; diff --git a/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndexModule.java b/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndexModule.java index 299d6b3b1a9d..0fae703859cf 100644 --- a/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndexModule.java +++ b/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndexModule.java @@ -24,15 +24,12 @@ import com.fasterxml.jackson.databind.module.SimpleModule; import com.google.inject.Binder; import org.apache.druid.initialization.DruidModule; -import org.apache.druid.java.util.common.logger.Logger; import java.util.Collections; import java.util.List; public class OakIncrementalIndexModule implements DruidModule { - private static final Logger log = new Logger(OakIncrementalIndexModule.class); - public static final Module JACKSON_MODULE = new SimpleModule("OakIncrementalIndexModule") .registerSubtypes( new NamedType(OakIncrementalIndexSpec.class, OakIncrementalIndexSpec.TYPE), @@ -42,7 +39,6 @@ public class OakIncrementalIndexModule implements DruidModule @Override public void configure(Binder binder) { - log.info("Test"); } @Override diff --git a/processing/src/main/java/org/apache/druid/segment/incremental/IncrementalIndex.java b/processing/src/main/java/org/apache/druid/segment/incremental/IncrementalIndex.java index 6170272742ce..76186cf1c5a5 100644 --- a/processing/src/main/java/org/apache/druid/segment/incremental/IncrementalIndex.java +++ b/processing/src/main/java/org/apache/druid/segment/incremental/IncrementalIndex.java @@ -1370,6 +1370,57 @@ public void clear() } } + /** + * Caches references to selector objects for each column instead of creating a new object each time in order to save + * heap space. In general the selectorFactory need not to thread-safe. If required, set concurrentEventAdd to true to + * use concurrent hash map instead of vanilla hash map for thread-safe operations. + */ + protected static class CachingColumnSelectorFactory implements ColumnSelectorFactory + { + private final Map> columnSelectorMap; + private final ColumnSelectorFactory delegate; + + public CachingColumnSelectorFactory(ColumnSelectorFactory delegate, boolean concurrentEventAdd) + { + this.delegate = delegate; + + if (concurrentEventAdd) { + columnSelectorMap = new ConcurrentHashMap<>(); + } else { + columnSelectorMap = new HashMap<>(); + } + } + + @Override + public DimensionSelector makeDimensionSelector(DimensionSpec dimensionSpec) + { + return delegate.makeDimensionSelector(dimensionSpec); + } + + @Override + public ColumnValueSelector makeColumnValueSelector(String columnName) + { + ColumnValueSelector existing = columnSelectorMap.get(columnName); + if (existing != null) { + return existing; + } + + // We cannot use columnSelectorMap.computeIfAbsent(columnName, delegate::makeColumnValueSelector) + // here since makeColumnValueSelector may modify the columnSelectorMap itself through + // virtual column references, triggering a ConcurrentModificationException in JDK 9 and above. + ColumnValueSelector columnValueSelector = delegate.makeColumnValueSelector(columnName); + existing = columnSelectorMap.putIfAbsent(columnName, columnValueSelector); + return existing != null ? existing : columnValueSelector; + } + + @Nullable + @Override + public ColumnCapabilities getColumnCapabilities(String columnName) + { + return delegate.getColumnCapabilities(columnName); + } + } + private class LongMetricColumnSelector implements LongColumnSelector { private final IncrementalIndexRowHolder currEntry; diff --git a/processing/src/main/java/org/apache/druid/segment/incremental/OffheapIncrementalIndex.java b/processing/src/main/java/org/apache/druid/segment/incremental/OffheapIncrementalIndex.java index 3ca9b2716354..815b6cebd848 100644 --- a/processing/src/main/java/org/apache/druid/segment/incremental/OffheapIncrementalIndex.java +++ b/processing/src/main/java/org/apache/druid/segment/incremental/OffheapIncrementalIndex.java @@ -129,7 +129,7 @@ protected BufferAggregator[] initAggs( selectors.put( agg.getName(), - new OnheapIncrementalIndex.CachingColumnSelectorFactory(columnSelectorFactory, concurrentEventAdd) + new CachingColumnSelectorFactory(columnSelectorFactory, concurrentEventAdd) ); aggOffsetInBuffer[i] = aggsCurOffsetInBuffer; diff --git a/processing/src/main/java/org/apache/druid/segment/incremental/OnheapIncrementalIndex.java b/processing/src/main/java/org/apache/druid/segment/incremental/OnheapIncrementalIndex.java index ab975836265e..0249581abd1c 100644 --- a/processing/src/main/java/org/apache/druid/segment/incremental/OnheapIncrementalIndex.java +++ b/processing/src/main/java/org/apache/druid/segment/incremental/OnheapIncrementalIndex.java @@ -27,11 +27,7 @@ import org.apache.druid.java.util.common.parsers.ParseException; import org.apache.druid.query.aggregation.Aggregator; import org.apache.druid.query.aggregation.AggregatorFactory; -import org.apache.druid.query.dimension.DimensionSpec; import org.apache.druid.segment.ColumnSelectorFactory; -import org.apache.druid.segment.ColumnValueSelector; -import org.apache.druid.segment.DimensionSelector; -import org.apache.druid.segment.column.ColumnCapabilities; import org.apache.druid.utils.JvmUtils; import javax.annotation.Nullable; @@ -385,57 +381,6 @@ public void close() } } - /** - * Caches references to selector objects for each column instead of creating a new object each time in order to save - * heap space. In general the selectorFactory need not to thread-safe. If required, set concurrentEventAdd to true to - * use concurrent hash map instead of vanilla hash map for thread-safe operations. - */ - public static class CachingColumnSelectorFactory implements ColumnSelectorFactory - { - private final Map> columnSelectorMap; - private final ColumnSelectorFactory delegate; - - public CachingColumnSelectorFactory(ColumnSelectorFactory delegate, boolean concurrentEventAdd) - { - this.delegate = delegate; - - if (concurrentEventAdd) { - columnSelectorMap = new ConcurrentHashMap<>(); - } else { - columnSelectorMap = new HashMap<>(); - } - } - - @Override - public DimensionSelector makeDimensionSelector(DimensionSpec dimensionSpec) - { - return delegate.makeDimensionSelector(dimensionSpec); - } - - @Override - public ColumnValueSelector makeColumnValueSelector(String columnName) - { - ColumnValueSelector existing = columnSelectorMap.get(columnName); - if (existing != null) { - return existing; - } - - // We cannot use columnSelectorMap.computeIfAbsent(columnName, delegate::makeColumnValueSelector) - // here since makeColumnValueSelector may modify the columnSelectorMap itself through - // virtual column references, triggering a ConcurrentModificationException in JDK 9 and above. - ColumnValueSelector columnValueSelector = delegate.makeColumnValueSelector(columnName); - existing = columnSelectorMap.putIfAbsent(columnName, columnValueSelector); - return existing != null ? existing : columnValueSelector; - } - - @Nullable - @Override - public ColumnCapabilities getColumnCapabilities(String columnName) - { - return delegate.getColumnCapabilities(columnName); - } - } - public static class Builder extends AppendableIndexBuilder { @Override From 36a67cbe740e6fa15f3a179f4a02aa027e5a3513 Mon Sep 17 00:00:00 2001 From: Liran Funaro Date: Thu, 28 Oct 2021 15:22:23 +0300 Subject: [PATCH 14/33] Updates following the merge: * Remove "offheap" parameter from benchmarks * Fix bug in IncrementalIndexIngestionTest * Generify `iterableWithPostAggregations` * Move common logic to IncrementalIndex --- .../FilteredAggregatorBenchmark.java | 4 +- .../IncrementalIndexReadBenchmark.java | 2 +- .../indexing/IndexIngestionBenchmark.java | 2 +- .../indexing/IndexPersistBenchmark.java | 2 +- .../benchmark/query/GroupByBenchmark.java | 2 +- .../druid/benchmark/query/ScanBenchmark.java | 2 +- .../benchmark/query/SearchBenchmark.java | 2 +- .../benchmark/query/TimeseriesBenchmark.java | 2 +- .../druid/benchmark/query/TopNBenchmark.java | 2 +- .../oak-incremental-index/pom.xml | 2 +- .../incremental/oak/OakIncrementalIndex.java | 221 ++++-------------- .../oak/OakIncrementalIndexModule.java | 3 +- .../oak/OffheapIncrementalIndexSpec.java | 97 -------- .../segment/incremental/IncrementalIndex.java | 179 +++++++------- .../incremental/OnheapIncrementalIndex.java | 193 +++++++-------- .../IncrementalIndexIngestionTest.java | 1 + .../OnheapIncrementalIndexBenchmark.java | 9 +- 17 files changed, 244 insertions(+), 481 deletions(-) delete mode 100644 extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OffheapIncrementalIndexSpec.java diff --git a/benchmarks/src/test/java/org/apache/druid/benchmark/FilteredAggregatorBenchmark.java b/benchmarks/src/test/java/org/apache/druid/benchmark/FilteredAggregatorBenchmark.java index 771039008839..07c59a9c719a 100644 --- a/benchmarks/src/test/java/org/apache/druid/benchmark/FilteredAggregatorBenchmark.java +++ b/benchmarks/src/test/java/org/apache/druid/benchmark/FilteredAggregatorBenchmark.java @@ -207,7 +207,7 @@ public void setup() @State(Scope.Benchmark) public static class IncrementalIndexState { - @Param({"onheap", "offheap", "oak"}) + @Param({"onheap", "oak"}) private String indexType; IncrementalIndex incIndex; @@ -237,7 +237,7 @@ public void tearDown() @State(Scope.Benchmark) public static class IncrementalIndexIngestState { - @Param({"onheap", "offheap", "oak"}) + @Param({"onheap", "oak"}) private String indexType; IncrementalIndex incIndex; diff --git a/benchmarks/src/test/java/org/apache/druid/benchmark/indexing/IncrementalIndexReadBenchmark.java b/benchmarks/src/test/java/org/apache/druid/benchmark/indexing/IncrementalIndexReadBenchmark.java index 229815a00def..6ff6cbc85f80 100644 --- a/benchmarks/src/test/java/org/apache/druid/benchmark/indexing/IncrementalIndexReadBenchmark.java +++ b/benchmarks/src/test/java/org/apache/druid/benchmark/indexing/IncrementalIndexReadBenchmark.java @@ -85,7 +85,7 @@ public class IncrementalIndexReadBenchmark @Param({"true", "false"}) private boolean rollup; - @Param({"onheap", "offheap", "oak"}) + @Param({"onheap", "oak"}) private String indexType; private static final Logger log = new Logger(IncrementalIndexReadBenchmark.class); diff --git a/benchmarks/src/test/java/org/apache/druid/benchmark/indexing/IndexIngestionBenchmark.java b/benchmarks/src/test/java/org/apache/druid/benchmark/indexing/IndexIngestionBenchmark.java index 4a6b588548b2..f5fe8f5e6915 100644 --- a/benchmarks/src/test/java/org/apache/druid/benchmark/indexing/IndexIngestionBenchmark.java +++ b/benchmarks/src/test/java/org/apache/druid/benchmark/indexing/IndexIngestionBenchmark.java @@ -69,7 +69,7 @@ public class IndexIngestionBenchmark @Param({"none", "moderate", "high"}) private String rollupOpportunity; - @Param({"onheap", "offheap", "oak"}) + @Param({"onheap", "oak"}) private String indexType; private static final Logger log = new Logger(IndexIngestionBenchmark.class); diff --git a/benchmarks/src/test/java/org/apache/druid/benchmark/indexing/IndexPersistBenchmark.java b/benchmarks/src/test/java/org/apache/druid/benchmark/indexing/IndexPersistBenchmark.java index 651cc48e0915..d980e32401d3 100644 --- a/benchmarks/src/test/java/org/apache/druid/benchmark/indexing/IndexPersistBenchmark.java +++ b/benchmarks/src/test/java/org/apache/druid/benchmark/indexing/IndexPersistBenchmark.java @@ -96,7 +96,7 @@ public class IndexPersistBenchmark @Param({"none", "moderate", "high"}) private String rollupOpportunity; - @Param({"onheap", "offheap", "oak"}) + @Param({"onheap", "oak"}) private String indexType; private AppendableIndexSpec appendableIndexSpec; diff --git a/benchmarks/src/test/java/org/apache/druid/benchmark/query/GroupByBenchmark.java b/benchmarks/src/test/java/org/apache/druid/benchmark/query/GroupByBenchmark.java index 2e6d83b38e24..87577377a9d6 100644 --- a/benchmarks/src/test/java/org/apache/druid/benchmark/query/GroupByBenchmark.java +++ b/benchmarks/src/test/java/org/apache/druid/benchmark/query/GroupByBenchmark.java @@ -535,7 +535,7 @@ public String getFormatString() @State(Scope.Benchmark) public static class IncrementalIndexState { - @Param({"onheap", "offheap", "oak"}) + @Param({"onheap", "oak"}) private String indexType; IncrementalIndex incIndex; diff --git a/benchmarks/src/test/java/org/apache/druid/benchmark/query/ScanBenchmark.java b/benchmarks/src/test/java/org/apache/druid/benchmark/query/ScanBenchmark.java index d8392e028209..5fff6081b258 100644 --- a/benchmarks/src/test/java/org/apache/druid/benchmark/query/ScanBenchmark.java +++ b/benchmarks/src/test/java/org/apache/druid/benchmark/query/ScanBenchmark.java @@ -284,7 +284,7 @@ public void setup() @State(Scope.Benchmark) public static class IncrementalIndexState { - @Param({"onheap", "offheap", "oak"}) + @Param({"onheap", "oak"}) private String indexType; IncrementalIndex incIndex; diff --git a/benchmarks/src/test/java/org/apache/druid/benchmark/query/SearchBenchmark.java b/benchmarks/src/test/java/org/apache/druid/benchmark/query/SearchBenchmark.java index 88d77d304082..b09996f2b777 100644 --- a/benchmarks/src/test/java/org/apache/druid/benchmark/query/SearchBenchmark.java +++ b/benchmarks/src/test/java/org/apache/druid/benchmark/query/SearchBenchmark.java @@ -354,7 +354,7 @@ public void setup() @State(Scope.Benchmark) public static class IncrementalIndexState { - @Param({"onheap", "offheap", "oak"}) + @Param({"onheap", "oak"}) private String indexType; IncrementalIndex incIndex; diff --git a/benchmarks/src/test/java/org/apache/druid/benchmark/query/TimeseriesBenchmark.java b/benchmarks/src/test/java/org/apache/druid/benchmark/query/TimeseriesBenchmark.java index 4c5421a1b650..7b22303e9116 100644 --- a/benchmarks/src/test/java/org/apache/druid/benchmark/query/TimeseriesBenchmark.java +++ b/benchmarks/src/test/java/org/apache/druid/benchmark/query/TimeseriesBenchmark.java @@ -279,7 +279,7 @@ public void setup() @State(Scope.Benchmark) public static class IncrementalIndexState { - @Param({"onheap", "offheap", "oak"}) + @Param({"onheap", "oak"}) private String indexType; IncrementalIndex incIndex; diff --git a/benchmarks/src/test/java/org/apache/druid/benchmark/query/TopNBenchmark.java b/benchmarks/src/test/java/org/apache/druid/benchmark/query/TopNBenchmark.java index e7d70be0f516..a6a08919f9d5 100644 --- a/benchmarks/src/test/java/org/apache/druid/benchmark/query/TopNBenchmark.java +++ b/benchmarks/src/test/java/org/apache/druid/benchmark/query/TopNBenchmark.java @@ -257,7 +257,7 @@ public void setup() @State(Scope.Benchmark) public static class IncrementalIndexState { - @Param({"onheap", "offheap", "oak"}) + @Param({"onheap", "oak"}) private String indexType; IncrementalIndex incIndex; diff --git a/extensions-contrib/oak-incremental-index/pom.xml b/extensions-contrib/oak-incremental-index/pom.xml index f21920ddb21c..a43e19400b4f 100644 --- a/extensions-contrib/oak-incremental-index/pom.xml +++ b/extensions-contrib/oak-incremental-index/pom.xml @@ -29,7 +29,7 @@ org.apache.druid druid - 0.22.0-SNAPSHOT + 0.23.0-SNAPSHOT ../../pom.xml diff --git a/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndex.java b/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndex.java index fe0b460bef59..cbb6844b2999 100644 --- a/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndex.java +++ b/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndex.java @@ -22,7 +22,6 @@ import com.google.common.base.Supplier; import com.google.common.collect.Iterators; -import com.google.common.collect.Maps; import com.yahoo.oak.OakBuffer; import com.yahoo.oak.OakMap; import com.yahoo.oak.OakMapBuilder; @@ -33,17 +32,12 @@ import com.yahoo.oak.OakUnscopedBuffer; import org.apache.druid.annotations.EverythingIsNonnullByDefault; import org.apache.druid.data.input.InputRow; -import org.apache.druid.data.input.MapBasedRow; import org.apache.druid.data.input.Row; -import org.apache.druid.java.util.common.StringUtils; import org.apache.druid.java.util.common.logger.Logger; import org.apache.druid.java.util.common.parsers.ParseException; import org.apache.druid.query.aggregation.AggregatorFactory; import org.apache.druid.query.aggregation.BufferAggregator; import org.apache.druid.query.aggregation.PostAggregator; -import org.apache.druid.segment.ColumnSelectorFactory; -import org.apache.druid.segment.DimensionHandler; -import org.apache.druid.segment.DimensionIndexer; import org.apache.druid.segment.incremental.AppendableIndexBuilder; import org.apache.druid.segment.incremental.IncrementalIndex; import org.apache.druid.segment.incremental.IncrementalIndexRow; @@ -52,12 +46,10 @@ import javax.annotation.Nullable; import java.nio.ByteBuffer; -import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; @@ -70,29 +62,18 @@ * and performance overhead. */ @EverythingIsNonnullByDefault -public class OakIncrementalIndex extends IncrementalIndex implements IncrementalIndex.FactsHolder +public class OakIncrementalIndex extends IncrementalIndex implements IncrementalIndex.FactsHolder { - protected final int maxRowCount; - protected final long maxBytesInMemory; - private final boolean rollup; - + private final BufferAggregator[] aggregators; private final OakMap facts; - private final AtomicInteger rowIndexGenerator; - - @Nullable - private Map selectors; // Given a ByteBuffer and an offset inside the buffer, offset + aggOffsetInBuffer[i] // would give a position in the buffer where the i^th aggregator's value is stored. - @Nullable - private int[] aggregatorOffsetInBuffer; - private int aggregatorsTotalSize; + private final int[] aggregatorOffsetInBuffer; + private final int aggregatorsTotalSize; private static final Logger log = new Logger(OakIncrementalIndex.class); - @Nullable - private String outOfRowsReason = null; - public OakIncrementalIndex(IncrementalIndexSchema incrementalIndexSchema, boolean deserializeComplexMetrics, boolean concurrentEventAdd, @@ -102,16 +83,19 @@ public OakIncrementalIndex(IncrementalIndexSchema incrementalIndexSchema, int oakBlockSize, int oakChunkMaxItems) { - super(incrementalIndexSchema, deserializeComplexMetrics, concurrentEventAdd); + super(incrementalIndexSchema, deserializeComplexMetrics, concurrentEventAdd, maxRowCount, maxBytesInMemory); - assert selectors != null; - assert aggregatorOffsetInBuffer != null; + AggregatorFactory[] metrics = getMetricAggs(); + this.aggregators = new BufferAggregator[metrics.length]; - this.maxRowCount = maxRowCount; - this.maxBytesInMemory = maxBytesInMemory <= 0 ? Long.MAX_VALUE : maxBytesInMemory; + this.aggregatorOffsetInBuffer = new int[metrics.length]; - this.rowIndexGenerator = new AtomicInteger(0); - this.rollup = incrementalIndexSchema.isRollup(); + int curAggOffset = 0; + for (int i = 0; i < metrics.length; i++) { + aggregatorOffsetInBuffer[i] = curAggOffset; + curAggOffset += metrics[i].getMaxIntermediateSizeWithNulls(); + } + this.aggregatorsTotalSize = curAggOffset; final IncrementalIndexRow minRow = new IncrementalIndexRow( incrementalIndexSchema.getMinTimestamp(), @@ -121,8 +105,8 @@ public OakIncrementalIndex(IncrementalIndexSchema incrementalIndexSchema, ); this.facts = new OakMapBuilder<>( - new OakKey.Comparator(dimensionDescsList, this.rollup), - new OakKey.Serializer(dimensionDescsList, this.rowIndexGenerator), + new OakKey.Comparator(dimensionDescsList, isRollup()), + new OakKey.Serializer(dimensionDescsList, indexIncrement), new OakValueSerializer(), minRow ).setPreferredBlockSize(oakBlockSize) @@ -138,31 +122,15 @@ public FactsHolder getFacts() } @Override - public boolean canAppendRow() - { - final boolean countCheck = getNumEntries().get() < maxRowCount; - // if maxBytesInMemory = -1, then ignore sizeCheck - final boolean sizeCheck = maxBytesInMemory <= 0 || getBytesInMemory().get() < maxBytesInMemory; - final boolean canAdd = countCheck && sizeCheck; - if (!countCheck && !sizeCheck) { - outOfRowsReason = StringUtils.format( - "Maximum number of rows [%d] and maximum size in bytes [%d] reached", - maxRowCount, - maxBytesInMemory - ); - } else if (!countCheck) { - outOfRowsReason = StringUtils.format("Maximum number of rows [%d] reached", maxRowCount); - } else if (!sizeCheck) { - outOfRowsReason = StringUtils.format("Maximum size in bytes [%d] reached", maxBytesInMemory); - } - - return canAdd; + public int size() + { + return facts.size(); } @Override - public String getOutOfRowsReason() + public long getBytesInMemory() { - return outOfRowsReason; + return facts.memorySize(); } @Override @@ -170,16 +138,12 @@ public void close() { super.close(); - for (BufferAggregator agg : getAggs()) { + for (BufferAggregator agg : aggregators) { if (agg != null) { agg.close(); } } - if (selectors != null) { - selectors.clear(); - } - clear(); } @@ -193,59 +157,33 @@ protected AddToFactsResult addToFacts(InputRow row, if (!skipMaxRowsInMemoryCheck) { // We validate here as a sanity check that we did not exceed the row and memory limitations // in previous insertions. - if (getNumEntries().get() > maxRowCount || (maxBytesInMemory > 0 && getBytesInMemory().get() > maxBytesInMemory)) { + if (size() > maxRowCount || (maxBytesInMemory > 0 && getBytesInMemory() > maxBytesInMemory)) { throw new IndexSizeExceededException( "Maximum number of rows [%d out of %d] or max size in bytes [%d out of %d] reached", - getNumEntries().get(), maxRowCount, - getBytesInMemory().get(), maxBytesInMemory + size(), maxRowCount, + getBytesInMemory(), maxBytesInMemory ); } } - final OakInputRowContext ctx = new OakInputRowContext(rowContainer, row); - if (rollup) { - // In rollup mode, we let the key-serializer assign the row index. - // Upon lookup, the comparator ignores this special index value and only compares according to the key itself. - // The serializer is only called on insertion, so it will not increment the index if the key already exits. - key.setRowIndex(OakKey.Serializer.ASSIGN_ROW_INDEX_IF_ABSENT); - } else { - // In plain mode, we force a new row index. - // Upon lookup, since there is no key with this index, a new key will be inserted every time. - key.setRowIndex(rowIndexGenerator.getAndIncrement()); - } + // In rollup mode, we let the key-serializer assign the row index. + // Upon lookup, the comparator ignores this special index value and only compares according to the key itself. + // The serializer is only called on insertion, so it will not increment the index if the key already exits. + // In plain mode, we force a new row index. + // Upon lookup, since there is no key with this index, a new key will be inserted every time. + key.setRowIndex(isRollup() ? OakKey.Serializer.ASSIGN_ROW_INDEX_IF_ABSENT : indexIncrement.getAndIncrement()); // This call is different from FactsHolder.putIfAbsent() because it also handles the aggregation // in case the key already exits. + final OakInputRowContext ctx = new OakInputRowContext(rowContainer, row); facts.zc().putIfAbsentComputeIfPresent(key, ctx, buffer -> aggregate(ctx, buffer)); - - int rowCount = facts.size(); - long memorySize = facts.memorySize(); - - getNumEntries().set(rowCount); - getBytesInMemory().set(memorySize); - - return new AddToFactsResult(rowCount, memorySize, ctx.parseExceptionMessages); + return new AddToFactsResult(size(), getBytesInMemory(), ctx.parseExceptionMessages); } @Override public int getLastRowIndex() { - return rowIndexGenerator.get() - 1; - } - - @Override - protected BufferAggregator[] getAggsForRow(int rowOffset) - { - // We should never get here because we override iterableWithPostAggregations - throw new UnsupportedOperationException(); - } - - @Override - protected Object getAggVal(BufferAggregator agg, int rowOffset, int aggPosition) - { - // We should never get here because we override iterableWithPostAggregations - // This implementation does not need an additional structure to keep rowOffset - throw new UnsupportedOperationException(); + return indexIncrement.get() - 1; } private int getOffsetInBuffer(int aggOffset, int aggIndex) @@ -263,35 +201,35 @@ private int getOffsetInBuffer(OakIncrementalIndexRow oakRow, int aggIndex) protected float getMetricFloatValue(IncrementalIndexRow incrementalIndexRow, int aggIndex) { OakIncrementalIndexRow oakRow = (OakIncrementalIndexRow) incrementalIndexRow; - return getAggs()[aggIndex].getFloat(oakRow.getAggregationsBuffer(), getOffsetInBuffer(oakRow, aggIndex)); + return aggregators[aggIndex].getFloat(oakRow.getAggregationsBuffer(), getOffsetInBuffer(oakRow, aggIndex)); } @Override protected long getMetricLongValue(IncrementalIndexRow incrementalIndexRow, int aggIndex) { OakIncrementalIndexRow oakRow = (OakIncrementalIndexRow) incrementalIndexRow; - return getAggs()[aggIndex].getLong(oakRow.getAggregationsBuffer(), getOffsetInBuffer(oakRow, aggIndex)); + return aggregators[aggIndex].getLong(oakRow.getAggregationsBuffer(), getOffsetInBuffer(oakRow, aggIndex)); } @Override protected Object getMetricObjectValue(IncrementalIndexRow incrementalIndexRow, int aggIndex) { OakIncrementalIndexRow oakRow = (OakIncrementalIndexRow) incrementalIndexRow; - return getAggs()[aggIndex].get(oakRow.getAggregationsBuffer(), getOffsetInBuffer(oakRow, aggIndex)); + return aggregators[aggIndex].get(oakRow.getAggregationsBuffer(), getOffsetInBuffer(oakRow, aggIndex)); } @Override protected double getMetricDoubleValue(IncrementalIndexRow incrementalIndexRow, int aggIndex) { OakIncrementalIndexRow oakRow = (OakIncrementalIndexRow) incrementalIndexRow; - return getAggs()[aggIndex].getDouble(oakRow.getAggregationsBuffer(), getOffsetInBuffer(oakRow, aggIndex)); + return aggregators[aggIndex].getDouble(oakRow.getAggregationsBuffer(), getOffsetInBuffer(oakRow, aggIndex)); } @Override protected boolean isNull(IncrementalIndexRow incrementalIndexRow, int aggIndex) { OakIncrementalIndexRow oakRow = (OakIncrementalIndexRow) incrementalIndexRow; - return getAggs()[aggIndex].isNull(oakRow.getAggregationsBuffer(), getOffsetInBuffer(oakRow, aggIndex)); + return aggregators[aggIndex].isNull(oakRow.getAggregationsBuffer(), getOffsetInBuffer(oakRow, aggIndex)); } @Override @@ -300,44 +238,22 @@ public Iterable iterableWithPostAggregations( final boolean descending ) { - final AggregatorFactory[] metrics = getMetricAggs(); - final BufferAggregator[] aggregators = getAggs(); - // It might be possible to rewrite this function to return a serialized row. Function, Row> transformer = entry -> { OakUnsafeDirectBuffer keyOakBuff = (OakUnsafeDirectBuffer) entry.getKey(); OakUnsafeDirectBuffer valueOakBuff = (OakUnsafeDirectBuffer) entry.getValue(); - long serializedKeyAddress = keyOakBuff.getAddress(); - - long timeStamp = OakKey.getTimestamp(serializedKeyAddress); - int dimsLength = OakKey.getDimsLength(serializedKeyAddress); - - Map theVals = Maps.newLinkedHashMap(); - for (int i = 0; i < dimsLength; ++i) { - Object dim = OakKey.getDim(serializedKeyAddress, i); - DimensionDesc dimensionDesc = dimensionDescsList.get(i); - if (dimensionDesc == null) { - continue; - } - String dimensionName = dimensionDesc.getName(); - DimensionHandler handler = dimensionDesc.getHandler(); - if (dim == null || handler.getLengthOfEncodedKeyComponent(dim) == 0) { - theVals.put(dimensionName, null); - continue; - } - final DimensionIndexer indexer = dimensionDesc.getIndexer(); - Object rowVals = indexer.convertUnsortedEncodedKeyComponentToActualList(dim); - theVals.put(dimensionName, rowVals); - } - - ByteBuffer valueBuff = valueOakBuff.getByteBuffer(); - int valueOffset = valueOakBuff.getOffset(); - for (int i = 0; i < aggregators.length; ++i) { - Object theVal = aggregators[i].get(valueBuff, valueOffset + aggregatorOffsetInBuffer[i]); - theVals.put(metrics[i].getName(), theVal); - } - - return new MapBasedRow(timeStamp, theVals); + final long serializedKeyAddress = keyOakBuff.getAddress(); + final ByteBuffer valueBuff = valueOakBuff.getByteBuffer(); + final int valueOffset = valueOakBuff.getOffset(); + + return getMapBasedRowWithPostAggregations( + OakKey.getTimestamp(serializedKeyAddress), + OakKey.getDimsLength(serializedKeyAddress), + i -> OakKey.getDim(serializedKeyAddress, i), + aggregators.length, + i -> aggregators[i].get(valueBuff, valueOffset + aggregatorOffsetInBuffer[i]), + postAggs + ); }; return () -> transformIterator(descending, transformer); @@ -345,42 +261,9 @@ public Iterable iterableWithPostAggregations( // Aggregator management: initialization and aggregation - @Override - protected BufferAggregator[] initAggs(AggregatorFactory[] metrics, - Supplier rowSupplier, - boolean deserializeComplexMetrics, - boolean concurrentEventAdd) - { - this.selectors = new HashMap<>(); - this.aggregatorOffsetInBuffer = new int[metrics.length]; - - int curAggOffset = 0; - for (int i = 0; i < metrics.length; i++) { - aggregatorOffsetInBuffer[i] = curAggOffset; - curAggOffset += metrics[i].getMaxIntermediateSizeWithNulls(); - } - this.aggregatorsTotalSize = curAggOffset; - - for (AggregatorFactory agg : metrics) { - ColumnSelectorFactory columnSelectorFactory = makeColumnSelectorFactory( - agg, - rowSupplier, - deserializeComplexMetrics - ); - - this.selectors.put( - agg.getName(), - new CachingColumnSelectorFactory(columnSelectorFactory, concurrentEventAdd) - ); - } - - return new BufferAggregator[metrics.length]; - } - public void initAggValue(OakInputRowContext ctx, ByteBuffer aggBuffer, int aggOffset) { AggregatorFactory[] metrics = getMetricAggs(); - BufferAggregator[] aggregators = getAggs(); assert selectors != null; if (aggregators.length > 0 && aggregators[aggregators.length - 1] == null) { @@ -415,8 +298,6 @@ public void aggregate(OakInputRowContext ctx, OakBuffer buffer) public void aggregate(OakInputRowContext ctx, ByteBuffer aggBuffer, int aggOffset) { - final BufferAggregator[] aggregators = getAggs(); - ctx.setRow(); for (int i = 0; i < aggregators.length; i++) { diff --git a/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndexModule.java b/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndexModule.java index 0fae703859cf..e1e1e0e27a2a 100644 --- a/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndexModule.java +++ b/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndexModule.java @@ -32,8 +32,7 @@ public class OakIncrementalIndexModule implements DruidModule { public static final Module JACKSON_MODULE = new SimpleModule("OakIncrementalIndexModule") .registerSubtypes( - new NamedType(OakIncrementalIndexSpec.class, OakIncrementalIndexSpec.TYPE), - new NamedType(OffheapIncrementalIndexSpec.class, OffheapIncrementalIndexSpec.TYPE) + new NamedType(OakIncrementalIndexSpec.class, OakIncrementalIndexSpec.TYPE) ); @Override diff --git a/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OffheapIncrementalIndexSpec.java b/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OffheapIncrementalIndexSpec.java deleted file mode 100644 index d549c5a0088d..000000000000 --- a/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OffheapIncrementalIndexSpec.java +++ /dev/null @@ -1,97 +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. - */ - -package org.apache.druid.segment.incremental.oak; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.google.common.base.Supplier; -import org.apache.druid.collections.StupidPool; -import org.apache.druid.segment.incremental.AppendableIndexBuilder; -import org.apache.druid.segment.incremental.AppendableIndexSpec; -import org.apache.druid.segment.incremental.OffheapIncrementalIndex; -import org.apache.druid.utils.JvmUtils; - -import javax.annotation.Nullable; -import java.nio.ByteBuffer; - -/** - * This extension also bundles the option to use the off-heap index for ingestion, that is not supported by default. - */ -public class OffheapIncrementalIndexSpec implements AppendableIndexSpec, Supplier -{ - public static final String TYPE = "offheap"; - static final int DEFAULT_BUFFER_SIZE = 1 << 23; - static final int DEFAULT_CACHE_SIZE = 1 << 30; - - final int bufferSize; - final int cacheSize; - - final StupidPool bufferPool; - - @JsonCreator - public OffheapIncrementalIndexSpec( - final @JsonProperty("bufferSize") @Nullable Integer bufferSize, - final @JsonProperty("cacheSize") @Nullable Integer cacheSize - ) - { - this.bufferSize = bufferSize != null && bufferSize > 0 ? bufferSize : DEFAULT_BUFFER_SIZE; - this.cacheSize = cacheSize != null && cacheSize > this.bufferSize ? cacheSize : DEFAULT_CACHE_SIZE; - this.bufferPool = new StupidPool<>( - "Off-heap incremental-index buffer pool", - this, - 0, - this.cacheSize / this.bufferSize - ); - } - - @JsonProperty - public int getBufferSize() - { - return bufferSize; - } - - @JsonProperty - public int getCacheSize() - { - return cacheSize; - } - - @Override - public ByteBuffer get() - { - return ByteBuffer.allocateDirect(bufferSize); - } - - @Override - public AppendableIndexBuilder builder() - { - return new OffheapIncrementalIndex.Builder().setBufferPool(bufferPool); - } - - @Override - public long getDefaultMaxBytesInMemory() - { - // In the realtime node, the entire JVM's direct memory is utilized for ingestion and persist operations. - // But maxBytesInMemory only refers to the active index size and not to the index being flushed to disk and the - // persist buffer. - // To account for that, we set default to 1/2 of the max jvm's direct memory. - return JvmUtils.getRuntimeInfo().getDirectMemorySizeBytes() / 2; - } -} diff --git a/processing/src/main/java/org/apache/druid/segment/incremental/IncrementalIndex.java b/processing/src/main/java/org/apache/druid/segment/incremental/IncrementalIndex.java index 2ad8b6c62d18..dcfc2d523a7d 100644 --- a/processing/src/main/java/org/apache/druid/segment/incremental/IncrementalIndex.java +++ b/processing/src/main/java/org/apache/druid/segment/incremental/IncrementalIndex.java @@ -33,6 +33,7 @@ import com.google.errorprone.annotations.concurrent.GuardedBy; import org.apache.druid.common.config.NullHandling; import org.apache.druid.data.input.InputRow; +import org.apache.druid.data.input.MapBasedRow; import org.apache.druid.data.input.Row; import org.apache.druid.data.input.impl.DimensionSchema; import org.apache.druid.data.input.impl.DimensionsSpec; @@ -40,6 +41,7 @@ import org.apache.druid.java.util.common.DateTimes; import org.apache.druid.java.util.common.IAE; import org.apache.druid.java.util.common.ISE; +import org.apache.druid.java.util.common.StringUtils; import org.apache.druid.java.util.common.granularity.Granularity; import org.apache.druid.java.util.common.parsers.ParseException; import org.apache.druid.query.aggregation.AggregatorFactory; @@ -94,7 +96,6 @@ import java.util.concurrent.ConcurrentSkipListMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicLong; import java.util.stream.Stream; public abstract class IncrementalIndex extends AbstractIndex implements Iterable, Closeable @@ -215,6 +216,9 @@ public ColumnCapabilities getColumnCapabilities(String columnName) private final long minTimestamp; private final Granularity gran; private final boolean rollup; + protected final int maxRowCount; + protected final long maxBytesInMemory; + private final List> rowTransformers; private final VirtualColumns virtualColumns; private final AggregatorFactory[] metrics; @@ -227,14 +231,19 @@ public ColumnCapabilities getColumnCapabilities(String columnName) protected final List dimensionDescsList; // dimension capabilities are provided by the indexers private final Map timeAndMetricsColumnCapabilities; - private final AtomicInteger numEntries = new AtomicInteger(); - private final AtomicLong bytesInMemory = new AtomicLong(); + protected final Map selectors; + + protected final AtomicInteger indexIncrement = new AtomicInteger(0); // This is modified on add() in a critical section. private final ThreadLocal in = new ThreadLocal<>(); private final Supplier rowSupplier = in::get; - private volatile DateTime maxIngestedEventTime; + @Nullable + private volatile DateTime maxIngestedEventTime = null; + + @Nullable + private String outOfRowsReason = null; /** @@ -252,7 +261,9 @@ public ColumnCapabilities getColumnCapabilities(String columnName) protected IncrementalIndex( final IncrementalIndexSchema incrementalIndexSchema, final boolean deserializeComplexMetrics, - final boolean concurrentEventAdd + final boolean concurrentEventAdd, + final int maxRowCount, + final long maxBytesInMemory ) { this.minTimestamp = incrementalIndexSchema.getMinTimestamp(); @@ -263,6 +274,9 @@ protected IncrementalIndex( this.rowTransformers = new CopyOnWriteArrayList<>(); this.deserializeComplexMetrics = deserializeComplexMetrics; + this.maxRowCount = maxRowCount; + this.maxBytesInMemory = maxBytesInMemory == 0 ? Long.MAX_VALUE : maxBytesInMemory; + this.timeAndMetricsColumnCapabilities = new HashMap<>(); this.metadata = new Metadata( null, @@ -272,7 +286,16 @@ protected IncrementalIndex( this.rollup ); - initAggs(metrics, rowSupplier, deserializeComplexMetrics, concurrentEventAdd); + selectors = new HashMap<>(); + for (AggregatorFactory agg : metrics) { + selectors.put( + agg.getName(), + new OnheapIncrementalIndex.CachingColumnSelectorFactory( + makeColumnSelectorFactory(agg, rowSupplier, deserializeComplexMetrics), + concurrentEventAdd + ) + ); + } this.metricDescs = Maps.newLinkedHashMap(); for (AggregatorFactory metric : metrics) { @@ -322,16 +345,32 @@ protected IncrementalIndex( public abstract FactsHolder getFacts(); - public abstract boolean canAppendRow(); + public boolean canAppendRow() + { + final boolean countCheck = size() < maxRowCount; + // if maxBytesInMemory = -1, then ignore sizeCheck + final boolean sizeCheck = maxBytesInMemory <= 0 || getBytesInMemory() < maxBytesInMemory; + final boolean canAdd = countCheck && sizeCheck; + if (!countCheck && !sizeCheck) { + outOfRowsReason = StringUtils.format( + "Maximum number of rows [%d] and maximum size in bytes [%d] reached", + maxRowCount, + maxBytesInMemory + ); + } else if (!countCheck) { + outOfRowsReason = StringUtils.format("Maximum number of rows [%d] reached", maxRowCount); + } else if (!sizeCheck) { + outOfRowsReason = StringUtils.format("Maximum size in bytes [%d] reached", maxBytesInMemory); + } - public abstract String getOutOfRowsReason(); + return canAdd; + } - protected abstract void initAggs( - AggregatorFactory[] metrics, - Supplier rowSupplier, - boolean deserializeComplexMetrics, - boolean concurrentEventAdd - ); + @Nullable + public String getOutOfRowsReason() + { + return outOfRowsReason; + } // Note: This method needs to be thread safe. protected abstract AddToFactsResult addToFacts( @@ -417,6 +456,7 @@ public boolean isRollup() @Override public void close() { + selectors.clear(); } public InputRow formatRow(InputRow row) @@ -647,34 +687,23 @@ private synchronized void updateMaxIngestedTime(DateTime eventTime) public boolean isEmpty() { - return numEntries.get() == 0; + return size() == 0; } - public int size() - { - return numEntries.get(); - } + public abstract int size(); + + public abstract long getBytesInMemory(); boolean getDeserializeComplexMetrics() { return deserializeComplexMetrics; } - protected AtomicInteger getNumEntries() - { - return numEntries; - } - AggregatorFactory[] getMetrics() { return metrics; } - public AtomicLong getBytesInMemory() - { - return bytesInMemory; - } - private long getMinTimeMillis() { return getFacts().getMinTimeMillis(); @@ -880,6 +909,47 @@ public abstract Iterable iterableWithPostAggregations( boolean descending ); + public MapBasedRow getMapBasedRowWithPostAggregations( + long timeStamp, + int dimsLength, + Function getDim, + int aggsLength, + Function getAgg, + @Nullable final List postAggs + ) + { + Map theVals = Maps.newLinkedHashMap(); + + for (int i = 0; i < dimsLength; ++i) { + Object dim = getDim.apply(i); + DimensionDesc dimensionDesc = dimensionDescsList.get(i); + if (dimensionDesc == null) { + continue; + } + String dimensionName = dimensionDesc.getName(); + DimensionHandler handler = dimensionDesc.getHandler(); + if (dim == null || handler.getLengthOfEncodedKeyComponent(dim) == 0) { + theVals.put(dimensionName, null); + continue; + } + final DimensionIndexer indexer = dimensionDesc.getIndexer(); + Object rowVals = indexer.convertUnsortedEncodedKeyComponentToActualList(dim); + theVals.put(dimensionName, rowVals); + } + + for (int i = 0; i < aggsLength; ++i) { + theVals.put(metrics[i].getName(), getAgg.apply(i)); + } + + if (postAggs != null) { + for (PostAggregator postAgg : postAggs) { + theVals.put(postAgg.getName(), postAgg.compute(theVals)); + } + } + + return new MapBasedRow(timeStamp, theVals); + } + public DateTime getMaxIngestedEventTime() { return maxIngestedEventTime; @@ -1314,57 +1384,6 @@ public void clear() } } - /** - * Caches references to selector objects for each column instead of creating a new object each time in order to save - * heap space. In general the selectorFactory need not to thread-safe. If required, set concurrentEventAdd to true to - * use concurrent hash map instead of vanilla hash map for thread-safe operations. - */ - protected static class CachingColumnSelectorFactory implements ColumnSelectorFactory - { - private final Map> columnSelectorMap; - private final ColumnSelectorFactory delegate; - - public CachingColumnSelectorFactory(ColumnSelectorFactory delegate, boolean concurrentEventAdd) - { - this.delegate = delegate; - - if (concurrentEventAdd) { - columnSelectorMap = new ConcurrentHashMap<>(); - } else { - columnSelectorMap = new HashMap<>(); - } - } - - @Override - public DimensionSelector makeDimensionSelector(DimensionSpec dimensionSpec) - { - return delegate.makeDimensionSelector(dimensionSpec); - } - - @Override - public ColumnValueSelector makeColumnValueSelector(String columnName) - { - ColumnValueSelector existing = columnSelectorMap.get(columnName); - if (existing != null) { - return existing; - } - - // We cannot use columnSelectorMap.computeIfAbsent(columnName, delegate::makeColumnValueSelector) - // here since makeColumnValueSelector may modify the columnSelectorMap itself through - // virtual column references, triggering a ConcurrentModificationException in JDK 9 and above. - ColumnValueSelector columnValueSelector = delegate.makeColumnValueSelector(columnName); - existing = columnSelectorMap.putIfAbsent(columnName, columnValueSelector); - return existing != null ? existing : columnValueSelector; - } - - @Nullable - @Override - public ColumnCapabilities getColumnCapabilities(String columnName) - { - return delegate.getColumnCapabilities(columnName); - } - } - private class LongMetricColumnSelector implements LongColumnSelector { private final IncrementalIndexRowHolder currEntry; diff --git a/processing/src/main/java/org/apache/druid/segment/incremental/OnheapIncrementalIndex.java b/processing/src/main/java/org/apache/druid/segment/incremental/OnheapIncrementalIndex.java index 2f7a3f0331dd..dd1c8e05cace 100644 --- a/processing/src/main/java/org/apache/druid/segment/incremental/OnheapIncrementalIndex.java +++ b/processing/src/main/java/org/apache/druid/segment/incremental/OnheapIncrementalIndex.java @@ -21,11 +21,8 @@ import com.google.common.base.Supplier; import com.google.common.collect.Iterators; -import com.google.common.collect.Maps; import org.apache.druid.data.input.InputRow; -import org.apache.druid.data.input.MapBasedRow; import org.apache.druid.data.input.Row; -import org.apache.druid.java.util.common.StringUtils; import org.apache.druid.java.util.common.io.Closer; import org.apache.druid.java.util.common.logger.Logger; import org.apache.druid.java.util.common.parsers.ParseException; @@ -35,8 +32,6 @@ import org.apache.druid.query.dimension.DimensionSpec; import org.apache.druid.segment.ColumnSelectorFactory; import org.apache.druid.segment.ColumnValueSelector; -import org.apache.druid.segment.DimensionHandler; -import org.apache.druid.segment.DimensionIndexer; import org.apache.druid.segment.DimensionSelector; import org.apache.druid.segment.column.ColumnCapabilities; import org.apache.druid.utils.JvmUtils; @@ -65,15 +60,10 @@ public class OnheapIncrementalIndex extends IncrementalIndex private static final int ROUGH_OVERHEAD_PER_MAP_ENTRY = Long.BYTES * 5 + Integer.BYTES; private final ConcurrentHashMap aggregators = new ConcurrentHashMap<>(); private final FactsHolder facts; - private final AtomicInteger indexIncrement = new AtomicInteger(0); private final long maxBytesPerRowForAggregators; - protected final int maxRowCount; - protected final long maxBytesInMemory; - @Nullable - private volatile Map selectors; - @Nullable - private String outOfRowsReason = null; + protected final AtomicInteger numEntries = new AtomicInteger(); + protected final AtomicLong bytesInMemory = new AtomicLong(); OnheapIncrementalIndex( IncrementalIndexSchema incrementalIndexSchema, @@ -84,9 +74,7 @@ public class OnheapIncrementalIndex extends IncrementalIndex long maxBytesInMemory ) { - super(incrementalIndexSchema, deserializeComplexMetrics, concurrentEventAdd); - this.maxRowCount = maxRowCount; - this.maxBytesInMemory = maxBytesInMemory == 0 ? Long.MAX_VALUE : maxBytesInMemory; + super(incrementalIndexSchema, deserializeComplexMetrics, concurrentEventAdd, maxRowCount, maxBytesInMemory); this.facts = incrementalIndexSchema.isRollup() ? new RollupFactsHolder(sortFacts, dimsComparator(), getDimensions()) : new PlainFactsHolder(sortFacts, dimsComparator()); maxBytesPerRowForAggregators = getMaxBytesPerRowForAggregators(incrementalIndexSchema); @@ -128,23 +116,15 @@ public FactsHolder getFacts() } @Override - protected void initAggs( - final AggregatorFactory[] metrics, - final Supplier rowSupplier, - final boolean deserializeComplexMetrics, - final boolean concurrentEventAdd - ) + public int size() { - selectors = new HashMap<>(); - for (AggregatorFactory agg : metrics) { - selectors.put( - agg.getName(), - new CachingColumnSelectorFactory( - makeColumnSelectorFactory(agg, rowSupplier, deserializeComplexMetrics), - concurrentEventAdd - ) - ); - } + return numEntries.get(); + } + + @Override + public long getBytesInMemory() + { + return bytesInMemory.get(); } @Override @@ -161,8 +141,6 @@ protected AddToFactsResult addToFacts( Aggregator[] aggs; final AggregatorFactory[] metrics = getMetrics(); - final AtomicInteger numEntries = getNumEntries(); - final AtomicLong sizeInBytes = getBytesInMemory(); if (IncrementalIndexRow.EMPTY_ROW_INDEX != priorIndex) { aggs = concurrentGet(priorIndex); doAggregate(metrics, aggs, rowContainer, row, parseExceptionMessages); @@ -175,7 +153,7 @@ protected AddToFactsResult addToFacts( concurrentSet(rowIndex, aggs); // Last ditch sanity checks - if ((numEntries.get() >= maxRowCount || sizeInBytes.get() >= maxBytesInMemory) + if ((numEntries.get() >= maxRowCount || bytesInMemory.get() >= maxBytesInMemory) && facts.getPriorIndex(key) == IncrementalIndexRow.EMPTY_ROW_INDEX && !skipMaxRowsInMemoryCheck) { throw new IndexSizeExceededException( @@ -188,7 +166,7 @@ protected AddToFactsResult addToFacts( if (IncrementalIndexRow.EMPTY_ROW_INDEX == prev) { numEntries.incrementAndGet(); long estimatedRowSize = estimateRowSizeInBytes(key, maxBytesPerRowForAggregators); - sizeInBytes.addAndGet(estimatedRowSize); + bytesInMemory.addAndGet(estimatedRowSize); } else { // We lost a race parseExceptionMessages.clear(); @@ -200,7 +178,7 @@ protected AddToFactsResult addToFacts( } } - return new AddToFactsResult(numEntries.get(), sizeInBytes.get(), parseExceptionMessages); + return new AddToFactsResult(numEntries.get(), bytesInMemory.get(), parseExceptionMessages); } /** @@ -302,36 +280,6 @@ protected void concurrentRemove(int offset) aggregators.remove(offset); } - @Override - public boolean canAppendRow() - { - final boolean countCheck = size() < maxRowCount; - // if maxBytesInMemory = -1, then ignore sizeCheck - final boolean sizeCheck = maxBytesInMemory <= 0 || getBytesInMemory().get() < maxBytesInMemory; - final boolean canAdd = countCheck && sizeCheck; - if (!countCheck && !sizeCheck) { - outOfRowsReason = StringUtils.format( - "Maximum number of rows [%d] and maximum size in bytes [%d] reached", - maxRowCount, - maxBytesInMemory - ); - } else { - if (!countCheck) { - outOfRowsReason = StringUtils.format("Maximum number of rows [%d] reached", maxRowCount); - } else if (!sizeCheck) { - outOfRowsReason = StringUtils.format("Maximum size in bytes [%d] reached", maxBytesInMemory); - } - } - - return canAdd; - } - - @Override - public String getOutOfRowsReason() - { - return outOfRowsReason; - } - protected Aggregator[] getAggsForRow(int rowOffset) { return concurrentGet(rowOffset); @@ -373,53 +321,20 @@ public Iterable iterableWithPostAggregations( final boolean descending ) { - final AggregatorFactory[] metrics = getMetricAggs(); - - { - return () -> { - final List dimensions = getDimensions(); - - return Iterators.transform( - getFacts().iterator(descending), - incrementalIndexRow -> { - final int rowOffset = incrementalIndexRow.getRowIndex(); - - Object[] theDims = incrementalIndexRow.getDims(); - - Map theVals = Maps.newLinkedHashMap(); - for (int i = 0; i < theDims.length; ++i) { - Object dim = theDims[i]; - DimensionDesc dimensionDesc = dimensions.get(i); - if (dimensionDesc == null) { - continue; - } - String dimensionName = dimensionDesc.getName(); - DimensionHandler handler = dimensionDesc.getHandler(); - if (dim == null || handler.getLengthOfEncodedKeyComponent(dim) == 0) { - theVals.put(dimensionName, null); - continue; - } - final DimensionIndexer indexer = dimensionDesc.getIndexer(); - Object rowVals = indexer.convertUnsortedEncodedKeyComponentToActualList(dim); - theVals.put(dimensionName, rowVals); - } - - Aggregator[] aggs = getAggsForRow(rowOffset); - for (int i = 0; i < aggs.length; ++i) { - theVals.put(metrics[i].getName(), aggs[i].get()); - } - - if (postAggs != null) { - for (PostAggregator postAgg : postAggs) { - theVals.put(postAgg.getName(), postAgg.compute(theVals)); - } - } - - return new MapBasedRow(incrementalIndexRow.getTimestamp(), theVals); - } - ); - }; - } + return () -> Iterators.transform( + facts.iterator(descending), + incrementalIndexRow -> { + Aggregator[] aggs = getAggsForRow(incrementalIndexRow.getRowIndex()); + return getMapBasedRowWithPostAggregations( + incrementalIndexRow.getTimestamp(), + incrementalIndexRow.getDimsLength(), + incrementalIndexRow::getDim, + aggs.length, + i -> aggs[i].get(), + postAggs + ); + } + ); } /** @@ -433,8 +348,56 @@ public void close() closeAggregators(); aggregators.clear(); facts.clear(); - if (selectors != null) { - selectors.clear(); + } + + /** + * Caches references to selector objects for each column instead of creating a new object each time in order to save + * heap space. In general the selectorFactory need not to thread-safe. If required, set concurrentEventAdd to true to + * use concurrent hash map instead of vanilla hash map for thread-safe operations. + */ + static class CachingColumnSelectorFactory implements ColumnSelectorFactory + { + private final Map> columnSelectorMap; + private final ColumnSelectorFactory delegate; + + public CachingColumnSelectorFactory(ColumnSelectorFactory delegate, boolean concurrentEventAdd) + { + this.delegate = delegate; + + if (concurrentEventAdd) { + columnSelectorMap = new ConcurrentHashMap<>(); + } else { + columnSelectorMap = new HashMap<>(); + } + } + + @Override + public DimensionSelector makeDimensionSelector(DimensionSpec dimensionSpec) + { + return delegate.makeDimensionSelector(dimensionSpec); + } + + @Override + public ColumnValueSelector makeColumnValueSelector(String columnName) + { + ColumnValueSelector existing = columnSelectorMap.get(columnName); + if (existing != null) { + return existing; + } + + // We cannot use columnSelectorMap.computeIfAbsent(columnName, delegate::makeColumnValueSelector) + // here since makeColumnValueSelector may modify the columnSelectorMap itself through + // virtual column references, triggering a ConcurrentModificationException in JDK 9 and above. + ColumnValueSelector columnValueSelector = delegate.makeColumnValueSelector(columnName); + existing = columnSelectorMap.putIfAbsent(columnName, columnValueSelector); + return existing != null ? existing : columnValueSelector; + } + + @Nullable + @Override + public ColumnCapabilities getColumnCapabilities(String columnName) + { + return delegate.getColumnCapabilities(columnName); } } diff --git a/processing/src/test/java/org/apache/druid/segment/incremental/IncrementalIndexIngestionTest.java b/processing/src/test/java/org/apache/druid/segment/incremental/IncrementalIndexIngestionTest.java index 1f5dc5121932..9d846b63d385 100644 --- a/processing/src/test/java/org/apache/druid/segment/incremental/IncrementalIndexIngestionTest.java +++ b/processing/src/test/java/org/apache/druid/segment/incremental/IncrementalIndexIngestionTest.java @@ -127,6 +127,7 @@ public void run() addThreads[i].join(); } checkThread.interrupt(); + checkThread.join(); Assert.assertEquals(0, checkFailedCount.get()); } diff --git a/processing/src/test/java/org/apache/druid/segment/incremental/OnheapIncrementalIndexBenchmark.java b/processing/src/test/java/org/apache/druid/segment/incremental/OnheapIncrementalIndexBenchmark.java index 7b7783917d37..a1e927ab7e86 100644 --- a/processing/src/test/java/org/apache/druid/segment/incremental/OnheapIncrementalIndexBenchmark.java +++ b/processing/src/test/java/org/apache/druid/segment/incremental/OnheapIncrementalIndexBenchmark.java @@ -71,7 +71,6 @@ import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicLong; /** * Extending AbstractBenchmark means only runs if explicitly called @@ -176,8 +175,6 @@ protected AddToFactsResult addToFacts( Aggregator[] aggs; final AggregatorFactory[] metrics = getMetrics(); - final AtomicInteger numEntries = getNumEntries(); - final AtomicLong sizeInBytes = getBytesInMemory(); if (null != priorIdex) { aggs = indexedMap.get(priorIdex); } else { @@ -197,14 +194,14 @@ protected AddToFactsResult addToFacts( // Last ditch sanity checks - if ((numEntries.get() >= maxRowCount || sizeInBytes.get() >= maxBytesInMemory) + if ((numEntries.get() >= maxRowCount || bytesInMemory.get() >= maxBytesInMemory) && getFacts().getPriorIndex(key) == IncrementalIndexRow.EMPTY_ROW_INDEX) { throw new IndexSizeExceededException("Maximum number of rows or max bytes reached"); } final int prev = getFacts().putIfAbsent(key, rowIndex); if (IncrementalIndexRow.EMPTY_ROW_INDEX == prev) { numEntries.incrementAndGet(); - sizeInBytes.incrementAndGet(); + bytesInMemory.incrementAndGet(); } else { // We lost a race aggs = indexedMap.get(prev); @@ -224,7 +221,7 @@ && getFacts().getPriorIndex(key) == IncrementalIndexRow.EMPTY_ROW_INDEX) { rowContainer.set(null); - return new AddToFactsResult(numEntries.get(), sizeInBytes.get(), new ArrayList<>()); + return new AddToFactsResult(numEntries.get(), bytesInMemory.get(), new ArrayList<>()); } @Override From f2efed01b9b29846db051942ba57f90695eb8fdc Mon Sep 17 00:00:00 2001 From: Liran Funaro Date: Thu, 28 Oct 2021 16:05:08 +0300 Subject: [PATCH 15/33] Bug fix --- .../java/org/apache/druid/segment/realtime/plumber/Sink.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/main/java/org/apache/druid/segment/realtime/plumber/Sink.java b/server/src/main/java/org/apache/druid/segment/realtime/plumber/Sink.java index ccf5e4c5421b..29cddf275589 100644 --- a/server/src/main/java/org/apache/druid/segment/realtime/plumber/Sink.java +++ b/server/src/main/java/org/apache/druid/segment/realtime/plumber/Sink.java @@ -279,7 +279,7 @@ public long getBytesInMemory() return 0; } - return currHydrant.getIndex().getBytesInMemory().get(); + return currHydrant.getIndex().getBytesInMemory(); } } From f90b3332c7074229291e0e0912eea4fe650a7679 Mon Sep 17 00:00:00 2001 From: Liran Funaro Date: Wed, 5 Jan 2022 15:27:30 +0200 Subject: [PATCH 16/33] Fix merge issue --- .../org/apache/druid/segment/incremental/IncrementalIndex.java | 1 + 1 file changed, 1 insertion(+) diff --git a/processing/src/main/java/org/apache/druid/segment/incremental/IncrementalIndex.java b/processing/src/main/java/org/apache/druid/segment/incremental/IncrementalIndex.java index b0d8785b81b0..35632bdfb11b 100644 --- a/processing/src/main/java/org/apache/druid/segment/incremental/IncrementalIndex.java +++ b/processing/src/main/java/org/apache/druid/segment/incremental/IncrementalIndex.java @@ -34,6 +34,7 @@ import org.apache.druid.common.config.NullHandling; import org.apache.druid.data.input.InputRow; import org.apache.druid.data.input.MapBasedInputRow; +import org.apache.druid.data.input.MapBasedRow; import org.apache.druid.data.input.Row; import org.apache.druid.data.input.impl.DimensionSchema; import org.apache.druid.data.input.impl.DimensionsSpec; From f9af2aa553d32d7ecb24a944c56df04f5b48439c Mon Sep 17 00:00:00 2001 From: Liran Funaro Date: Wed, 5 Jan 2022 16:50:47 +0200 Subject: [PATCH 17/33] Refactor iterableWithPostAggregations() --- .../incremental/oak/OakIncrementalIndex.java | 35 +++++++++---------- .../segment/incremental/IncrementalIndex.java | 25 +++++++------ .../incremental/OnheapIncrementalIndex.java | 10 +++--- 3 files changed, 35 insertions(+), 35 deletions(-) diff --git a/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndex.java b/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndex.java index cbb6844b2999..ac4fb4bca85d 100644 --- a/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndex.java +++ b/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndex.java @@ -51,6 +51,7 @@ import java.util.Map; import java.util.Objects; import java.util.function.Function; +import java.util.stream.IntStream; /** @@ -232,31 +233,28 @@ protected boolean isNull(IncrementalIndexRow incrementalIndexRow, int aggIndex) return aggregators[aggIndex].isNull(oakRow.getAggregationsBuffer(), getOffsetInBuffer(oakRow, aggIndex)); } + private OakIncrementalIndexRow rowFromMapEntry(Map.Entry entry) { + return new OakIncrementalIndexRow( + entry.getKey(), dimensionDescsList, entry.getValue() + ); + } + @Override public Iterable iterableWithPostAggregations( @Nullable final List postAggs, final boolean descending ) { - // It might be possible to rewrite this function to return a serialized row. - Function, Row> transformer = entry -> { - OakUnsafeDirectBuffer keyOakBuff = (OakUnsafeDirectBuffer) entry.getKey(); - OakUnsafeDirectBuffer valueOakBuff = (OakUnsafeDirectBuffer) entry.getValue(); - final long serializedKeyAddress = keyOakBuff.getAddress(); - final ByteBuffer valueBuff = valueOakBuff.getByteBuffer(); - final int valueOffset = valueOakBuff.getOffset(); - + return () -> transformIterator(descending, entry -> { + OakIncrementalIndexRow row = rowFromMapEntry(entry); return getMapBasedRowWithPostAggregations( - OakKey.getTimestamp(serializedKeyAddress), - OakKey.getDimsLength(serializedKeyAddress), - i -> OakKey.getDim(serializedKeyAddress, i), - aggregators.length, - i -> aggregators[i].get(valueBuff, valueOffset + aggregatorOffsetInBuffer[i]), + row, + IntStream.range(0, aggregators.length).mapToObj( + i -> getMetricObjectValue(row, i) + ), postAggs ); - }; - - return () -> transformIterator(descending, transformer); + }); } // Aggregator management: initialization and aggregation @@ -360,8 +358,7 @@ public Iterator transformIterator( private Iterator transformNonStreamIterator( Iterator> iterator) { - return Iterators.transform(iterator, entry -> - new OakIncrementalIndexRow(entry.getKey(), dimensionDescsList, entry.getValue())); + return Iterators.transform(iterator, this::rowFromMapEntry); } /** @@ -376,7 +373,7 @@ private Iterator transformStreamIterator( return Iterators.transform(iterator, entry -> { if (rowHolder[0] == null) { - rowHolder[0] = new OakIncrementalIndexRow(entry.getKey(), dimensionDescsList, entry.getValue()); + rowHolder[0] = rowFromMapEntry(entry); } else { rowHolder[0].reset(); } diff --git a/processing/src/main/java/org/apache/druid/segment/incremental/IncrementalIndex.java b/processing/src/main/java/org/apache/druid/segment/incremental/IncrementalIndex.java index 35632bdfb11b..32e53d762785 100644 --- a/processing/src/main/java/org/apache/druid/segment/incremental/IncrementalIndex.java +++ b/processing/src/main/java/org/apache/druid/segment/incremental/IncrementalIndex.java @@ -942,19 +942,23 @@ public abstract Iterable iterableWithPostAggregations( boolean descending ); + /** + * Apply post aggregation on a row. + * @param row the row to apply on + * @param values a stream of the row's values + * @param postAggs the post aggregators + * @return a new row + */ public MapBasedRow getMapBasedRowWithPostAggregations( - long timeStamp, - int dimsLength, - Function getDim, - int aggsLength, - Function getAgg, + IncrementalIndexRow row, + Stream values, @Nullable final List postAggs ) { Map theVals = Maps.newLinkedHashMap(); - for (int i = 0; i < dimsLength; ++i) { - Object dim = getDim.apply(i); + for (int i = 0; i < row.getDimsLength(); ++i) { + Object dim = row.getDim(i); DimensionDesc dimensionDesc = dimensionDescsList.get(i); if (dimensionDesc == null) { continue; @@ -970,8 +974,9 @@ public MapBasedRow getMapBasedRowWithPostAggregations( theVals.put(dimensionName, rowVals); } - for (int i = 0; i < aggsLength; ++i) { - theVals.put(metrics[i].getName(), getAgg.apply(i)); + int i = 0; + for (Iterator it = values.iterator(); it.hasNext() && i < metrics.length; i++) { + theVals.put(metrics[i].getName(), it.next()); } if (postAggs != null) { @@ -980,7 +985,7 @@ public MapBasedRow getMapBasedRowWithPostAggregations( } } - return new MapBasedRow(timeStamp, theVals); + return new MapBasedRow(row.getTimestamp(), theVals); } public DateTime getMaxIngestedEventTime() diff --git a/processing/src/main/java/org/apache/druid/segment/incremental/OnheapIncrementalIndex.java b/processing/src/main/java/org/apache/druid/segment/incremental/OnheapIncrementalIndex.java index dd32cd7388fd..dc04aca8768a 100644 --- a/processing/src/main/java/org/apache/druid/segment/incremental/OnheapIncrementalIndex.java +++ b/processing/src/main/java/org/apache/druid/segment/incremental/OnheapIncrementalIndex.java @@ -46,6 +46,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.Stream; /** * @@ -337,13 +338,10 @@ public Iterable iterableWithPostAggregations( return () -> Iterators.transform( facts.iterator(descending), incrementalIndexRow -> { - Aggregator[] aggs = getAggsForRow(incrementalIndexRow.getRowIndex()); + final Aggregator[] aggs = getAggsForRow(incrementalIndexRow.getRowIndex()); return getMapBasedRowWithPostAggregations( - incrementalIndexRow.getTimestamp(), - incrementalIndexRow.getDimsLength(), - incrementalIndexRow::getDim, - aggs.length, - i -> aggs[i].get(), + incrementalIndexRow, + Stream.of(aggs).map(Aggregator::get), postAggs ); } From a3ebdd665f88e2ca15b16f6d1aafde82b1cddcc8 Mon Sep 17 00:00:00 2001 From: Liran Funaro Date: Wed, 5 Jan 2022 17:19:17 +0200 Subject: [PATCH 18/33] Refactor to reduce footprint --- .../incremental/oak/OakIncrementalIndex.java | 22 +++++++++++++++++- .../druid/segment/StringDimensionIndexer.java | 3 +-- .../segment/incremental/IncrementalIndex.java | 22 ++++++++---------- .../incremental/OnheapIncrementalIndex.java | 23 ++++++++++++++++++- .../IncrementalIndexAdapterTest.java | 4 ++-- 5 files changed, 55 insertions(+), 19 deletions(-) diff --git a/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndex.java b/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndex.java index ac4fb4bca85d..22f26eba6c75 100644 --- a/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndex.java +++ b/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndex.java @@ -38,14 +38,17 @@ import org.apache.druid.query.aggregation.AggregatorFactory; import org.apache.druid.query.aggregation.BufferAggregator; import org.apache.druid.query.aggregation.PostAggregator; +import org.apache.druid.segment.ColumnSelectorFactory; import org.apache.druid.segment.incremental.AppendableIndexBuilder; import org.apache.druid.segment.incremental.IncrementalIndex; import org.apache.druid.segment.incremental.IncrementalIndexRow; import org.apache.druid.segment.incremental.IncrementalIndexSchema; import org.apache.druid.segment.incremental.IndexSizeExceededException; +import org.apache.druid.segment.incremental.OnheapIncrementalIndex; import javax.annotation.Nullable; import java.nio.ByteBuffer; +import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -122,6 +125,22 @@ public FactsHolder getFacts() return this; } + @Override + protected Map generateSelectors(AggregatorFactory[] metrics, Supplier rowSupplier, boolean deserializeComplexMetrics, boolean concurrentEventAdd) + { + Map selectors = new HashMap<>(); + for (AggregatorFactory agg : metrics) { + selectors.put( + agg.getName(), + new OnheapIncrementalIndex.CachingColumnSelectorFactory( + makeColumnSelectorFactory(agg, rowSupplier, deserializeComplexMetrics), + concurrentEventAdd + ) + ); + } + return selectors; + } + @Override public int size() { @@ -233,7 +252,8 @@ protected boolean isNull(IncrementalIndexRow incrementalIndexRow, int aggIndex) return aggregators[aggIndex].isNull(oakRow.getAggregationsBuffer(), getOffsetInBuffer(oakRow, aggIndex)); } - private OakIncrementalIndexRow rowFromMapEntry(Map.Entry entry) { + private OakIncrementalIndexRow rowFromMapEntry(Map.Entry entry) + { return new OakIncrementalIndexRow( entry.getKey(), dimensionDescsList, entry.getValue() ); diff --git a/processing/src/main/java/org/apache/druid/segment/StringDimensionIndexer.java b/processing/src/main/java/org/apache/druid/segment/StringDimensionIndexer.java index 52c167090311..b5df18f66773 100644 --- a/processing/src/main/java/org/apache/druid/segment/StringDimensionIndexer.java +++ b/processing/src/main/java/org/apache/druid/segment/StringDimensionIndexer.java @@ -266,8 +266,7 @@ class IndexerDimensionSelector implements DimensionSelector, IdLookup private int[] nullIdIntArray; /** - * Tries to fetch the IndexedInts from the row using getStringDim() if possible. - * Otherwise, it will fetch the int array using getDim() and will convert it to IndexedInts. + * Tries to fetch the int array using getDim() and convert it to IndexedInts. * If the dim is null or with zero length, the value is considered null. * It may be null or empty due to currEntry's rowIndex being smaller than the row's rowIndex in which this * dim first appears. diff --git a/processing/src/main/java/org/apache/druid/segment/incremental/IncrementalIndex.java b/processing/src/main/java/org/apache/druid/segment/incremental/IncrementalIndex.java index 32e53d762785..659ad690ad31 100644 --- a/processing/src/main/java/org/apache/druid/segment/incremental/IncrementalIndex.java +++ b/processing/src/main/java/org/apache/druid/segment/incremental/IncrementalIndex.java @@ -292,16 +292,7 @@ protected IncrementalIndex( this.rollup ); - selectors = new HashMap<>(); - for (AggregatorFactory agg : metrics) { - selectors.put( - agg.getName(), - new OnheapIncrementalIndex.CachingColumnSelectorFactory( - makeColumnSelectorFactory(agg, rowSupplier, deserializeComplexMetrics), - concurrentEventAdd - ) - ); - } + this.selectors = generateSelectors(metrics, rowSupplier, deserializeComplexMetrics, concurrentEventAdd); for (AggregatorFactory metric : metrics) { MetricDesc metricDesc = new MetricDesc(metricDescs.size(), metric); @@ -354,7 +345,6 @@ public boolean canAppendRow() final boolean countCheck = size() < maxRowCount; // if maxBytesInMemory = -1, then ignore sizeCheck final boolean sizeCheck = maxBytesInMemory <= 0 || getBytesInMemory() < maxBytesInMemory; - final boolean canAdd = countCheck && sizeCheck; if (!countCheck && !sizeCheck) { outOfRowsReason = StringUtils.format( "Maximum number of rows [%d] and maximum size in bytes [%d] reached", @@ -366,8 +356,7 @@ public boolean canAppendRow() } else if (!sizeCheck) { outOfRowsReason = StringUtils.format("Maximum size in bytes [%d] reached", maxBytesInMemory); } - - return canAdd; + return countCheck && sizeCheck; } @Nullable @@ -376,6 +365,13 @@ public String getOutOfRowsReason() return outOfRowsReason; } + protected abstract Map generateSelectors( + AggregatorFactory[] metrics, + Supplier rowSupplier, + boolean deserializeComplexMetrics, + boolean concurrentEventAdd + ); + // Note: This method needs to be thread safe. protected abstract AddToFactsResult addToFacts( InputRow row, diff --git a/processing/src/main/java/org/apache/druid/segment/incremental/OnheapIncrementalIndex.java b/processing/src/main/java/org/apache/druid/segment/incremental/OnheapIncrementalIndex.java index dc04aca8768a..8f87f71b9288 100644 --- a/processing/src/main/java/org/apache/druid/segment/incremental/OnheapIncrementalIndex.java +++ b/processing/src/main/java/org/apache/druid/segment/incremental/OnheapIncrementalIndex.java @@ -129,6 +129,27 @@ public FactsHolder getFacts() return facts; } + @Override + protected Map generateSelectors( + final AggregatorFactory[] metrics, + final Supplier rowSupplier, + final boolean deserializeComplexMetrics, + final boolean concurrentEventAdd + ) + { + Map selectors = new HashMap<>(); + for (AggregatorFactory agg : metrics) { + selectors.put( + agg.getName(), + new CachingColumnSelectorFactory( + makeColumnSelectorFactory(agg, rowSupplier, deserializeComplexMetrics), + concurrentEventAdd + ) + ); + } + return selectors; + } + @Override public int size() { @@ -366,7 +387,7 @@ public void close() * heap space. In general the selectorFactory need not to thread-safe. If required, set concurrentEventAdd to true to * use concurrent hash map instead of vanilla hash map for thread-safe operations. */ - static class CachingColumnSelectorFactory implements ColumnSelectorFactory + public static class CachingColumnSelectorFactory implements ColumnSelectorFactory { private final Map> columnSelectorMap; private final ColumnSelectorFactory delegate; diff --git a/processing/src/test/java/org/apache/druid/segment/incremental/IncrementalIndexAdapterTest.java b/processing/src/test/java/org/apache/druid/segment/incremental/IncrementalIndexAdapterTest.java index 90981ed1032c..097874e72f32 100644 --- a/processing/src/test/java/org/apache/druid/segment/incremental/IncrementalIndexAdapterTest.java +++ b/processing/src/test/java/org/apache/druid/segment/incremental/IncrementalIndexAdapterTest.java @@ -130,9 +130,9 @@ public void testGetRowsIterableNoRollup() throws Exception IncrementalIndexTest.populateIndex(timestamp, toPersist1); // facts.keySet() return the rows in the order they are stored internally. - // In plain mode, OnheapInrementalIndex and OffheapIncrementalIndex sort their rows internally by timestamp then by + // In plain mode, OnheapInrementalIndex sort its rows internally by timestamp then by // index (the order they were inserted). - // But facts.keySet() does not require this order. Other implementations, e.g. OakIncrementalIndex, might sort their + // But facts.keySet() does not require this order. Other implementations, might sort their // rows in their native order (as it would be expected by facts.persistIterable()). // To mitigate this, we validate the row index without expecting a specific order. HashMap dim1Vals = new HashMap<>(); From d75d08133e1f971bc2663ee33cce38dac4361bfb Mon Sep 17 00:00:00 2001 From: Liran Funaro Date: Wed, 5 Jan 2022 17:27:48 +0200 Subject: [PATCH 19/33] Align with preparation PR --- .../incremental/oak/OakIncrementalIndex.java | 12 ++-- .../segment/incremental/IncrementalIndex.java | 64 +++++++++++++++++++ .../incremental/OnheapIncrementalIndex.java | 60 +---------------- 3 files changed, 72 insertions(+), 64 deletions(-) diff --git a/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndex.java b/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndex.java index 22f26eba6c75..760478e9721f 100644 --- a/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndex.java +++ b/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndex.java @@ -126,16 +126,18 @@ public FactsHolder getFacts() } @Override - protected Map generateSelectors(AggregatorFactory[] metrics, Supplier rowSupplier, boolean deserializeComplexMetrics, boolean concurrentEventAdd) + protected Map generateSelectors( + AggregatorFactory[] metrics, + Supplier rowSupplier, + boolean deserializeComplexMetrics, + boolean concurrentEventAdd + ) { Map selectors = new HashMap<>(); for (AggregatorFactory agg : metrics) { selectors.put( agg.getName(), - new OnheapIncrementalIndex.CachingColumnSelectorFactory( - makeColumnSelectorFactory(agg, rowSupplier, deserializeComplexMetrics), - concurrentEventAdd - ) + makeCachedColumnSelectorFactory(agg, rowSupplier, deserializeComplexMetrics, concurrentEventAdd) ); } return selectors; diff --git a/processing/src/main/java/org/apache/druid/segment/incremental/IncrementalIndex.java b/processing/src/main/java/org/apache/druid/segment/incremental/IncrementalIndex.java index 659ad690ad31..eb995e8e2534 100644 --- a/processing/src/main/java/org/apache/druid/segment/incremental/IncrementalIndex.java +++ b/processing/src/main/java/org/apache/druid/segment/incremental/IncrementalIndex.java @@ -1093,6 +1093,19 @@ protected ColumnSelectorFactory makeColumnSelectorFactory( return makeColumnSelectorFactory(this, virtualColumns, agg, in, deserializeComplexMetrics); } + protected ColumnSelectorFactory makeCachedColumnSelectorFactory( + final AggregatorFactory agg, + final Supplier in, + final boolean deserializeComplexMetrics, + final boolean concurrentEventAdd + ) + { + return new CachingColumnSelectorFactory( + makeColumnSelectorFactory(agg, in, deserializeComplexMetrics), + concurrentEventAdd + ); + } + protected final Comparator dimsComparator() { return new IncrementalIndexRowComparator(dimensionDescsList); @@ -1547,4 +1560,55 @@ public void inspectRuntimeShape(RuntimeShapeInspector inspector) inspector.visit("index", IncrementalIndex.this); } } + + /** + * Caches references to selector objects for each column instead of creating a new object each time in order to save + * heap space. In general the selectorFactory need not to thread-safe. If required, set concurrentEventAdd to true to + * use concurrent hash map instead of vanilla hash map for thread-safe operations. + */ + static class CachingColumnSelectorFactory implements ColumnSelectorFactory + { + private final Map> columnSelectorMap; + private final ColumnSelectorFactory delegate; + + public CachingColumnSelectorFactory(ColumnSelectorFactory delegate, boolean concurrentEventAdd) + { + this.delegate = delegate; + + if (concurrentEventAdd) { + columnSelectorMap = new ConcurrentHashMap<>(); + } else { + columnSelectorMap = new HashMap<>(); + } + } + + @Override + public DimensionSelector makeDimensionSelector(DimensionSpec dimensionSpec) + { + return delegate.makeDimensionSelector(dimensionSpec); + } + + @Override + public ColumnValueSelector makeColumnValueSelector(String columnName) + { + ColumnValueSelector existing = columnSelectorMap.get(columnName); + if (existing != null) { + return existing; + } + + // We cannot use columnSelectorMap.computeIfAbsent(columnName, delegate::makeColumnValueSelector) + // here since makeColumnValueSelector may modify the columnSelectorMap itself through + // virtual column references, triggering a ConcurrentModificationException in JDK 9 and above. + ColumnValueSelector columnValueSelector = delegate.makeColumnValueSelector(columnName); + existing = columnSelectorMap.putIfAbsent(columnName, columnValueSelector); + return existing != null ? existing : columnValueSelector; + } + + @Nullable + @Override + public ColumnCapabilities getColumnCapabilities(String columnName) + { + return delegate.getColumnCapabilities(columnName); + } + } } diff --git a/processing/src/main/java/org/apache/druid/segment/incremental/OnheapIncrementalIndex.java b/processing/src/main/java/org/apache/druid/segment/incremental/OnheapIncrementalIndex.java index 8f87f71b9288..87e771f088a4 100644 --- a/processing/src/main/java/org/apache/druid/segment/incremental/OnheapIncrementalIndex.java +++ b/processing/src/main/java/org/apache/druid/segment/incremental/OnheapIncrementalIndex.java @@ -29,11 +29,7 @@ import org.apache.druid.query.aggregation.Aggregator; import org.apache.druid.query.aggregation.AggregatorFactory; import org.apache.druid.query.aggregation.PostAggregator; -import org.apache.druid.query.dimension.DimensionSpec; import org.apache.druid.segment.ColumnSelectorFactory; -import org.apache.druid.segment.ColumnValueSelector; -import org.apache.druid.segment.DimensionSelector; -import org.apache.druid.segment.column.ColumnCapabilities; import org.apache.druid.utils.JvmUtils; import javax.annotation.Nullable; @@ -141,10 +137,7 @@ protected Map generateSelectors( for (AggregatorFactory agg : metrics) { selectors.put( agg.getName(), - new CachingColumnSelectorFactory( - makeColumnSelectorFactory(agg, rowSupplier, deserializeComplexMetrics), - concurrentEventAdd - ) + makeCachedColumnSelectorFactory(agg, rowSupplier, deserializeComplexMetrics, concurrentEventAdd) ); } return selectors; @@ -382,57 +375,6 @@ public void close() facts.clear(); } - /** - * Caches references to selector objects for each column instead of creating a new object each time in order to save - * heap space. In general the selectorFactory need not to thread-safe. If required, set concurrentEventAdd to true to - * use concurrent hash map instead of vanilla hash map for thread-safe operations. - */ - public static class CachingColumnSelectorFactory implements ColumnSelectorFactory - { - private final Map> columnSelectorMap; - private final ColumnSelectorFactory delegate; - - public CachingColumnSelectorFactory(ColumnSelectorFactory delegate, boolean concurrentEventAdd) - { - this.delegate = delegate; - - if (concurrentEventAdd) { - columnSelectorMap = new ConcurrentHashMap<>(); - } else { - columnSelectorMap = new HashMap<>(); - } - } - - @Override - public DimensionSelector makeDimensionSelector(DimensionSpec dimensionSpec) - { - return delegate.makeDimensionSelector(dimensionSpec); - } - - @Override - public ColumnValueSelector makeColumnValueSelector(String columnName) - { - ColumnValueSelector existing = columnSelectorMap.get(columnName); - if (existing != null) { - return existing; - } - - // We cannot use columnSelectorMap.computeIfAbsent(columnName, delegate::makeColumnValueSelector) - // here since makeColumnValueSelector may modify the columnSelectorMap itself through - // virtual column references, triggering a ConcurrentModificationException in JDK 9 and above. - ColumnValueSelector columnValueSelector = delegate.makeColumnValueSelector(columnName); - existing = columnSelectorMap.putIfAbsent(columnName, columnValueSelector); - return existing != null ? existing : columnValueSelector; - } - - @Nullable - @Override - public ColumnCapabilities getColumnCapabilities(String columnName) - { - return delegate.getColumnCapabilities(columnName); - } - } - public static class Builder extends AppendableIndexBuilder { @Override From ebf9ddcec22412d8b0b33ddb0dc095bd39f0e3df Mon Sep 17 00:00:00 2001 From: Liran Funaro Date: Thu, 6 Jan 2022 10:34:30 +0200 Subject: [PATCH 20/33] Remove unnecessary changes --- .../incremental/oak/OakIncrementalIndex.java | 1 - .../druid/segment/StringDimensionIndexer.java | 21 ------------------- .../incremental/IncrementalIndexAdapter.java | 13 +----------- 3 files changed, 1 insertion(+), 34 deletions(-) diff --git a/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndex.java b/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndex.java index 760478e9721f..04afa8c9f089 100644 --- a/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndex.java +++ b/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndex.java @@ -44,7 +44,6 @@ import org.apache.druid.segment.incremental.IncrementalIndexRow; import org.apache.druid.segment.incremental.IncrementalIndexSchema; import org.apache.druid.segment.incremental.IndexSizeExceededException; -import org.apache.druid.segment.incremental.OnheapIncrementalIndex; import javax.annotation.Nullable; import java.nio.ByteBuffer; diff --git a/processing/src/main/java/org/apache/druid/segment/StringDimensionIndexer.java b/processing/src/main/java/org/apache/druid/segment/StringDimensionIndexer.java index b5df18f66773..86f3780ca985 100644 --- a/processing/src/main/java/org/apache/druid/segment/StringDimensionIndexer.java +++ b/processing/src/main/java/org/apache/druid/segment/StringDimensionIndexer.java @@ -535,25 +535,4 @@ public void fillBitmapsFromUnsortedEncodedKeyComponent( bitmapIndexes[dimValIdx].add(rowNum); } } - - public void fillBitmapsFromUnsortedEncodedKeyComponent( - IndexedInts key, - int rowNum, - MutableBitmap[] bitmapIndexes, - BitmapFactory factory - ) - { - if (!hasBitmapIndexes) { - throw new UnsupportedOperationException("This column does not include bitmap indexes"); - } - - final int size = key.size(); - for (int i = 0; i < size; i++) { - int dimValIdx = key.get(i); - if (bitmapIndexes[dimValIdx] == null) { - bitmapIndexes[dimValIdx] = factory.makeEmptyMutableBitmap(); - } - bitmapIndexes[dimValIdx].add(rowNum); - } - } } diff --git a/processing/src/main/java/org/apache/druid/segment/incremental/IncrementalIndexAdapter.java b/processing/src/main/java/org/apache/druid/segment/incremental/IncrementalIndexAdapter.java index 57a6859f680e..16e9efb523bc 100644 --- a/processing/src/main/java/org/apache/druid/segment/incremental/IncrementalIndexAdapter.java +++ b/processing/src/main/java/org/apache/druid/segment/incremental/IncrementalIndexAdapter.java @@ -26,12 +26,10 @@ import org.apache.druid.segment.IndexableAdapter; import org.apache.druid.segment.IntIteratorUtils; import org.apache.druid.segment.Metadata; -import org.apache.druid.segment.StringDimensionIndexer; import org.apache.druid.segment.TransformableRowIterator; import org.apache.druid.segment.column.ColumnCapabilities; import org.apache.druid.segment.data.BitmapValues; import org.apache.druid.segment.data.CloseableIndexed; -import org.apache.druid.segment.data.IndexedInts; import org.joda.time.Interval; import javax.annotation.Nullable; @@ -112,16 +110,7 @@ private void processRows( if (capabilities.hasBitmapIndexes()) { final MutableBitmap[] bitmapIndexes = accessor.invertedIndexes; final DimensionIndexer indexer = accessor.indexer; - - // It is possible that the current indexer is StringDimensionIndexer and that the row - // supports fetching the IndexedInts object directly, which has better performance. - IndexedInts s = indexer instanceof StringDimensionIndexer ? row.getStringDim(dimIndex) : null; - - if (s != null) { - ((StringDimensionIndexer) indexer).fillBitmapsFromUnsortedEncodedKeyComponent(s, rowNum, bitmapIndexes, bitmapFactory); - } else { - indexer.fillBitmapsFromUnsortedEncodedKeyComponent(row.getDim(dimIndex), rowNum, bitmapIndexes, bitmapFactory); - } + indexer.fillBitmapsFromUnsortedEncodedKeyComponent(row.getDim(dimIndex), rowNum, bitmapIndexes, bitmapFactory); } } ++rowNum; From b007d2c4df5f7eac5db9cf90ae59822114fb15ad Mon Sep 17 00:00:00 2001 From: Liran Funaro Date: Thu, 6 Jan 2022 11:33:00 +0200 Subject: [PATCH 21/33] Move common code to IncrementalIndex --- .../incremental/oak/OakIncrementalIndex.java | 20 ------------------ .../segment/incremental/IncrementalIndex.java | 21 +++++++++++++------ .../incremental/OnheapIncrementalIndex.java | 21 ------------------- 3 files changed, 15 insertions(+), 47 deletions(-) diff --git a/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndex.java b/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndex.java index 04afa8c9f089..3d8a6fa615d4 100644 --- a/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndex.java +++ b/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndex.java @@ -38,7 +38,6 @@ import org.apache.druid.query.aggregation.AggregatorFactory; import org.apache.druid.query.aggregation.BufferAggregator; import org.apache.druid.query.aggregation.PostAggregator; -import org.apache.druid.segment.ColumnSelectorFactory; import org.apache.druid.segment.incremental.AppendableIndexBuilder; import org.apache.druid.segment.incremental.IncrementalIndex; import org.apache.druid.segment.incremental.IncrementalIndexRow; @@ -47,7 +46,6 @@ import javax.annotation.Nullable; import java.nio.ByteBuffer; -import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -124,24 +122,6 @@ public FactsHolder getFacts() return this; } - @Override - protected Map generateSelectors( - AggregatorFactory[] metrics, - Supplier rowSupplier, - boolean deserializeComplexMetrics, - boolean concurrentEventAdd - ) - { - Map selectors = new HashMap<>(); - for (AggregatorFactory agg : metrics) { - selectors.put( - agg.getName(), - makeCachedColumnSelectorFactory(agg, rowSupplier, deserializeComplexMetrics, concurrentEventAdd) - ); - } - return selectors; - } - @Override public int size() { diff --git a/processing/src/main/java/org/apache/druid/segment/incremental/IncrementalIndex.java b/processing/src/main/java/org/apache/druid/segment/incremental/IncrementalIndex.java index eb995e8e2534..d77cace75fc3 100644 --- a/processing/src/main/java/org/apache/druid/segment/incremental/IncrementalIndex.java +++ b/processing/src/main/java/org/apache/druid/segment/incremental/IncrementalIndex.java @@ -84,6 +84,7 @@ import javax.annotation.Nullable; import java.io.Closeable; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Comparator; import java.util.Deque; @@ -99,6 +100,7 @@ import java.util.concurrent.ConcurrentSkipListMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; import java.util.stream.Stream; public abstract class IncrementalIndex extends AbstractIndex implements Iterable, Closeable, ColumnInspector @@ -365,12 +367,19 @@ public String getOutOfRowsReason() return outOfRowsReason; } - protected abstract Map generateSelectors( - AggregatorFactory[] metrics, - Supplier rowSupplier, - boolean deserializeComplexMetrics, - boolean concurrentEventAdd - ); + protected Map generateSelectors( + final AggregatorFactory[] metrics, + final Supplier rowSupplier, + final boolean deserializeComplexMetrics, + final boolean concurrentEventAdd + ) + { + return Arrays.stream(metrics).collect(Collectors.toMap( + AggregatorFactory::getName, + agg -> makeCachedColumnSelectorFactory(agg, rowSupplier, deserializeComplexMetrics, concurrentEventAdd), + (agg1, agg2) -> agg2 + )); + } // Note: This method needs to be thread safe. protected abstract AddToFactsResult addToFacts( diff --git a/processing/src/main/java/org/apache/druid/segment/incremental/OnheapIncrementalIndex.java b/processing/src/main/java/org/apache/druid/segment/incremental/OnheapIncrementalIndex.java index 87e771f088a4..70a9afef5d8e 100644 --- a/processing/src/main/java/org/apache/druid/segment/incremental/OnheapIncrementalIndex.java +++ b/processing/src/main/java/org/apache/druid/segment/incremental/OnheapIncrementalIndex.java @@ -29,15 +29,12 @@ import org.apache.druid.query.aggregation.Aggregator; import org.apache.druid.query.aggregation.AggregatorFactory; import org.apache.druid.query.aggregation.PostAggregator; -import org.apache.druid.segment.ColumnSelectorFactory; import org.apache.druid.utils.JvmUtils; import javax.annotation.Nullable; import java.io.IOException; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; @@ -125,24 +122,6 @@ public FactsHolder getFacts() return facts; } - @Override - protected Map generateSelectors( - final AggregatorFactory[] metrics, - final Supplier rowSupplier, - final boolean deserializeComplexMetrics, - final boolean concurrentEventAdd - ) - { - Map selectors = new HashMap<>(); - for (AggregatorFactory agg : metrics) { - selectors.put( - agg.getName(), - makeCachedColumnSelectorFactory(agg, rowSupplier, deserializeComplexMetrics, concurrentEventAdd) - ); - } - return selectors; - } - @Override public int size() { From fd3d53d4f73098d9e6940ef56dcea6e84a72a709 Mon Sep 17 00:00:00 2001 From: Liran Funaro Date: Fri, 7 Jan 2022 01:49:41 +0200 Subject: [PATCH 22/33] Add getIndexedDim() --- .../oak/OakIncrementalIndexRow.java | 27 +++++++------- .../druid/segment/incremental/oak/OakKey.java | 22 +++++------- .../druid/segment/StringDimensionIndexer.java | 35 +++++++------------ .../incremental/IncrementalIndexRow.java | 28 +++++++++++---- 4 files changed, 55 insertions(+), 57 deletions(-) diff --git a/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndexRow.java b/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndexRow.java index 2689b00a0d86..edae5f1b2146 100644 --- a/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndexRow.java +++ b/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndexRow.java @@ -40,8 +40,6 @@ public class OakIncrementalIndexRow extends IncrementalIndexRow private ByteBuffer aggregationsBuffer; private int aggregationsOffset; private int dimsLength; - @Nullable - private OakKey.StringDim stringDim; public OakIncrementalIndexRow(OakUnscopedBuffer dimensions, List dimensionDescsList, @@ -54,7 +52,6 @@ public OakIncrementalIndexRow(OakUnscopedBuffer dimensions, this.aggregationsBuffer = null; this.aggregationsOffset = 0; this.dimsLength = -1; // lazy initialization - this.stringDim = null; } /** @@ -71,9 +68,6 @@ public void reset() dimensions = oakDimensions.getAddress(); aggregationsBuffer = null; aggregationsOffset = 0; - if (stringDim != null) { - stringDim.reset(dimensions); - } } private void updateAggregationsBuffer() @@ -132,21 +126,26 @@ public boolean isDimNull(int dimIndex) } /** - * Allows faster access to a string dimension because it uses lazy evaluation (no need for deserialization). - * The caller must validate that this dimension is not null. + * Allows faster access to a IndexedInts dimension because it uses lazy evaluation (no need for deserialization). */ @Override - public IndexedInts getStringDim(final int dimIndex) + @Nullable + public IndexedInts getIndexedDim(final int dimIndex, @Nullable IndexedInts cachedIndexedInts) { - if (stringDim == null) { - stringDim = new OakKey.StringDim(dimensions); + if (isDimNull(dimIndex)) { + return null; } - if (stringDim.getDimIndex() != dimIndex) { - stringDim.setDimIndex(dimIndex); + OakKey.StringDim indexedInts; + + if (!(cachedIndexedInts instanceof OakKey.StringDim)) { + indexedInts = new OakKey.StringDim(dimensions, dimIndex); + } else { + indexedInts = (OakKey.StringDim) cachedIndexedInts; + indexedInts.setValues(dimensions, dimIndex); } - return stringDim; + return indexedInts; } @Override diff --git a/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakKey.java b/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakKey.java index 9ab026752886..be838c186e3c 100644 --- a/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakKey.java +++ b/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakKey.java @@ -177,24 +177,18 @@ public static class StringDim implements IndexedInts int arraySize; long arrayAddress; - public StringDim(long dimensionsAddress) + public StringDim(long dimensionsAddress, int dimIndex) { - this.dimensionsAddress = dimensionsAddress; - dimIndex = -1; - initialized = false; + setValues(dimensionsAddress, dimIndex); } - public void reset(long dimensions) + public void setValues(long dimensionsAddress, int dimIndex) { - this.dimensionsAddress = dimensions; - dimIndex = -1; - initialized = false; - } - - public void setDimIndex(final int dimIndex) - { - this.dimIndex = dimIndex; - initialized = false; + if (this.dimensionsAddress != dimensionsAddress || this.dimIndex != dimIndex) { + this.dimensionsAddress = dimensionsAddress; + this.dimIndex = dimIndex; + initialized = false; + } } private void init() diff --git a/processing/src/main/java/org/apache/druid/segment/StringDimensionIndexer.java b/processing/src/main/java/org/apache/druid/segment/StringDimensionIndexer.java index 86f3780ca985..f2602cfc8a69 100644 --- a/processing/src/main/java/org/apache/druid/segment/StringDimensionIndexer.java +++ b/processing/src/main/java/org/apache/druid/segment/StringDimensionIndexer.java @@ -259,14 +259,17 @@ public DimensionSelector makeDimensionSelector( class IndexerDimensionSelector implements DimensionSelector, IdLookup { - private final ArrayBasedIndexedInts indexedInts = new ArrayBasedIndexedInts(); + private final ArrayBasedIndexedInts defaultIndexedInts = new ArrayBasedIndexedInts(); + + @MonotonicNonNull + private IndexedInts cachedIndexedInts = null; @Nullable @MonotonicNonNull private int[] nullIdIntArray; /** - * Tries to fetch the int array using getDim() and convert it to IndexedInts. + * Tries to fetch the dimention as an IndexedInts. * If the dim is null or with zero length, the value is considered null. * It may be null or empty due to currEntry's rowIndex being smaller than the row's rowIndex in which this * dim first appears. @@ -276,32 +279,18 @@ class IndexerDimensionSelector implements DimensionSelector, IdLookup @Nullable private IndexedInts getRowOrNull() { - IncrementalIndexRow key = currEntry.get(); - - if (key.isDimNull(dimIndex)) { - return null; - } - - IndexedInts ret = key.getStringDim(dimIndex); + IndexedInts ret = currEntry.get().getIndexedDim(dimIndex, cachedIndexedInts); if (ret != null) { - // Use if the incremental index row supports lazy indexed int and it is not empty. + cachedIndexedInts = ret; return ret.size() > 0 ? ret : null; } - - int[] indices = (int[]) key.getDim(dimIndex); - - if (indices == null || indices.length == 0) { - return null; - } - - indexedInts.setValues(indices, indices.length); - return indexedInts; + return null; } private IndexedInts getDefaultIndexedInts() { if (hasMultipleValues) { - indexedInts.setValues(IntArrays.EMPTY_ARRAY, 0); + defaultIndexedInts.setValues(IntArrays.EMPTY_ARRAY, 0); } else { final int nullId = getEncodedValue(null, false); if (nullId >= 0 && nullId < maxId) { @@ -309,15 +298,15 @@ private IndexedInts getDefaultIndexedInts() if (nullIdIntArray == null) { nullIdIntArray = new int[]{nullId}; } - indexedInts.setValues(nullIdIntArray, 1); + defaultIndexedInts.setValues(nullIdIntArray, 1); } else { // null doesn't exist in the dictionary; return an empty array. // Choose to use ArrayBasedIndexedInts later, instead of special "empty" IndexedInts, for monomorphism - indexedInts.setValues(IntArrays.EMPTY_ARRAY, 0); + defaultIndexedInts.setValues(IntArrays.EMPTY_ARRAY, 0); } } - return indexedInts; + return defaultIndexedInts; } @Override diff --git a/processing/src/main/java/org/apache/druid/segment/incremental/IncrementalIndexRow.java b/processing/src/main/java/org/apache/druid/segment/incremental/IncrementalIndexRow.java index fb1eece6e46a..5f0abc86ac59 100644 --- a/processing/src/main/java/org/apache/druid/segment/incremental/IncrementalIndexRow.java +++ b/processing/src/main/java/org/apache/druid/segment/incremental/IncrementalIndexRow.java @@ -23,6 +23,7 @@ import com.google.common.collect.Lists; import org.apache.druid.java.util.common.DateTimes; import org.apache.druid.segment.DimensionIndexer; +import org.apache.druid.segment.data.ArrayBasedIndexedInts; import org.apache.druid.segment.data.IndexedInts; import javax.annotation.Nullable; @@ -107,6 +108,27 @@ public Object getDim(int index) return dims[index]; } + @Nullable + public IndexedInts getIndexedDim(final int index, @Nullable IndexedInts cachedIndexedInts) + { + Object dim = getDim(index); + if (!(dim instanceof int[])) { + return null; + } + + int[] indices = (int[]) dim; + ArrayBasedIndexedInts indexedInts; + + if (!(cachedIndexedInts instanceof ArrayBasedIndexedInts)) { + indexedInts = new ArrayBasedIndexedInts(indices); + } else { + indexedInts = (ArrayBasedIndexedInts) cachedIndexedInts; + indexedInts.setValues(indices, indices.length); + } + + return indexedInts; + } + public int getDimsLength() { return dims.length; @@ -117,12 +139,6 @@ public boolean isDimNull(int index) return (index >= dims.length) || (dims[index] == null); } - @Nullable - public IndexedInts getStringDim(final int dimIndex) - { - return null; - } - public int getRowIndex() { return rowIndex; From 9c65674831681e8f356df694b320e2ed068beb06 Mon Sep 17 00:00:00 2001 From: Liran Funaro Date: Mon, 10 Jan 2022 10:42:20 +0200 Subject: [PATCH 23/33] Fix spotbug --- .../oak/OakIncrementalIndexRow.java | 8 ++--- .../druid/segment/incremental/oak/OakKey.java | 35 +++++-------------- .../druid/segment/StringDimensionIndexer.java | 3 +- 3 files changed, 15 insertions(+), 31 deletions(-) diff --git a/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndexRow.java b/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndexRow.java index edae5f1b2146..db8746138107 100644 --- a/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndexRow.java +++ b/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndexRow.java @@ -136,12 +136,12 @@ public IndexedInts getIndexedDim(final int dimIndex, @Nullable IndexedInts cache return null; } - OakKey.StringDim indexedInts; + OakKey.IndexedDim indexedInts; - if (!(cachedIndexedInts instanceof OakKey.StringDim)) { - indexedInts = new OakKey.StringDim(dimensions, dimIndex); + if (!(cachedIndexedInts instanceof OakKey.IndexedDim)) { + indexedInts = new OakKey.IndexedDim(dimensions, dimIndex); } else { - indexedInts = (OakKey.StringDim) cachedIndexedInts; + indexedInts = (OakKey.IndexedDim) cachedIndexedInts; indexedInts.setValues(dimensions, dimIndex); } diff --git a/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakKey.java b/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakKey.java index be838c186e3c..a9a1878664bd 100644 --- a/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakKey.java +++ b/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakKey.java @@ -166,18 +166,19 @@ static Object[] getAllDims(long address) } /** - * StringDim purpose is to generate a lazy-evaluation version of a string dimension instead of the array of integers - * that is returned by getDim(int index). + * A lazy-evaluation version of an indexed ints dimension, used by + * {@link OakIncrementalIndexRow#getIndexedDim(int, IndexedInts)}. + * As opposed to the integers array version that is returned by + * {@link IncrementalIndexRow#getIndexedDim(int, IndexedInts)}. */ - public static class StringDim implements IndexedInts + public static class IndexedDim implements IndexedInts { long dimensionsAddress; int dimIndex; - boolean initialized; int arraySize; long arrayAddress; - public StringDim(long dimensionsAddress, int dimIndex) + public IndexedDim(long dimensionsAddress, int dimIndex) { setValues(dimensionsAddress, dimIndex); } @@ -187,39 +188,21 @@ public void setValues(long dimensionsAddress, int dimIndex) if (this.dimensionsAddress != dimensionsAddress || this.dimIndex != dimIndex) { this.dimensionsAddress = dimensionsAddress; this.dimIndex = dimIndex; - initialized = false; + long dimAddress = this.dimensionsAddress + getDimOffsetInBuffer(dimIndex); + arrayAddress = dimensionsAddress + UnsafeUtils.getInt(dimAddress + STRING_DIM_ARRAY_POS_OFFSET); + arraySize = UnsafeUtils.getInt(dimAddress + STRING_DIM_ARRAY_LENGTH_OFFSET); } } - private void init() - { - if (initialized) { - return; - } - - long dimAddress = this.dimensionsAddress + getDimOffsetInBuffer(dimIndex); - arrayAddress = dimensionsAddress + UnsafeUtils.getInt(dimAddress + STRING_DIM_ARRAY_POS_OFFSET); - arraySize = UnsafeUtils.getInt(dimAddress + STRING_DIM_ARRAY_LENGTH_OFFSET); - initialized = true; - } - - public int getDimIndex() - { - init(); - return dimIndex; - } - @Override public int size() { - init(); return arraySize; } @Override public int get(int index) { - init(); return UnsafeUtils.getInt(arrayAddress + ((long) index) * Integer.BYTES); } diff --git a/processing/src/main/java/org/apache/druid/segment/StringDimensionIndexer.java b/processing/src/main/java/org/apache/druid/segment/StringDimensionIndexer.java index f2602cfc8a69..0bdd9458b566 100644 --- a/processing/src/main/java/org/apache/druid/segment/StringDimensionIndexer.java +++ b/processing/src/main/java/org/apache/druid/segment/StringDimensionIndexer.java @@ -261,8 +261,9 @@ class IndexerDimensionSelector implements DimensionSelector, IdLookup { private final ArrayBasedIndexedInts defaultIndexedInts = new ArrayBasedIndexedInts(); + @Nullable @MonotonicNonNull - private IndexedInts cachedIndexedInts = null; + private IndexedInts cachedIndexedInts; @Nullable @MonotonicNonNull From c0762dd1701218b77930cf44efc513dc1ab6610a Mon Sep 17 00:00:00 2001 From: Liran Funaro Date: Mon, 10 Jan 2022 16:42:54 +0200 Subject: [PATCH 24/33] Bug fix --- .../druid/segment/incremental/IncrementalIndexRow.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/processing/src/main/java/org/apache/druid/segment/incremental/IncrementalIndexRow.java b/processing/src/main/java/org/apache/druid/segment/incremental/IncrementalIndexRow.java index 5f0abc86ac59..cd17a8639c3d 100644 --- a/processing/src/main/java/org/apache/druid/segment/incremental/IncrementalIndexRow.java +++ b/processing/src/main/java/org/apache/druid/segment/incremental/IncrementalIndexRow.java @@ -119,13 +119,13 @@ public IndexedInts getIndexedDim(final int index, @Nullable IndexedInts cachedIn int[] indices = (int[]) dim; ArrayBasedIndexedInts indexedInts; - if (!(cachedIndexedInts instanceof ArrayBasedIndexedInts)) { - indexedInts = new ArrayBasedIndexedInts(indices); - } else { + if (cachedIndexedInts instanceof ArrayBasedIndexedInts) { indexedInts = (ArrayBasedIndexedInts) cachedIndexedInts; - indexedInts.setValues(indices, indices.length); + } else { + indexedInts = new ArrayBasedIndexedInts(); } + indexedInts.setValues(indices, indices.length); return indexedInts; } From b6c9df9de529c2bafabec8eb2a0d5f51b66b5567 Mon Sep 17 00:00:00 2001 From: Liran Funaro Date: Wed, 12 Jan 2022 11:51:45 +0200 Subject: [PATCH 25/33] Update Oak version --- .../oak-incremental-index/pom.xml | 2 +- .../incremental/oak/OakIncrementalIndex.java | 42 +++++++------- .../oak/OakIncrementalIndexRow.java | 18 +----- .../oak/OakIncrementalIndexSpec.java | 6 +- .../druid/segment/incremental/oak/OakKey.java | 57 +++++++++++-------- 5 files changed, 62 insertions(+), 63 deletions(-) diff --git a/extensions-contrib/oak-incremental-index/pom.xml b/extensions-contrib/oak-incremental-index/pom.xml index a43e19400b4f..203019400cce 100644 --- a/extensions-contrib/oak-incremental-index/pom.xml +++ b/extensions-contrib/oak-incremental-index/pom.xml @@ -37,7 +37,7 @@ com.yahoo.oak oak - 0.2.3.1 + 0.2.4-SNAPSHOT diff --git a/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndex.java b/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndex.java index 3d8a6fa615d4..2a709fc59ab0 100644 --- a/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndex.java +++ b/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndex.java @@ -113,7 +113,7 @@ public OakIncrementalIndex(IncrementalIndexSchema incrementalIndexSchema, ).setPreferredBlockSize(oakBlockSize) .setChunkMaxItems(oakChunkMaxItems) .setMemoryCapacity(oakMaxMemoryCapacity) - .build(); + .buildOrderedMap(); } @Override @@ -187,50 +187,45 @@ public int getLastRowIndex() return indexIncrement.get() - 1; } - private int getOffsetInBuffer(int aggOffset, int aggIndex) + private int getOffsetInBuffer(int aggIndex) { assert aggregatorOffsetInBuffer != null; - return aggOffset + aggregatorOffsetInBuffer[aggIndex]; - } - - private int getOffsetInBuffer(OakIncrementalIndexRow oakRow, int aggIndex) - { - return getOffsetInBuffer(oakRow.getAggregationsOffset(), aggIndex); + return aggregatorOffsetInBuffer[aggIndex]; } @Override protected float getMetricFloatValue(IncrementalIndexRow incrementalIndexRow, int aggIndex) { OakIncrementalIndexRow oakRow = (OakIncrementalIndexRow) incrementalIndexRow; - return aggregators[aggIndex].getFloat(oakRow.getAggregationsBuffer(), getOffsetInBuffer(oakRow, aggIndex)); + return aggregators[aggIndex].getFloat(oakRow.getAggregationsBuffer(), getOffsetInBuffer(aggIndex)); } @Override protected long getMetricLongValue(IncrementalIndexRow incrementalIndexRow, int aggIndex) { OakIncrementalIndexRow oakRow = (OakIncrementalIndexRow) incrementalIndexRow; - return aggregators[aggIndex].getLong(oakRow.getAggregationsBuffer(), getOffsetInBuffer(oakRow, aggIndex)); + return aggregators[aggIndex].getLong(oakRow.getAggregationsBuffer(), getOffsetInBuffer(aggIndex)); } @Override protected Object getMetricObjectValue(IncrementalIndexRow incrementalIndexRow, int aggIndex) { OakIncrementalIndexRow oakRow = (OakIncrementalIndexRow) incrementalIndexRow; - return aggregators[aggIndex].get(oakRow.getAggregationsBuffer(), getOffsetInBuffer(oakRow, aggIndex)); + return aggregators[aggIndex].get(oakRow.getAggregationsBuffer(), getOffsetInBuffer(aggIndex)); } @Override protected double getMetricDoubleValue(IncrementalIndexRow incrementalIndexRow, int aggIndex) { OakIncrementalIndexRow oakRow = (OakIncrementalIndexRow) incrementalIndexRow; - return aggregators[aggIndex].getDouble(oakRow.getAggregationsBuffer(), getOffsetInBuffer(oakRow, aggIndex)); + return aggregators[aggIndex].getDouble(oakRow.getAggregationsBuffer(), getOffsetInBuffer(aggIndex)); } @Override protected boolean isNull(IncrementalIndexRow incrementalIndexRow, int aggIndex) { OakIncrementalIndexRow oakRow = (OakIncrementalIndexRow) incrementalIndexRow; - return aggregators[aggIndex].isNull(oakRow.getAggregationsBuffer(), getOffsetInBuffer(oakRow, aggIndex)); + return aggregators[aggIndex].isNull(oakRow.getAggregationsBuffer(), getOffsetInBuffer(aggIndex)); } private OakIncrementalIndexRow rowFromMapEntry(Map.Entry entry) @@ -260,7 +255,7 @@ public Iterable iterableWithPostAggregations( // Aggregator management: initialization and aggregation - public void initAggValue(OakInputRowContext ctx, ByteBuffer aggBuffer, int aggOffset) + public void initAggValue(OakInputRowContext ctx, ByteBuffer aggBuffer) { AggregatorFactory[] metrics = getMetricAggs(); assert selectors != null; @@ -283,19 +278,19 @@ public void initAggValue(OakInputRowContext ctx, ByteBuffer aggBuffer, int aggOf } for (int i = 0; i < metrics.length; i++) { - aggregators[i].init(aggBuffer, getOffsetInBuffer(aggOffset, i)); + aggregators[i].init(aggBuffer, getOffsetInBuffer(i)); } - aggregate(ctx, aggBuffer, aggOffset); + aggregate(ctx, aggBuffer); } public void aggregate(OakInputRowContext ctx, OakBuffer buffer) { OakUnsafeDirectBuffer unsafeBuffer = (OakUnsafeDirectBuffer) buffer; - aggregate(ctx, unsafeBuffer.getByteBuffer(), unsafeBuffer.getOffset()); + aggregate(ctx, unsafeBuffer.getByteBuffer()); } - public void aggregate(OakInputRowContext ctx, ByteBuffer aggBuffer, int aggOffset) + public void aggregate(OakInputRowContext ctx, ByteBuffer aggBuffer) { ctx.setRow(); @@ -303,7 +298,7 @@ public void aggregate(OakInputRowContext ctx, ByteBuffer aggBuffer, int aggOffse final BufferAggregator agg = aggregators[i]; try { - agg.aggregate(aggBuffer, getOffsetInBuffer(aggOffset, i)); + agg.aggregate(aggBuffer, getOffsetInBuffer(i)); } catch (ParseException e) { // "aggregate" can throw ParseExceptions if a selector expects something but gets something else. @@ -325,7 +320,7 @@ class OakValueSerializer implements OakSerializer public void serialize(OakInputRowContext ctx, OakScopedWriteBuffer buffer) { OakUnsafeDirectBuffer unsafeBuffer = (OakUnsafeDirectBuffer) buffer; - initAggValue(ctx, unsafeBuffer.getByteBuffer(), unsafeBuffer.getOffset()); + initAggValue(ctx, unsafeBuffer.getByteBuffer()); } @Override @@ -340,6 +335,13 @@ public int calculateSize(OakInputRowContext row) { return aggregatorsTotalSize; } + + @Override + public int calculateHash(OakInputRowContext oakInputRowContext) + { + // This method should not be called. + throw new UnsupportedOperationException(); + } } // FactsHolder helper methods diff --git a/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndexRow.java b/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndexRow.java index db8746138107..67615740ed35 100644 --- a/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndexRow.java +++ b/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndexRow.java @@ -38,7 +38,6 @@ public class OakIncrementalIndexRow extends IncrementalIndexRow private final OakUnsafeDirectBuffer oakAggregations; @Nullable private ByteBuffer aggregationsBuffer; - private int aggregationsOffset; private int dimsLength; public OakIncrementalIndexRow(OakUnscopedBuffer dimensions, @@ -50,7 +49,6 @@ public OakIncrementalIndexRow(OakUnscopedBuffer dimensions, this.oakAggregations = (OakUnsafeDirectBuffer) aggregations; this.dimensions = oakDimensions.getAddress(); this.aggregationsBuffer = null; - this.aggregationsOffset = 0; this.dimsLength = -1; // lazy initialization } @@ -67,29 +65,17 @@ public void reset() dimsLength = -1; dimensions = oakDimensions.getAddress(); aggregationsBuffer = null; - aggregationsOffset = 0; } - private void updateAggregationsBuffer() + public ByteBuffer getAggregationsBuffer() { + // Read buffer only once if (aggregationsBuffer == null) { aggregationsBuffer = oakAggregations.getByteBuffer(); - aggregationsOffset = oakAggregations.getOffset(); } - } - - public ByteBuffer getAggregationsBuffer() - { - updateAggregationsBuffer(); return aggregationsBuffer; } - public int getAggregationsOffset() - { - updateAggregationsBuffer(); - return aggregationsOffset; - } - @Override public long getTimestamp() { diff --git a/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndexSpec.java b/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndexSpec.java index e49468668cf9..d5be1190fd09 100644 --- a/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndexSpec.java +++ b/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndexSpec.java @@ -24,6 +24,7 @@ import org.apache.druid.segment.incremental.AppendableIndexSpec; import org.apache.druid.utils.JvmUtils; +import javax.annotation.Nonnull; import javax.annotation.Nullable; /** @@ -71,6 +72,7 @@ public int getOakChunkMaxItems() return oakChunkMaxItems; } + @Nonnull @Override public OakIncrementalIndex.Builder builder() { @@ -83,9 +85,11 @@ public OakIncrementalIndex.Builder builder() @Override public long getDefaultMaxBytesInMemory() { + // Oak allocates its keys/values directly so the JVM off-heap limitations does not apply on it. + // Yet, we want to respect these values if the user did not specify any specific limitation. // In the realtime node, the entire JVM's direct memory is utilized for ingestion and persist operations. // But maxBytesInMemory only refers to the active index size and not to the index being flushed to disk and the - // persist buffer. + // persist-buffer. // To account for that, we set default to 1/2 of the max jvm's direct memory. return JvmUtils.getRuntimeInfo().getDirectMemorySizeBytes() / 2; } diff --git a/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakKey.java b/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakKey.java index a9a1878664bd..22d7a8302e9a 100644 --- a/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakKey.java +++ b/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakKey.java @@ -21,13 +21,13 @@ import com.google.common.primitives.Ints; import com.google.common.primitives.Longs; +import com.yahoo.oak.DirectUtils; import com.yahoo.oak.OakBuffer; import com.yahoo.oak.OakComparator; import com.yahoo.oak.OakScopedReadBuffer; import com.yahoo.oak.OakScopedWriteBuffer; import com.yahoo.oak.OakSerializer; import com.yahoo.oak.OakUnsafeDirectBuffer; -import com.yahoo.oak.UnsafeUtils; import org.apache.druid.query.monomorphicprocessing.RuntimeShapeInspector; import org.apache.druid.segment.DimensionIndexer; import org.apache.druid.segment.column.ColumnCapabilities; @@ -102,17 +102,17 @@ static long getKeyAddress(OakBuffer buffer) static long getTimestamp(long address) { - return UnsafeUtils.getLong(address + TIME_STAMP_OFFSET); + return DirectUtils.getLong(address + TIME_STAMP_OFFSET); } static int getRowIndex(long address) { - return UnsafeUtils.getInt(address + ROW_INDEX_OFFSET); + return DirectUtils.getInt(address + ROW_INDEX_OFFSET); } static int getDimsLength(long address) { - return UnsafeUtils.getInt(address + DIMS_LENGTH_OFFSET); + return DirectUtils.getInt(address + DIMS_LENGTH_OFFSET); } static int getDimOffsetInBuffer(int dimIndex) @@ -128,14 +128,14 @@ static boolean isValueTypeNull(int dimValueTypeID) static boolean isDimNull(long address, int dimIndex) { long dimAddress = address + getDimOffsetInBuffer(dimIndex); - return isValueTypeNull(UnsafeUtils.getInt(dimAddress + DIM_VALUE_TYPE_OFFSET)); + return isValueTypeNull(DirectUtils.getInt(dimAddress + DIM_VALUE_TYPE_OFFSET)); } @Nullable static Object getDim(long address, int dimIndex) { long dimAddress = address + getDimOffsetInBuffer(dimIndex); - int dimValueTypeID = UnsafeUtils.getInt(dimAddress + DIM_VALUE_TYPE_OFFSET); + int dimValueTypeID = DirectUtils.getInt(dimAddress + DIM_VALUE_TYPE_OFFSET); if (isValueTypeNull(dimValueTypeID)) { return null; @@ -143,16 +143,16 @@ static Object getDim(long address, int dimIndex) switch (VALUE_ORDINAL_TYPES[dimValueTypeID]) { case DOUBLE: - return UnsafeUtils.getDouble(dimAddress + DIM_DATA_OFFSET); + return DirectUtils.getDouble(dimAddress + DIM_DATA_OFFSET); case FLOAT: - return UnsafeUtils.getFloat(dimAddress + DIM_DATA_OFFSET); + return DirectUtils.getFloat(dimAddress + DIM_DATA_OFFSET); case LONG: - return UnsafeUtils.getLong(dimAddress + DIM_DATA_OFFSET); + return DirectUtils.getLong(dimAddress + DIM_DATA_OFFSET); case STRING: - int arrayPos = UnsafeUtils.getInt(dimAddress + STRING_DIM_ARRAY_POS_OFFSET); - int arraySize = UnsafeUtils.getInt(dimAddress + STRING_DIM_ARRAY_LENGTH_OFFSET); + int arrayPos = DirectUtils.getInt(dimAddress + STRING_DIM_ARRAY_POS_OFFSET); + int arraySize = DirectUtils.getInt(dimAddress + STRING_DIM_ARRAY_LENGTH_OFFSET); int[] array = new int[arraySize]; - UnsafeUtils.copyToArray(address + arrayPos, array, arraySize); + DirectUtils.copyToArray(address + arrayPos, array, arraySize); return array; default: return null; @@ -189,8 +189,8 @@ public void setValues(long dimensionsAddress, int dimIndex) this.dimensionsAddress = dimensionsAddress; this.dimIndex = dimIndex; long dimAddress = this.dimensionsAddress + getDimOffsetInBuffer(dimIndex); - arrayAddress = dimensionsAddress + UnsafeUtils.getInt(dimAddress + STRING_DIM_ARRAY_POS_OFFSET); - arraySize = UnsafeUtils.getInt(dimAddress + STRING_DIM_ARRAY_LENGTH_OFFSET); + arrayAddress = dimensionsAddress + DirectUtils.getInt(dimAddress + STRING_DIM_ARRAY_POS_OFFSET); + arraySize = DirectUtils.getInt(dimAddress + STRING_DIM_ARRAY_LENGTH_OFFSET); } } @@ -203,7 +203,7 @@ public int size() @Override public int get(int index) { - return UnsafeUtils.getInt(arrayAddress + ((long) index) * Integer.BYTES); + return DirectUtils.getInt(arrayAddress + ((long) index) * Integer.BYTES); } @Override @@ -249,9 +249,9 @@ public void serialize(IncrementalIndexRow incrementalIndexRow, OakScopedWriteBuf rowIndex = rowIndexGenerator.getAndIncrement(); incrementalIndexRow.setRowIndex(rowIndex); } - UnsafeUtils.putLong(address + TIME_STAMP_OFFSET, timestamp); - UnsafeUtils.putInt(address + DIMS_LENGTH_OFFSET, dimsLength); - UnsafeUtils.putInt(address + ROW_INDEX_OFFSET, rowIndex); + DirectUtils.putLong(address + TIME_STAMP_OFFSET, timestamp); + DirectUtils.putInt(address + DIMS_LENGTH_OFFSET, dimsLength); + DirectUtils.putInt(address + ROW_INDEX_OFFSET, rowIndex); long dimsAddress = address + DIMS_OFFSET; // the index for writing the int arrays of the string-dim (after all the dims' data) @@ -263,25 +263,25 @@ public void serialize(IncrementalIndexRow incrementalIndexRow, OakScopedWriteBuf boolean isDimHaveValue = dimValueType != null; int dimValueTypeID = isDimHaveValue ? dimValueType.ordinal() : NULL_DIM; - UnsafeUtils.putInt(dimsAddress + DIM_VALUE_TYPE_OFFSET, dimValueTypeID); + DirectUtils.putInt(dimsAddress + DIM_VALUE_TYPE_OFFSET, dimValueTypeID); if (isDimHaveValue) { switch (dimValueType) { case FLOAT: - UnsafeUtils.putFloat(dimsAddress + DIM_DATA_OFFSET, (Float) dim); + DirectUtils.putFloat(dimsAddress + DIM_DATA_OFFSET, (Float) dim); break; case DOUBLE: - UnsafeUtils.putDouble(dimsAddress + DIM_DATA_OFFSET, (Double) dim); + DirectUtils.putDouble(dimsAddress + DIM_DATA_OFFSET, (Double) dim); break; case LONG: - UnsafeUtils.putLong(dimsAddress + DIM_DATA_OFFSET, (Long) dim); + DirectUtils.putLong(dimsAddress + DIM_DATA_OFFSET, (Long) dim); break; case STRING: int[] arr = (int[]) dim; int length = arr.length; - UnsafeUtils.putInt(dimsAddress + STRING_DIM_ARRAY_POS_OFFSET, stringDimArraysPos); - UnsafeUtils.putInt(dimsAddress + STRING_DIM_ARRAY_LENGTH_OFFSET, length); - long lengthBytes = UnsafeUtils.copyFromArray(arr, address + stringDimArraysPos, length); + DirectUtils.putInt(dimsAddress + STRING_DIM_ARRAY_POS_OFFSET, stringDimArraysPos); + DirectUtils.putInt(dimsAddress + STRING_DIM_ARRAY_LENGTH_OFFSET, length); + long lengthBytes = DirectUtils.copyFromArray(arr, address + stringDimArraysPos, length); stringDimArraysPos += (int) lengthBytes; break; } @@ -325,6 +325,13 @@ public int calculateSize(IncrementalIndexRow incrementalIndexRow) return sizeInBytes; } + + @Override + public int calculateHash(IncrementalIndexRow incrementalIndexRow) + { + // This method should not be called. + throw new UnsupportedOperationException(); + } } public static class Comparator implements OakComparator From 14247ce941f732cf75f1a0097d3af49df7cbd554 Mon Sep 17 00:00:00 2001 From: Liran Funaro Date: Wed, 12 Jan 2022 14:40:14 +0200 Subject: [PATCH 26/33] Fix doc --- .../druid/segment/incremental/oak/OakIncrementalIndexSpec.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndexSpec.java b/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndexSpec.java index d5be1190fd09..a3bdce8c34f7 100644 --- a/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndexSpec.java +++ b/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndexSpec.java @@ -28,8 +28,7 @@ import javax.annotation.Nullable; /** - * Since the off-heap incremental index is not yet supported in production ingestion, we define its spec here only - * for testing purposes. + * Oak incremental index spec (describes the in-memory indexing method for data ingestion). */ public class OakIncrementalIndexSpec implements AppendableIndexSpec { From 530a9a6de9b485707e516fc452e14dd266736a39 Mon Sep 17 00:00:00 2001 From: Liran Funaro Date: Thu, 27 Jan 2022 15:45:04 +0200 Subject: [PATCH 27/33] Update the latest release of OAk --- extensions-contrib/oak-incremental-index/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions-contrib/oak-incremental-index/pom.xml b/extensions-contrib/oak-incremental-index/pom.xml index 203019400cce..6ffbbed6075c 100644 --- a/extensions-contrib/oak-incremental-index/pom.xml +++ b/extensions-contrib/oak-incremental-index/pom.xml @@ -37,7 +37,7 @@ com.yahoo.oak oak - 0.2.4-SNAPSHOT + 0.2.4 From 998cca36ee3b01002bca94306ba2823ea8a5bee4 Mon Sep 17 00:00:00 2001 From: Liran Funaro Date: Sun, 30 Jan 2022 13:59:09 +0200 Subject: [PATCH 28/33] Fix groupId --- benchmarks/pom.xml | 2 +- extensions-contrib/oak-incremental-index/pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/benchmarks/pom.xml b/benchmarks/pom.xml index f98b2cdd569a..ac2eb41f102d 100644 --- a/benchmarks/pom.xml +++ b/benchmarks/pom.xml @@ -187,7 +187,7 @@ test - org.apache.druid.extensions + org.apache.druid.extensions.contrib oak-incremental-index ${project.parent.version} test diff --git a/extensions-contrib/oak-incremental-index/pom.xml b/extensions-contrib/oak-incremental-index/pom.xml index 6ffbbed6075c..0e58d94af1ce 100644 --- a/extensions-contrib/oak-incremental-index/pom.xml +++ b/extensions-contrib/oak-incremental-index/pom.xml @@ -21,7 +21,7 @@ 4.0.0 - org.apache.druid.extensions + org.apache.druid.extensions.contrib oak-incremental-index oak-incremental-index Druid incremental-index based on Oak lib https://github.com/yahoo/Oak From b6ee2b0517e6b422a009ad8ce5c567c7b20a65e0 Mon Sep 17 00:00:00 2001 From: Liran Funaro Date: Sun, 30 Jan 2022 17:19:05 +0200 Subject: [PATCH 29/33] Add extension docs --- distribution/pom.xml | 2 + .../oak-incremental-index.md | 124 ++++++++++++++++++ docs/development/extensions.md | 1 + .../oak-incremental-index/README.md | 38 ++++++ .../incremental/oak/OakIncrementalIndex.java | 19 ++- website/.spelling | 1 + website/sidebars.json | 1 + 7 files changed, 176 insertions(+), 10 deletions(-) create mode 100644 docs/development/extensions-contrib/oak-incremental-index.md create mode 100644 extensions-contrib/oak-incremental-index/README.md diff --git a/distribution/pom.xml b/distribution/pom.xml index 41c56dbac7bd..7cc592c66fa4 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -616,6 +616,8 @@ org.apache.druid.extensions.contrib:aliyun-oss-extensions -c org.apache.druid.extensions.contrib:opentelemetry-emitter + -c + org.apache.druid.extensions.contrib:oak-incremental-index diff --git a/docs/development/extensions-contrib/oak-incremental-index.md b/docs/development/extensions-contrib/oak-incremental-index.md new file mode 100644 index 000000000000..cb4805c16e95 --- /dev/null +++ b/docs/development/extensions-contrib/oak-incremental-index.md @@ -0,0 +1,124 @@ +--- +id: oak-incremental-index +title: "Oak Incremental Index" +--- + + + + +## Overview +This extension improves the CPU and memory efficiency of Druid's ingestion. +The same performance is achieved with 60% less memory and 50% less CPU time. +Ingestion-throughput was nearly doubled with the same memory budget, +and throughput was increased by 75% with the same CPU budget. +Full details of the experimental setup and results are available [here](https://github.com/liran-funaro/druid/wiki/Evaluation). + +It uses [OakMap open source library](https://github.com/yahoo/Oak) to store the keys and values outside the JVM heap. + +#### Main enhancements provided by this extension: +1. **Resource efficiency**: Use less CPU and RAM for the same performance +2. **Performance**: Improving performance by using more concurrent workers with the same resource allocation + +### Installation +Use the [pull-deps](../../operations/pull-deps.md) tool included with Druid to install this [extension](../../development/extensions.md#community-extensions) on all Druid middle-manager nodes. + +```bash +java -classpath "/lib/*" org.apache.druid.cli.Main tools pull-deps -c org.apache.druid.extensions.contrib:oak-incremental-index +``` + +### Enabling +After installation, to enable this extension, just add `oak-incremental-index` to `druid.extensions.loadList` in the +middle-manager's `runtime.properties` file and then restart its nodes. + +For example: +```bash +druid.extensions.loadList=["oak-incremental-index"] +``` + +## Usage +Some workloads may benefit from this extension, but others may perform better with the on-heap implementation +(despite consuming more RAM than the Oak implementation). +Hence, the user can choose which incremental-index implementation to use, depending on its needs. + +By default, the built-in on-heap incremental index is used. +After enabling the extension, the user must modify the [`ingestion spec`](../../ingestion/ingestion-spec.md) +to use Oak incremental index. +Namely, modify [`appendable index spec`](#appendableindexspec) +under [`tuning config`](../../ingestion/ingestion-spec.md#tuningconfig) as follows. + +### `spec` +All properties in the [`ingestion spec`](../../ingestion/ingestion-spec.md) remains as before. +However, the user need to pay attention to the following configurations under [`tuning config`](../../ingestion/ingestion-spec.md#tuningconfig). + +| Field | Description | Default | +|-----------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------| +| `appendableIndexSpec` | Tune which incremental index to use. See table [below](#appendableindexspec) for more information. | See table below | +| `maxRowsInMemory` | The maximum number of records to store in memory before persisting to disk. Note that this is the number of rows post-rollup, and so it may not be equal to the number of input records. Ingested records will be persisted to disk when either `maxRowsInMemory` or `maxBytesInMemory` are reached (whichever happens first), so they both determines the memory usage of the ingestion task. | `1000000` | +| `maxBytesInMemory` | The maximum aggregate size of records, in bytes, to store in memory before persisting. For oak-incremental-index, this accurately limits the memory usage, but for the default on-heap implementation, this is based on a rough estimate of memory usage. Ingested records will be persisted to disk when either `maxRowsInMemory` or `maxBytesInMemory` are reached (whichever happens first), so they both determines the memory usage of the ingestion task. `maxBytesInMemory` also includes heap usage of artifacts created from intermediary persists. This means that after every persist, the amount of `maxBytesInMemory` until next persist will decrease, and task will fail when the sum of bytes of all intermediary persisted artifacts exceeds `maxBytesInMemory`.

Setting `maxBytesInMemory` to `-1` disables this check, meaning Druid will rely entirely on `maxRowsInMemory` to control memory usage. Setting it to zero (default) means the default value will be used (see defaults per index type).

Note that for the on-heap implementation, the estimate of memory usage is designed to be an overestimate, and can be especially high when using complex ingest-time aggregators, including sketches. If this causes your indexing workloads to persist to disk too often, you can set `maxBytesInMemory` to `-1` and rely on `maxRowsInMemory` instead.

When using this extension, the `maxBytesInMemory` should be set according to your machine's memory limit to ensure Druid doesn't run into allocation problems. Specifically, take the available memory on your machine after deducting the JVM heap space and the off-heap buffers space, and then divide by two so that we'll have room for both the ingested index and the flushed one. | `onheap`: one-sixth of JVM heap size
`oak`: half of the direct memory limit | +| `skipBytesInMemoryOverheadCheck` | The calculation of `maxBytesInMemory` takes into account overhead objects created during ingestion and each intermediate persist. Setting this to true can exclude the bytes of these overhead objects from `maxBytesInMemory` check.

Note that the oak incremental index is stored in a different memory area than the overhead objects. As a result, it doesn't make sense to subject them to the same restrictions. Thus, setting this value to `true` is recommended for this extension. | false | +| Other properties | See [`tuning config`](../../ingestion/ingestion-spec.md#tuningconfig) for more information. || + +### `appendableIndexSpec` +The user can choose between the built-in on-heap incremental index and this extension. + +| Field | Description | Default | +|--------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------| +| `type` | Each in-memory index has its own tuning type code. You must specify the type code that matches your in-memory index. Available options are `onheap` and `oak`. | `onheap` | + +On-heap implementations have no additional parameters. The following are the parameters for this extension. +For most use cases, the defaults work well, but if there are issues, these can be adjusted. + +| Field | Description | Default | +|------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------| +| `type` | `oak` | | +| `oakBlockSize` | OakMap stores its data outside the JVM heap in memory blocks. Larger blocks consolidate allocations and reduce overhead, but can also waste more memory if not fully utilized. The default has a reasonable balance between performance and memory usage when tested in a batch ingestion scenario. | `8MiB` | +| `oakMaxMemoryCapacity` | OakMap maintains its memory blocks with an internal data-structure. Structure size is roughly `oakMaxMemoryCapacity/oakBlockSize`. We set this number to a large enough yet reasonable value so that this structure does not consume too much memory. | `32GiB` | +| `oakChunkMaxItems` | OakMap maintains its entries in small chunks. Using larger chunks reduces the number of on-heap objects, but may incur more overhead when balancing the entries between chunks. The default showed the best performance in batch ingestion scenarios. | `256` | + +# Example +The following tuning configuration is recommended for this extension: +```json +"tuningConfig": { + , + "maxRowsInMemory": 1000000, + "maxBytesInMemory": -1, + "skipBytesInMemoryOverheadCheck": true, + "appendableIndexSpec": { + "type": "oak" + } +} +``` + +The following is an example tuning configuration that sets all the common properties: +```json +"tuningConfig": { + , + "maxRowsInMemory": 1000000, + "maxBytesInMemory": -1, + "skipBytesInMemoryOverheadCheck": true, + "appendableIndexSpec": { + "type": "oak", + "oakMaxMemoryCapacity": 34359738368, + "oakBlockSize": 8388608, + "oakChunkMaxItems": 256 + } +} +``` + diff --git a/docs/development/extensions.md b/docs/development/extensions.md index 49f61c8d7467..34810d47d858 100644 --- a/docs/development/extensions.md +++ b/docs/development/extensions.md @@ -95,6 +95,7 @@ All of these community extensions can be downloaded using [pull-deps](../operati |druid-tdigestsketch|Support for approximate sketch aggregators based on [T-Digest](https://github.com/tdunning/t-digest)|[link](../development/extensions-contrib/tdigestsketch-quantiles.md)| |gce-extensions|GCE Extensions|[link](../development/extensions-contrib/gce-extensions.md)| |prometheus-emitter|Exposes [Druid metrics](../operations/metrics.md) for Prometheus server collection (https://prometheus.io/)|[link](./extensions-contrib/prometheus.md)| +|oak-incremental-index|Ingestion using [OakMap open source library](https://github.com/yahoo/Oak) to store the keys and values outside the JVM heap.|[link](./extensions-contrib/oak-incremental-index.md)| ## Promoting community extensions to core extensions diff --git a/extensions-contrib/oak-incremental-index/README.md b/extensions-contrib/oak-incremental-index/README.md new file mode 100644 index 000000000000..f45da37d616b --- /dev/null +++ b/extensions-contrib/oak-incremental-index/README.md @@ -0,0 +1,38 @@ + + +# Oak Incremental Index +This extension improves the CPU and memory efficiency of Druid's ingestion. +The same performance is achieved with 60% less memory and 50% less CPU time. +Ingestion-throughput was nearly doubled with the same memory budget, +and throughput was increased by 75% with the same CPU budget. +Full details of the experimental setup and results are available [here](https://github.com/liran-funaro/druid/wiki/Evaluation). + +It uses [OakMap open source library](https://github.com/yahoo/Oak) to store the keys and values outside the JVM heap. + +# Documentation +More information can be found in the [extension documentation](../../docs/development/extensions-contrib/oak-incremental-index.md). + +# Credits +This module is a result of feedback and work done by following people. + +* https://github.com/liran-funaro +* https://github.com/sanastas +* https://github.com/ebortnik +* https://github.com/eshcar diff --git a/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndex.java b/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndex.java index 2a709fc59ab0..cdf1e64708f1 100644 --- a/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndex.java +++ b/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndex.java @@ -461,20 +461,19 @@ public void clear() */ public static class Builder extends AppendableIndexBuilder { - // OakMap stores its data in off-heap memory blocks. Larger blocks consolidates allocations - // and reduce its overhead, but has the potential to waste more memory if the block is not fully utilized. - // The default is 8 MiB as it has a good balance between performance and memory usage in tested - // batch ingestion scenario. + // OakMap stores its data outside the JVM heap in memory blocks. Larger blocks consolidate allocations and reduce + // overhead, but can also waste more memory if not fully utilized. The default has a reasonable balance between + // performance and memory usage when tested in a batch ingestion scenario. public static final int DEFAULT_OAK_BLOCK_SIZE = 8 * (1 << 20); - // OakMap has a predefined maximal capacity, that allows it to minimize the internal data-structure - // for maintaining off-heap memory blocks. - // The default is arbirtrary large number (32 GiB) that won't limit the users. + // OakMap maintains its memory blocks with an internal data-structure. Structure size is roughly + // `oakMaxMemoryCapacity/oakBlockSize`. We set this number to a large enough yet reasonable value so that this + // structure does not consume too much memory. public static final long DEFAULT_OAK_MAX_MEMORY_CAPACITY = 32L * (1L << 30); - // OakMap internal data-structure maintains its entries in small chunks. Larger chunks reduces the number of - // on-heap objects, but might incure more overhead when balancing the entries between the chunks. - // The default is 256 as it showed best performance in tested batch ingestion scenario. + // OakMap maintains its entries in small chunks. Using larger chunks reduces the number of on-heap objects, but may + // incur more overhead when balancing the entries between chunks. The default showed the best performance in batch + // ingestion scenarios. public static final int DEFAULT_OAK_CHUNK_MAX_ITEMS = 256; public long oakMaxMemoryCapacity = DEFAULT_OAK_MAX_MEMORY_CAPACITY; diff --git a/website/.spelling b/website/.spelling index 54fb7d01a667..b1e1dea736a8 100644 --- a/website/.spelling +++ b/website/.spelling @@ -1993,3 +1993,4 @@ protobuf Golang multiValueHandling _n_ +OakMap diff --git a/website/sidebars.json b/website/sidebars.json index b51f13638afc..cc49d848c586 100644 --- a/website/sidebars.json +++ b/website/sidebars.json @@ -250,6 +250,7 @@ "development/extensions-contrib/gce-extensions", "development/extensions-contrib/aliyun-oss", "development/extensions-contrib/prometheus", + "development/extensions-contrib/oak-incremental-index", "operations/kubernetes", "querying/hll-old", "querying/select-query", From d209564960044a30669432501895af3d9ae4f978 Mon Sep 17 00:00:00 2001 From: Liran Funaro Date: Mon, 31 Jan 2022 15:25:45 +0200 Subject: [PATCH 30/33] Fix Oak license --- licenses.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/licenses.yaml b/licenses.yaml index e1e087600c76..dfb16b87f9d3 100644 --- a/licenses.yaml +++ b/licenses.yaml @@ -5024,8 +5024,8 @@ libraries: name: oak license_category: binary -version: 0.2.3 -module: druid-processing +version: 0.2.4 +module: extensions/oak-incremental-index license_name: Apache License version 2.0 libraries: - com.yahoo.oak: oak From 5bd946c72ed56999cf0398c17d19b5815c1852e4 Mon Sep 17 00:00:00 2001 From: Liran Funaro Date: Tue, 1 Feb 2022 20:04:09 +0200 Subject: [PATCH 31/33] Change default maxBytesInMemory and change docs accordingly. --- .../oak-incremental-index.md | 41 +++++++++++++++---- .../oak/OakIncrementalIndexSpec.java | 13 +++--- 2 files changed, 38 insertions(+), 16 deletions(-) diff --git a/docs/development/extensions-contrib/oak-incremental-index.md b/docs/development/extensions-contrib/oak-incremental-index.md index cb4805c16e95..8601c97b6d1a 100644 --- a/docs/development/extensions-contrib/oak-incremental-index.md +++ b/docs/development/extensions-contrib/oak-incremental-index.md @@ -52,6 +52,29 @@ For example: druid.extensions.loadList=["oak-incremental-index"] ``` +### Resource Configurations +Since Oak allocates its keys/values directly, it is not subject to the JVM's on/off-heap limitations. +Despite this, we have to respect runtime resource limits if they aren't specified by the ingestion specs. +To ensure that the Oak index doesn't use more resources than available, we use the same default `maxBytesInMemory` as the on-heap index, i.e., 1/6 of the maximal heap size. +It assumes that the middle-manager is configured correctly according to the machine's resources. + +As the middle-manager resource configuration typically consumes all the instance memory, we need to reduce the heap size in order to accommodate the Oak index. +This can be achieved by ensuring the minimal heap size of the ingestion task is 2/3 (or less) of the current maximal heap size. +Generally, smaller heap sizes lead to greater resource efficiency. + +If, for example, the middle-manager's `runtime.properties` file had the following configuration: +```properties +druid.indexer.runner.javaOpts=-Xms3g -Xmx3g +``` +It is suggested to change it to: +```properties +druid.indexer.runner.javaOpts=-Xms2g -Xmx3g +``` +If you'd like to maximize resource efficiency, try: +```properties +druid.indexer.runner.javaOpts=-Xms256m -Xmx3g +``` + ## Usage Some workloads may benefit from this extension, but others may perform better with the on-heap implementation (despite consuming more RAM than the Oak implementation). @@ -67,13 +90,13 @@ under [`tuning config`](../../ingestion/ingestion-spec.md#tuningconfig) as follo All properties in the [`ingestion spec`](../../ingestion/ingestion-spec.md) remains as before. However, the user need to pay attention to the following configurations under [`tuning config`](../../ingestion/ingestion-spec.md#tuningconfig). -| Field | Description | Default | -|-----------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------| -| `appendableIndexSpec` | Tune which incremental index to use. See table [below](#appendableindexspec) for more information. | See table below | -| `maxRowsInMemory` | The maximum number of records to store in memory before persisting to disk. Note that this is the number of rows post-rollup, and so it may not be equal to the number of input records. Ingested records will be persisted to disk when either `maxRowsInMemory` or `maxBytesInMemory` are reached (whichever happens first), so they both determines the memory usage of the ingestion task. | `1000000` | -| `maxBytesInMemory` | The maximum aggregate size of records, in bytes, to store in memory before persisting. For oak-incremental-index, this accurately limits the memory usage, but for the default on-heap implementation, this is based on a rough estimate of memory usage. Ingested records will be persisted to disk when either `maxRowsInMemory` or `maxBytesInMemory` are reached (whichever happens first), so they both determines the memory usage of the ingestion task. `maxBytesInMemory` also includes heap usage of artifacts created from intermediary persists. This means that after every persist, the amount of `maxBytesInMemory` until next persist will decrease, and task will fail when the sum of bytes of all intermediary persisted artifacts exceeds `maxBytesInMemory`.

Setting `maxBytesInMemory` to `-1` disables this check, meaning Druid will rely entirely on `maxRowsInMemory` to control memory usage. Setting it to zero (default) means the default value will be used (see defaults per index type).

Note that for the on-heap implementation, the estimate of memory usage is designed to be an overestimate, and can be especially high when using complex ingest-time aggregators, including sketches. If this causes your indexing workloads to persist to disk too often, you can set `maxBytesInMemory` to `-1` and rely on `maxRowsInMemory` instead.

When using this extension, the `maxBytesInMemory` should be set according to your machine's memory limit to ensure Druid doesn't run into allocation problems. Specifically, take the available memory on your machine after deducting the JVM heap space and the off-heap buffers space, and then divide by two so that we'll have room for both the ingested index and the flushed one. | `onheap`: one-sixth of JVM heap size
`oak`: half of the direct memory limit | -| `skipBytesInMemoryOverheadCheck` | The calculation of `maxBytesInMemory` takes into account overhead objects created during ingestion and each intermediate persist. Setting this to true can exclude the bytes of these overhead objects from `maxBytesInMemory` check.

Note that the oak incremental index is stored in a different memory area than the overhead objects. As a result, it doesn't make sense to subject them to the same restrictions. Thus, setting this value to `true` is recommended for this extension. | false | -| Other properties | See [`tuning config`](../../ingestion/ingestion-spec.md#tuningconfig) for more information. || +| Field | Description | Default | +|----------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------| +| `appendableIndexSpec` | Tune which incremental index to use. See table [below](#appendableindexspec) for more information. | See table below | +| `maxRowsInMemory` | The maximum number of records to store in memory before persisting to disk. Note that this is the number of rows post-rollup, and so it may not be equal to the number of input records. Ingested records will be persisted to disk when either `maxRowsInMemory` or `maxBytesInMemory` are reached (whichever happens first), so they both determines the memory usage of the ingestion task. | `1000000` | +| `maxBytesInMemory` | The maximum aggregate size of records, in bytes, to store in memory before persisting. For oak-incremental-index, this accurately limits the memory usage, but for the default on-heap implementation, this is based on a rough estimate of memory usage. Ingested records will be persisted to disk when either `maxRowsInMemory` or `maxBytesInMemory` are reached (whichever happens first), so they both determines the memory usage of the ingestion task. `maxBytesInMemory` also includes heap usage of artifacts created from intermediary persists. This means that after every persist, the amount of `maxBytesInMemory` until next persist will decrease, and task will fail when the sum of bytes of all intermediary persisted artifacts exceeds `maxBytesInMemory`.

Setting `maxBytesInMemory` to `-1` disables this check, meaning Druid will rely entirely on `maxRowsInMemory` to control memory usage. Setting it to zero (default) means the default value will be used (see on the right).

Note that for the on-heap implementation, the estimate of memory usage is designed to be an overestimate, and can be especially high when using complex ingest-time aggregators, including sketches. If this causes your indexing workloads to persist to disk too often, you can set `maxBytesInMemory` to `-1` and rely on `maxRowsInMemory` instead.

When using this extension, the `maxBytesInMemory` should be set according to your machine's memory limit to ensure Druid doesn't run into allocation problems. Specifically, take the available memory on your machine after deducting the JVM heap space, the off-heap buffers space, and memory used for other process on the machine, and then divide by two so that we'll have room for both the ingested index and the flushed one. | one-sixth of JVM heap size | +| `skipBytesInMemoryOverheadCheck` | The calculation of `maxBytesInMemory` takes into account overhead objects created during ingestion and each intermediate persist. Setting this to true can exclude the bytes of these overhead objects from `maxBytesInMemory` check.

Note that the oak incremental index is stored in a different memory area than the overhead objects. As a result, it doesn't make sense to subject them to the same restrictions. Thus, setting this value to `true` is recommended for this extension. | false | +| Other properties | See [`tuning config`](../../ingestion/ingestion-spec.md#tuningconfig) for more information. || ### `appendableIndexSpec` The user can choose between the built-in on-heap incremental index and this extension. @@ -98,7 +121,7 @@ The following tuning configuration is recommended for this extension: "tuningConfig": { , "maxRowsInMemory": 1000000, - "maxBytesInMemory": -1, + "maxBytesInMemory": 0, "skipBytesInMemoryOverheadCheck": true, "appendableIndexSpec": { "type": "oak" @@ -111,7 +134,7 @@ The following is an example tuning configuration that sets all the common proper "tuningConfig": { , "maxRowsInMemory": 1000000, - "maxBytesInMemory": -1, + "maxBytesInMemory": 0, "skipBytesInMemoryOverheadCheck": true, "appendableIndexSpec": { "type": "oak", diff --git a/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndexSpec.java b/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndexSpec.java index a3bdce8c34f7..b7dc7224fbd6 100644 --- a/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndexSpec.java +++ b/extensions-contrib/oak-incremental-index/src/main/java/org/apache/druid/segment/incremental/oak/OakIncrementalIndexSpec.java @@ -84,12 +84,11 @@ public OakIncrementalIndex.Builder builder() @Override public long getDefaultMaxBytesInMemory() { - // Oak allocates its keys/values directly so the JVM off-heap limitations does not apply on it. - // Yet, we want to respect these values if the user did not specify any specific limitation. - // In the realtime node, the entire JVM's direct memory is utilized for ingestion and persist operations. - // But maxBytesInMemory only refers to the active index size and not to the index being flushed to disk and the - // persist-buffer. - // To account for that, we set default to 1/2 of the max jvm's direct memory. - return JvmUtils.getRuntimeInfo().getDirectMemorySizeBytes() / 2; + // Since Oak allocates its keys/values directly, it is not subject to the JVM's on/off-heap limitations. + // Despite this, we have to respect runtime resource limits if they aren't specified by the ingestion specs. + // To ensure that the Oak index doesn't use more resources than available, we use the same default + // `maxBytesInMemory` as the on-heap index, i.e., 1/6 of the maximal heap size. + // It assumes that the middle-manager is configured correctly according to the machine's resources. + return JvmUtils.getRuntimeInfo().getMaxHeapSizeBytes() / 6; } } From 9c247d998e5bef5d566d9f134bdc65a22d480824 Mon Sep 17 00:00:00 2001 From: Liran Funaro Date: Thu, 3 Mar 2022 15:21:55 +0200 Subject: [PATCH 32/33] Upgrade Oak version --- extensions-contrib/oak-incremental-index/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions-contrib/oak-incremental-index/pom.xml b/extensions-contrib/oak-incremental-index/pom.xml index 0e58d94af1ce..3247fb3b49fc 100644 --- a/extensions-contrib/oak-incremental-index/pom.xml +++ b/extensions-contrib/oak-incremental-index/pom.xml @@ -37,7 +37,7 @@ com.yahoo.oak oak - 0.2.4 + 0.2.5 From 30f92192fa339cb9a9dbb4b5da3afb087d6b5f91 Mon Sep 17 00:00:00 2001 From: Liran Funaro Date: Thu, 3 Mar 2022 16:49:13 +0200 Subject: [PATCH 33/33] Upgrade Oak version license --- licenses.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/licenses.yaml b/licenses.yaml index 7de4cf658166..08f9a27e94b5 100644 --- a/licenses.yaml +++ b/licenses.yaml @@ -5029,7 +5029,7 @@ libraries: name: oak license_category: binary -version: 0.2.4 +version: 0.2.5 module: extensions/oak-incremental-index license_name: Apache License version 2.0 libraries: