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}