001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.regionserver;
019
020import java.nio.ByteBuffer;
021import java.util.Comparator;
022import org.apache.hadoop.hbase.Cell;
023import org.apache.hadoop.hbase.util.ByteBufferUtils;
024import org.apache.hadoop.hbase.util.Bytes;
025import org.apache.hadoop.hbase.util.ClassSize;
026import org.apache.yetus.audience.InterfaceAudience;
027
028/**
029 * CellChunkMap is an array of serialized representations of Cell (pointing to Chunks with full Cell
030 * data) and can be allocated both off-heap and on-heap.
031 * <p>
032 * CellChunkMap is a byte array (chunk) holding all that is needed to access a Cell, which is
033 * actually saved on another deeper chunk. Per Cell we have a reference to this deeper byte array B
034 * (chunk ID, integer), offset in bytes in B (integer), length in bytes in B (integer) and seqID of
035 * the cell (long). In order to save reference to byte array we use the Chunk's ID given by
036 * ChunkCreator.
037 * <p>
038 * The CellChunkMap memory layout on chunk A relevant to a deeper byte array B, holding the actual
039 * cell data:
040 *
041 * <pre>
042 *
043 * < header > <---------------     first Cell     -----------------> <-- second Cell ...
044 * --------------------------------------------------------------------------------------- ...
045 *  integer  | integer      | integer      | integer     | long     |
046 *  4 bytes  | 4 bytes      | 4 bytes      | 4 bytes     | 8 bytes  |
047 *  ChunkID  | chunkID of   | offset in B  | length of   | sequence |          ...
048 *  of this  | chunk B with | where Cell's | Cell's      | ID of    |
049 *  chunk A  | Cell data    | data starts  | data in B   | the Cell |
050 * --------------------------------------------------------------------------------------- ...
051 * </pre>
052 */
053@InterfaceAudience.Private
054public class CellChunkMap<T extends Cell> extends CellFlatMap<T> {
055
056  private final Chunk[] chunks; // the array of chunks, on which the index is based
057
058  // number of cell-representations in a chunk
059  // depends on the size of the chunks (may be index chunks or regular data chunks)
060  // each chunk starts with its own ID following the cells data
061  private final int numOfCellRepsInChunk;
062
063  /**
064   * C-tor for creating CellChunkMap from existing Chunk array, which must be ordered (decreasingly
065   * or increasingly according to parameter "descending")
066   * @param comparator a tool for comparing cells
067   * @param chunks     ordered array of index chunk with cell representations
068   * @param min        the index of the first cell (usually 0)
069   * @param max        number of Cells or the index of the cell after the maximal cell
070   * @param descending the order of the given array
071   */
072  public CellChunkMap(Comparator<? super T> comparator, Chunk[] chunks, int min, int max,
073    boolean descending) {
074    super(comparator, min, max, descending);
075    this.chunks = chunks;
076    if (chunks != null && chunks.length != 0 && chunks[0] != null) {
077      this.numOfCellRepsInChunk =
078        (chunks[0].size - ChunkCreator.SIZEOF_CHUNK_HEADER) / ClassSize.CELL_CHUNK_MAP_ENTRY;
079    } else { // In case the chunks array was not allocated
080      this.numOfCellRepsInChunk = 0;
081    }
082  }
083
084  /**
085   * To be used by base (CellFlatMap) class only to create a sub-CellFlatMap Should be used only to
086   * create only CellChunkMap from CellChunkMap
087   */
088  @Override
089  protected CellFlatMap<T> createSubCellFlatMap(int min, int max, boolean descending) {
090    return new CellChunkMap<>(this.comparator(), this.chunks, min, max, descending);
091  }
092
093  @Override
094  protected T getCell(int i) {
095    // get the index of the relevant chunk inside chunk array
096    int chunkIndex = (i / numOfCellRepsInChunk);
097    ByteBuffer block = chunks[chunkIndex].getData();// get the ByteBuffer of the relevant chunk
098    int j = i - chunkIndex * numOfCellRepsInChunk; // get the index of the cell-representation
099
100    // find inside the offset inside the chunk holding the index, skip bytes for chunk id
101    int offsetInBytes = ChunkCreator.SIZEOF_CHUNK_HEADER + j * ClassSize.CELL_CHUNK_MAP_ENTRY;
102
103    // find the chunk holding the data of the cell, the chunkID is stored first
104    int chunkId = ByteBufferUtils.toInt(block, offsetInBytes);
105    Chunk chunk = ChunkCreator.getInstance().getChunk(chunkId);
106    if (chunk == null) {
107      // this should not happen
108      throw new IllegalArgumentException("In CellChunkMap, cell must be associated with chunk."
109        + ". We were looking for a cell at index " + i);
110    }
111
112    // find the offset of the data of the cell, skip integer for chunkID, offset is stored second
113    int offsetOfCell = ByteBufferUtils.toInt(block, offsetInBytes + Bytes.SIZEOF_INT);
114    // find the length of the data of the cell, skip two integers for chunkID and offset,
115    // length is stored third
116    int lengthOfCell = ByteBufferUtils.toInt(block, offsetInBytes + 2 * Bytes.SIZEOF_INT);
117    // find the seqID of the cell, skip three integers for chunkID, offset, and length
118    // the seqID is plain written as part of the cell representation
119    long cellSeqID = ByteBufferUtils.toLong(block, offsetInBytes + 3 * Bytes.SIZEOF_INT);
120
121    ByteBuffer buf = chunk.getData(); // get the ByteBuffer where the cell data is stored
122    if (buf == null) {
123      // this should not happen
124      throw new IllegalArgumentException(
125        "In CellChunkMap, chunk must be associated with ByteBuffer." + " Chunk: " + chunk
126          + " Chunk ID: " + chunk.getId() + ", is from pool: " + chunk.isFromPool()
127          + ". We were looking for a cell at index " + i);
128    }
129
130    @SuppressWarnings("unchecked")
131    T cell = (T) new ByteBufferChunkKeyValue(buf, offsetOfCell, lengthOfCell, cellSeqID);
132
133    return cell;
134  }
135}