001/**
002 *
003 * Licensed to the Apache Software Foundation (ASF) under one
004 * or more contributor license agreements.  See the NOTICE file
005 * distributed with this work for additional information
006 * regarding copyright ownership.  The ASF licenses this file
007 * to you under the Apache License, Version 2.0 (the
008 * "License"); you may not use this file except in compliance
009 * with the License.  You may obtain a copy of the License at
010 *
011 *     http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing, software
014 * distributed under the License is distributed on an "AS IS" BASIS,
015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016 * See the License for the specific language governing permissions and
017 * limitations under the License.
018 */
019package org.apache.hadoop.hbase.regionserver;
020
021import java.io.IOException;
022import java.nio.ByteBuffer;
023import org.apache.hadoop.hbase.ByteBufferKeyValue;
024import org.apache.hadoop.hbase.Cell;
025import org.apache.hadoop.hbase.CellComparator;
026import org.apache.hadoop.hbase.CellUtil;
027import org.apache.hadoop.hbase.ExtendedCell;
028import org.apache.hadoop.hbase.KeyValue;
029import org.apache.hadoop.hbase.KeyValueUtil;
030import org.apache.hadoop.hbase.util.ByteBufferUtils;
031import org.apache.hadoop.hbase.util.ClassSize;
032import org.apache.yetus.audience.InterfaceAudience;
033
034
035/**
036 * CellChunkImmutableSegment extends the API supported by a {@link Segment},
037 * and {@link ImmutableSegment}. This immutable segment is working with CellSet with
038 * CellChunkMap delegatee.
039 */
040@InterfaceAudience.Private
041public class CellChunkImmutableSegment extends ImmutableSegment {
042
043  public static final long DEEP_OVERHEAD_CCM =
044      ImmutableSegment.DEEP_OVERHEAD + ClassSize.CELL_CHUNK_MAP;
045  public static final float INDEX_CHUNK_UNUSED_SPACE_PRECENTAGE = 0.1f;
046
047  /////////////////////  CONSTRUCTORS  /////////////////////
048  /**------------------------------------------------------------------------
049   * C-tor to be used when new CellChunkImmutableSegment is built as a result of compaction/merge
050   * of a list of older ImmutableSegments.
051   * The given iterator returns the Cells that "survived" the compaction.
052   */
053  protected CellChunkImmutableSegment(CellComparator comparator, MemStoreSegmentsIterator iterator,
054      MemStoreLAB memStoreLAB, int numOfCells, MemStoreCompactionStrategy.Action action) {
055    super(null, comparator, memStoreLAB); // initialize the CellSet with NULL
056    long indexOverhead = DEEP_OVERHEAD_CCM;
057    // memStoreLAB cannot be null in this class
058    boolean onHeap = getMemStoreLAB().isOnHeap();
059    // initiate the heapSize with the size of the segment metadata
060    if(onHeap) {
061      incMemStoreSize(0, indexOverhead, 0);
062    } else {
063      incMemStoreSize(0, 0, indexOverhead);
064    }
065    // build the new CellSet based on CellArrayMap and update the CellSet of the new Segment
066    initializeCellSet(numOfCells, iterator, action);
067  }
068
069  /**------------------------------------------------------------------------
070   * C-tor to be used when new CellChunkImmutableSegment is built as a result of flattening
071   * of CSLMImmutableSegment
072   * The given iterator returns the Cells that "survived" the compaction.
073   */
074  protected CellChunkImmutableSegment(CSLMImmutableSegment segment,
075      MemStoreSizing memstoreSizing, MemStoreCompactionStrategy.Action action) {
076    super(segment); // initiailize the upper class
077    long indexOverhead = -CSLMImmutableSegment.DEEP_OVERHEAD_CSLM + DEEP_OVERHEAD_CCM;
078    // memStoreLAB cannot be null in this class
079    boolean onHeap = getMemStoreLAB().isOnHeap();
080    // initiate the heapSize with the size of the segment metadata
081    if(onHeap) {
082      incMemStoreSize(0, indexOverhead, 0);
083    } else {
084      incMemStoreSize(0, -CSLMImmutableSegment.DEEP_OVERHEAD_CSLM, DEEP_OVERHEAD_CCM);
085    }
086    int numOfCells = segment.getCellsCount();
087    // build the new CellSet based on CellChunkMap
088    reinitializeCellSet(numOfCells, segment.getScanner(Long.MAX_VALUE), segment.getCellSet(),
089        action);
090    // arrange the meta-data size, decrease all meta-data sizes related to SkipList;
091    // add sizes of CellChunkMap entry, decrease also Cell object sizes
092    // (reinitializeCellSet doesn't take the care for the sizes)
093    long newSegmentSizeDelta = numOfCells*(indexEntrySize()-ClassSize.CONCURRENT_SKIPLISTMAP_ENTRY);
094    if(onHeap) {
095      incMemStoreSize(0, newSegmentSizeDelta, 0);
096      memstoreSizing.incMemStoreSize(0, newSegmentSizeDelta, 0);
097    } else {
098      incMemStoreSize(0, 0, newSegmentSizeDelta);
099      memstoreSizing.incMemStoreSize(0, 0, newSegmentSizeDelta);
100
101    }
102  }
103
104  @Override
105  protected long indexEntryOnHeapSize(boolean onHeap) {
106    if(onHeap) {
107      return indexEntrySize();
108    }
109    // else the index is allocated off-heap
110    return 0;
111  }
112
113  @Override
114  protected long indexEntryOffHeapSize(boolean offHeap) {
115    if(offHeap) {
116      return indexEntrySize();
117    }
118    // else the index is allocated on-heap
119    return 0;
120  }
121
122  @Override
123  protected long indexEntrySize() {
124    return ((long) ClassSize.CELL_CHUNK_MAP_ENTRY - KeyValue.FIXED_OVERHEAD);
125  }
126
127  @Override
128  protected boolean canBeFlattened() {
129    return false;
130  }
131
132  /////////////////////  PRIVATE METHODS  /////////////////////
133  /*------------------------------------------------------------------------*/
134  // Create CellSet based on CellChunkMap from compacting iterator
135  private void initializeCellSet(int numOfCells, MemStoreSegmentsIterator iterator,
136      MemStoreCompactionStrategy.Action action) {
137
138    int numOfCellsAfterCompaction = 0;
139    int currentChunkIdx = 0;
140    int offsetInCurentChunk = ChunkCreator.SIZEOF_CHUNK_HEADER;
141    int numUniqueKeys=0;
142    Cell prev = null;
143    Chunk[] chunks = allocIndexChunks(numOfCells);
144    while (iterator.hasNext()) {        // the iterator hides the elimination logic for compaction
145      boolean alreadyCopied = false;
146      Cell c = iterator.next();
147      numOfCellsAfterCompaction++;
148      assert(c instanceof ExtendedCell);
149      if (((ExtendedCell)c).getChunkId() == ExtendedCell.CELL_NOT_BASED_ON_CHUNK) {
150        // CellChunkMap assumes all cells are allocated on MSLAB.
151        // Therefore, cells which are not allocated on MSLAB initially,
152        // are copied into MSLAB here.
153        c = copyCellIntoMSLAB(c);
154        alreadyCopied = true;
155      }
156      if (offsetInCurentChunk + ClassSize.CELL_CHUNK_MAP_ENTRY > chunks[currentChunkIdx].size) {
157        currentChunkIdx++;              // continue to the next index chunk
158        offsetInCurentChunk = ChunkCreator.SIZEOF_CHUNK_HEADER;
159      }
160      if (action == MemStoreCompactionStrategy.Action.COMPACT && !alreadyCopied) {
161        // for compaction copy cell to the new segment (MSLAB copy)
162        c = maybeCloneWithAllocator(c, false);
163      }
164      offsetInCurentChunk = // add the Cell reference to the index chunk
165          createCellReference((ByteBufferKeyValue)c, chunks[currentChunkIdx].getData(),
166              offsetInCurentChunk);
167      // the sizes still need to be updated in the new segment
168      // second parameter true, because in compaction/merge the addition of the cell to new segment
169      // is always successful
170      updateMetaInfo(c, true, null); // updates the size per cell
171      if(action == MemStoreCompactionStrategy.Action.MERGE_COUNT_UNIQUE_KEYS) {
172        //counting number of unique keys
173        if (prev != null) {
174          if (!CellUtil.matchingRowColumnBytes(prev, c)) {
175            numUniqueKeys++;
176          }
177        } else {
178          numUniqueKeys++;
179        }
180      }
181      prev = c;
182    }
183    if(action == MemStoreCompactionStrategy.Action.COMPACT) {
184      numUniqueKeys = numOfCells;
185    } else if(action != MemStoreCompactionStrategy.Action.MERGE_COUNT_UNIQUE_KEYS) {
186      numUniqueKeys = CellSet.UNKNOWN_NUM_UNIQUES;
187    }
188    // build the immutable CellSet
189    CellChunkMap ccm =
190        new CellChunkMap(getComparator(), chunks, 0, numOfCellsAfterCompaction, false);
191    this.setCellSet(null, new CellSet(ccm, numUniqueKeys));  // update the CellSet of this Segment
192  }
193
194  /*------------------------------------------------------------------------*/
195  // Create CellSet based on CellChunkMap from current ConcurrentSkipListMap based CellSet
196  // (without compacting iterator)
197  // This is a service for not-flat immutable segments
198  private void reinitializeCellSet(
199      int numOfCells, KeyValueScanner segmentScanner, CellSet oldCellSet,
200      MemStoreCompactionStrategy.Action action) {
201    Cell curCell;
202    Chunk[] chunks = allocIndexChunks(numOfCells);
203
204    int currentChunkIdx = 0;
205    int offsetInCurentChunk = ChunkCreator.SIZEOF_CHUNK_HEADER;
206
207    int numUniqueKeys=0;
208    Cell prev = null;
209    try {
210      while ((curCell = segmentScanner.next()) != null) {
211        assert(curCell instanceof ExtendedCell);
212        if (((ExtendedCell)curCell).getChunkId() == ExtendedCell.CELL_NOT_BASED_ON_CHUNK) {
213          // CellChunkMap assumes all cells are allocated on MSLAB.
214          // Therefore, cells which are not allocated on MSLAB initially,
215          // are copied into MSLAB here.
216          curCell = copyCellIntoMSLAB(curCell);
217        }
218        if (offsetInCurentChunk + ClassSize.CELL_CHUNK_MAP_ENTRY > chunks[currentChunkIdx].size) {
219          // continue to the next metadata chunk
220          currentChunkIdx++;
221          offsetInCurentChunk = ChunkCreator.SIZEOF_CHUNK_HEADER;
222        }
223        offsetInCurentChunk =
224            createCellReference((ByteBufferKeyValue) curCell, chunks[currentChunkIdx].getData(),
225                offsetInCurentChunk);
226        if(action == MemStoreCompactionStrategy.Action.FLATTEN_COUNT_UNIQUE_KEYS) {
227          //counting number of unique keys
228          if (prev != null) {
229            if (!CellUtil.matchingRowColumn(prev, curCell)) {
230              numUniqueKeys++;
231            }
232          } else {
233            numUniqueKeys++;
234          }
235        }
236        prev = curCell;
237      }
238      if(action != MemStoreCompactionStrategy.Action.FLATTEN_COUNT_UNIQUE_KEYS) {
239        numUniqueKeys = CellSet.UNKNOWN_NUM_UNIQUES;
240      }
241    } catch (IOException ie) {
242      throw new IllegalStateException(ie);
243    } finally {
244      segmentScanner.close();
245    }
246
247    CellChunkMap ccm = new CellChunkMap(getComparator(), chunks, 0, numOfCells, false);
248    // update the CellSet of this Segment
249    this.setCellSet(oldCellSet, new CellSet(ccm, numUniqueKeys));
250  }
251
252  /*------------------------------------------------------------------------*/
253  // for a given cell, write the cell representation on the index chunk
254  private int createCellReference(ByteBufferKeyValue cell, ByteBuffer idxBuffer, int idxOffset) {
255    int offset = idxOffset;
256    int dataChunkID = cell.getChunkId();
257
258    offset = ByteBufferUtils.putInt(idxBuffer, offset, dataChunkID);    // write data chunk id
259    offset = ByteBufferUtils.putInt(idxBuffer, offset, cell.getOffset());          // offset
260    offset = ByteBufferUtils.putInt(idxBuffer, offset, KeyValueUtil.length(cell)); // length
261    offset = ByteBufferUtils.putLong(idxBuffer, offset, cell.getSequenceId());     // seqId
262
263    return offset;
264  }
265
266  private int calculateNumberOfChunks(int numOfCells, int chunkSize) {
267    int numOfCellsInChunk = calcNumOfCellsInChunk(chunkSize);
268    int numberOfChunks = numOfCells / numOfCellsInChunk;
269    if(numOfCells % numOfCellsInChunk != 0) { // if cells cannot be divided evenly between chunks
270      numberOfChunks++;                   // add one additional chunk
271    }
272    return numberOfChunks;
273  }
274
275  // Assuming we are going to use regular data chunks as index chunks,
276  // we check here how much free space will remain in the last allocated chunk
277  // (the least occupied one).
278  // If the percentage of its remaining free space is above the INDEX_CHUNK_UNUSED_SPACE
279  // threshold, then we will use index chunks (which are smaller) instead.
280  private ChunkCreator.ChunkType useIndexChunks(int numOfCells) {
281    int dataChunkSize = ChunkCreator.getInstance().getChunkSize();
282    int numOfCellsInChunk = calcNumOfCellsInChunk(dataChunkSize);
283    int cellsInLastChunk = numOfCells % numOfCellsInChunk;
284    if (cellsInLastChunk == 0) { // There is no free space in the last chunk and thus,
285      return ChunkCreator.ChunkType.DATA_CHUNK;               // no need to use index chunks.
286    } else {
287      int chunkSpace = dataChunkSize - ChunkCreator.SIZEOF_CHUNK_HEADER;
288      int freeSpaceInLastChunk = chunkSpace - cellsInLastChunk * ClassSize.CELL_CHUNK_MAP_ENTRY;
289      if (freeSpaceInLastChunk > INDEX_CHUNK_UNUSED_SPACE_PRECENTAGE * chunkSpace) {
290        return ChunkCreator.ChunkType.INDEX_CHUNK;
291      }
292      return ChunkCreator.ChunkType.DATA_CHUNK;
293    }
294  }
295
296  private int calcNumOfCellsInChunk(int chunkSize) {
297    int chunkSpace = chunkSize - ChunkCreator.SIZEOF_CHUNK_HEADER;
298    int numOfCellsInChunk = chunkSpace / ClassSize.CELL_CHUNK_MAP_ENTRY;
299    return numOfCellsInChunk;
300  }
301
302  private Chunk[] allocIndexChunks(int numOfCells) {
303    // Decide whether to use regular or small chunks and then
304    // calculate how many chunks we will need for index
305
306    ChunkCreator.ChunkType chunkType = useIndexChunks(numOfCells);
307    int chunkSize = ChunkCreator.getInstance().getChunkSize(chunkType);
308    int numberOfChunks = calculateNumberOfChunks(numOfCells, chunkSize);
309    // all index Chunks are allocated from ChunkCreator
310    Chunk[] chunks = new Chunk[numberOfChunks];
311    // all index Chunks are allocated from ChunkCreator
312    for (int i = 0; i < numberOfChunks; i++) {
313      chunks[i] = this.getMemStoreLAB().getNewExternalChunk(chunkType);
314    }
315    return chunks;
316  }
317
318  private Cell copyCellIntoMSLAB(Cell cell) {
319    // Take care for a special case when a cell is copied from on-heap to (probably off-heap) MSLAB.
320    // The cell allocated as an on-heap JVM object (byte array) occupies slightly different
321    // amount of memory, than when the cell serialized and allocated on the MSLAB.
322    // Here, we update the heap size of the new segment only for the difference between object and
323    // serialized size. This is a decrease of the size as serialized cell is a bit smaller.
324    // The actual size of the cell is not added yet, and will be added (only in compaction)
325    // in initializeCellSet#updateMetaInfo().
326    long oldHeapSize = heapSizeChange(cell, true);
327    long oldOffHeapSize = offHeapSizeChange(cell, true);
328    long oldCellSize = getCellLength(cell);
329    cell = maybeCloneWithAllocator(cell, true);
330    long newHeapSize = heapSizeChange(cell, true);
331    long newOffHeapSize = offHeapSizeChange(cell, true);
332    long newCellSize = getCellLength(cell);
333    long heapOverhead = newHeapSize - oldHeapSize;
334    long offHeapOverhead = newOffHeapSize - oldOffHeapSize;
335    //TODO: maybe need to update the dataSize of the region
336    incMemStoreSize(newCellSize - oldCellSize, heapOverhead, offHeapOverhead);
337    return cell;
338  }
339}