/** * Copyright (C) 2014-2016 LinkedIn Corp. (pinot-core@linkedin.com) * * Licensed 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 com.linkedin.pinot.core.io.reader.impl.v2; import com.google.common.base.Preconditions; import com.linkedin.pinot.core.io.reader.BaseSingleColumnMultiValueReader; import com.linkedin.pinot.core.io.reader.impl.FixedByteSingleValueMultiColReader; import com.linkedin.pinot.core.segment.memory.PinotDataBuffer; import com.linkedin.pinot.core.util.PinotDataCustomBitSet; import com.linkedin.pinot.core.util.SizeUtil; import java.io.IOException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Storage Layout * ============== * There will be three sections HEADER section, BITMAP and RAW DATA * CHUNK OFFSET HEADER will contain one line per chunk, each line corresponding to the start offset * and length of the chunk * BITMAP This will contain sequence of bits. The number of bits will be equal to the * totalNumberOfValues.A bit is set to 1 if its start of a new docId. The number of bits set to 1 * will be equal to the number of docs. * RAWDATA This simply has the actual multi valued data stored in sequence of int's. The number of * ints is equal to the totalNumberOfValues * We divide all the documents into groups refered to as CHUNK. Each CHUNK will * - Have the same number of documents. * - Started Offset of each CHUNK in the BITMAP will stored in the HEADER section. This is to speed * the look up. * Over all each look up will take log(NUM CHUNKS) for binary search + CHUNK to linear scan on the * bitmap to find the right offset in the raw data section */ public class FixedBitMultiValueReader extends BaseSingleColumnMultiValueReader { private static final Logger LOGGER = LoggerFactory.getLogger(FixedBitMultiValueReader.class); private static int SIZE_OF_INT = 4; private static int NUM_COLS_IN_HEADER = 1; private int PREFERRED_NUM_VALUES_PER_CHUNK = 2048; private PinotDataBuffer indexDataBuffer; private PinotDataBuffer chunkOffsetsBuffer; private PinotDataBuffer bitsetBuffer; private PinotDataBuffer rawDataBuffer; private FixedByteSingleValueMultiColReader chunkOffsetsReader; private PinotDataCustomBitSet customBitSet; private FixedBitSingleValueReader rawDataReader; private int numChunks; private int chunkOffsetHeaderSize; private int bitsetSize; private int rawDataSize; private int totalSize; private int totalNumValues; private int docsPerChunk; private int numDocs; public FixedBitMultiValueReader(PinotDataBuffer indexDataBuffer, int numDocs, int totalNumValues, int columnSizeInBits, boolean signed) { this.indexDataBuffer = indexDataBuffer; this.numDocs = numDocs; this.totalNumValues = totalNumValues; float averageValuesPerDoc = totalNumValues / numDocs; this.docsPerChunk = (int) (Math.ceil(PREFERRED_NUM_VALUES_PER_CHUNK / averageValuesPerDoc)); LOGGER.debug( " Loading index data buffer of size:{} totalDocs:{} totalNumOfEntries:{} docsPerChunk:{}", indexDataBuffer.size(), numDocs, totalNumValues, docsPerChunk); this.numChunks = (numDocs + docsPerChunk - 1) / docsPerChunk; chunkOffsetHeaderSize = numChunks * SIZE_OF_INT * NUM_COLS_IN_HEADER; bitsetSize = (totalNumValues + 7) / 8; rawDataSize = SizeUtil.computeBytesRequired(totalNumValues, columnSizeInBits, SizeUtil.BIT_UNPACK_BATCH_SIZE); totalSize = chunkOffsetHeaderSize + bitsetSize + rawDataSize; Preconditions.checkState(totalSize > 0 && totalSize < Integer.MAX_VALUE, "Total size can not exceed 2GB"); chunkOffsetsBuffer = indexDataBuffer.view(0, chunkOffsetHeaderSize); int bitsetEndPos = chunkOffsetHeaderSize + bitsetSize; bitsetBuffer = indexDataBuffer.view(chunkOffsetHeaderSize, bitsetEndPos); rawDataBuffer = indexDataBuffer.view(bitsetEndPos, bitsetEndPos+rawDataSize); chunkOffsetsReader = new FixedByteSingleValueMultiColReader(chunkOffsetsBuffer, numChunks, new int[] { SIZE_OF_INT }); customBitSet = PinotDataCustomBitSet.withDataBuffer(bitsetSize, bitsetBuffer); rawDataReader = new FixedBitSingleValueReader(rawDataBuffer, totalNumValues, columnSizeInBits, signed); } public int getTotalSize() { return totalSize; } @Override public void close() throws IOException { rawDataReader.close(); chunkOffsetsReader.close(); customBitSet.close(); indexDataBuffer.close(); chunkOffsetsBuffer = null; bitsetBuffer = null; rawDataBuffer = null; customBitSet = null; rawDataReader = null; indexDataBuffer = null; } private int computeLength(int rowOffSetStart) { long rowOffSetEnd = customBitSet.nextSetBitAfter(rowOffSetStart); if (rowOffSetEnd < 0) { return (int) (totalNumValues - rowOffSetStart); } return (int) (rowOffSetEnd - rowOffSetStart); } private int computeStartOffset(int row) { int chunkId = row / docsPerChunk; int chunkIdOffset = chunkOffsetsReader.getInt(chunkId, 0); if (row % docsPerChunk == 0) { return chunkIdOffset; } return customBitSet.findNthBitSetAfter(chunkIdOffset, row - chunkId * docsPerChunk); } @Override public int getCharArray(int row, char[] charArray) { throw new UnsupportedOperationException("Only int data type is supported in fixedbit format"); } @Override public int getShortArray(int row, short[] shortsArray) { throw new UnsupportedOperationException("Only int data type is supported in fixedbit format"); } @Override public int getIntArray(int row, int[] intArray) { int startOffset = computeStartOffset(row); int length = computeLength(startOffset); for (int i = 0; i < length; i++) { intArray[i] = rawDataReader.getInt(startOffset + i); } return length; } @Override public int getLongArray(int row, long[] longArray) { throw new UnsupportedOperationException("Only int data type is supported in fixedbit format"); } @Override public int getFloatArray(int row, float[] floatArray) { throw new UnsupportedOperationException("Only int data type is supported in fixedbit format"); } @Override public int getDoubleArray(int row, double[] doubleArray) { throw new UnsupportedOperationException("Only int data type is supported in fixedbit format"); } @Override public int getStringArray(int row, String[] stringArray) { throw new UnsupportedOperationException("Only int data type is supported in fixedbit format"); } @Override public int getBytesArray(int row, byte[][] bytesArray) { throw new UnsupportedOperationException("Only int data type is supported in fixedbit format"); } }