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 static org.junit.jupiter.api.Assertions.assertEquals; 021import static org.junit.jupiter.api.Assertions.assertFalse; 022import static org.junit.jupiter.api.Assertions.assertNotNull; 023import static org.junit.jupiter.api.Assertions.assertTrue; 024 025import java.lang.management.ManagementFactory; 026import java.nio.ByteBuffer; 027import java.util.Iterator; 028import java.util.NavigableMap; 029import java.util.NavigableSet; 030import java.util.SortedSet; 031import java.util.stream.Stream; 032import org.apache.hadoop.conf.Configuration; 033import org.apache.hadoop.hbase.Cell; 034import org.apache.hadoop.hbase.CellComparator; 035import org.apache.hadoop.hbase.ExtendedCell; 036import org.apache.hadoop.hbase.HBaseParameterizedTestTemplate; 037import org.apache.hadoop.hbase.KeyValue; 038import org.apache.hadoop.hbase.KeyValueUtil; 039import org.apache.hadoop.hbase.PrivateCellUtil; 040import org.apache.hadoop.hbase.io.util.MemorySizeUtil; 041import org.apache.hadoop.hbase.regionserver.ChunkCreator.ChunkType; 042import org.apache.hadoop.hbase.testclassification.RegionServerTests; 043import org.apache.hadoop.hbase.testclassification.SmallTests; 044import org.apache.hadoop.hbase.util.ByteBufferUtils; 045import org.apache.hadoop.hbase.util.Bytes; 046import org.apache.hadoop.hbase.util.ClassSize; 047import org.junit.jupiter.api.BeforeEach; 048import org.junit.jupiter.api.Tag; 049import org.junit.jupiter.api.TestTemplate; 050import org.junit.jupiter.params.provider.Arguments; 051 052@Tag(RegionServerTests.TAG) 053@Tag(SmallTests.TAG) 054@HBaseParameterizedTestTemplate(name = "{index}: chunkType={0}") 055public class TestCellFlatSet { 056 057 public static Stream<Arguments> parameters() { 058 // test with different chunk sizes 059 return Stream.of(Arguments.of("SMALL_CHUNKS"), Arguments.of("NORMAL_CHUNKS")); 060 } 061 062 private static final int NUM_OF_CELLS = 4; 063 private static final int SMALL_CHUNK_SIZE = 64; 064 private ExtendedCell[] ascCells; 065 private CellArrayMap<ExtendedCell> ascCbOnHeap; 066 private ExtendedCell[] descCells; 067 private CellArrayMap<ExtendedCell> descCbOnHeap; 068 private final static Configuration CONF = new Configuration(); 069 private KeyValue lowerOuterCell; 070 private KeyValue upperOuterCell; 071 072 private CellChunkMap<ExtendedCell> ascCCM; // for testing ascending CellChunkMap with one chunk in 073 // array 074 private CellChunkMap<ExtendedCell> descCCM; // for testing descending CellChunkMap with one chunk 075 // in array 076 private final boolean smallChunks; 077 private static ChunkCreator chunkCreator; 078 079 public TestCellFlatSet(String chunkType) { 080 long globalMemStoreLimit = 081 (long) (ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getMax() 082 * MemorySizeUtil.getGlobalMemStoreHeapPercent(CONF, false)); 083 if (chunkType.equals("NORMAL_CHUNKS")) { 084 chunkCreator = ChunkCreator.initialize(MemStoreLAB.CHUNK_SIZE_DEFAULT, false, 085 globalMemStoreLimit, 0.2f, MemStoreLAB.POOL_INITIAL_SIZE_DEFAULT, null, 086 MemStoreLAB.INDEX_CHUNK_SIZE_PERCENTAGE_DEFAULT); 087 assertNotNull(chunkCreator); 088 smallChunks = false; 089 } else { 090 // chunkCreator with smaller chunk size, so only 3 cell-representations can accommodate a 091 // chunk 092 chunkCreator = ChunkCreator.initialize(SMALL_CHUNK_SIZE, false, globalMemStoreLimit, 0.2f, 093 MemStoreLAB.POOL_INITIAL_SIZE_DEFAULT, null, 094 MemStoreLAB.INDEX_CHUNK_SIZE_PERCENTAGE_DEFAULT); 095 assertNotNull(chunkCreator); 096 smallChunks = true; 097 } 098 } 099 100 @BeforeEach 101 public void setUp() throws Exception { 102 // create array of Cells to bass to the CellFlatMap under CellSet 103 final byte[] one = Bytes.toBytes(15); 104 final byte[] two = Bytes.toBytes(25); 105 final byte[] three = Bytes.toBytes(35); 106 final byte[] four = Bytes.toBytes(45); 107 108 final byte[] f = Bytes.toBytes("f"); 109 final byte[] q = Bytes.toBytes("q"); 110 final byte[] v = Bytes.toBytes(4); 111 112 final KeyValue kv1 = new KeyValue(one, f, q, 10, v); 113 final KeyValue kv2 = new KeyValue(two, f, q, 20, v); 114 final KeyValue kv3 = new KeyValue(three, f, q, 30, v); 115 final KeyValue kv4 = new KeyValue(four, f, q, 40, v); 116 lowerOuterCell = new KeyValue(Bytes.toBytes(10), f, q, 10, v); 117 upperOuterCell = new KeyValue(Bytes.toBytes(50), f, q, 10, v); 118 ascCells = new ExtendedCell[] { kv1, kv2, kv3, kv4 }; 119 ascCbOnHeap = new CellArrayMap<ExtendedCell>(CellComparator.getInstance(), ascCells, 0, 120 NUM_OF_CELLS, false); 121 descCells = new ExtendedCell[] { kv4, kv3, kv2, kv1 }; 122 descCbOnHeap = new CellArrayMap<ExtendedCell>(CellComparator.getInstance(), descCells, 0, 123 NUM_OF_CELLS, true); 124 125 CONF.setBoolean(MemStoreLAB.USEMSLAB_KEY, true); 126 CONF.setFloat(MemStoreLAB.CHUNK_POOL_MAXSIZE_KEY, 0.2f); 127 ChunkCreator.chunkPoolDisabled = false; 128 129 // create ascending and descending CellChunkMaps 130 // according to parameter, once built with normal chunks and at second with small chunks 131 ascCCM = setUpCellChunkMap(true); 132 descCCM = setUpCellChunkMap(false); 133 134 if (smallChunks) { // check jumbo chunks as well 135 ascCCM = setUpJumboCellChunkMap(true); 136 } 137 } 138 139 /* Create and test ascending CellSet based on CellArrayMap */ 140 @TestTemplate 141 public void testCellArrayMapAsc() throws Exception { 142 CellSet<ExtendedCell> cs = new CellSet<>(ascCbOnHeap); 143 testCellBlocks(cs); 144 testIterators(cs); 145 } 146 147 /* Create and test ascending and descending CellSet based on CellChunkMap */ 148 @TestTemplate 149 public void testCellChunkMap() throws Exception { 150 CellSet<ExtendedCell> cs = new CellSet<>(ascCCM); 151 testCellBlocks(cs); 152 testIterators(cs); 153 testSubSet(cs); 154 cs = new CellSet<>(descCCM); 155 testSubSet(cs); 156 // cs = new CellSet(ascMultCCM); 157 // testCellBlocks(cs); 158 // testSubSet(cs); 159 // cs = new CellSet(descMultCCM); 160 // testSubSet(cs); 161 } 162 163 @TestTemplate 164 public void testAsc() throws Exception { 165 CellSet<ExtendedCell> ascCs = new CellSet<>(ascCbOnHeap); 166 assertEquals(NUM_OF_CELLS, ascCs.size()); 167 testSubSet(ascCs); 168 } 169 170 @TestTemplate 171 public void testDesc() throws Exception { 172 CellSet<ExtendedCell> descCs = new CellSet<>(descCbOnHeap); 173 assertEquals(NUM_OF_CELLS, descCs.size()); 174 testSubSet(descCs); 175 } 176 177 private void testSubSet(CellSet<ExtendedCell> cs) throws Exception { 178 for (int i = 0; i != ascCells.length; ++i) { 179 NavigableSet<ExtendedCell> excludeTail = cs.tailSet(ascCells[i], false); 180 NavigableSet<ExtendedCell> includeTail = cs.tailSet(ascCells[i], true); 181 assertEquals(ascCells.length - 1 - i, excludeTail.size()); 182 assertEquals(ascCells.length - i, includeTail.size()); 183 Iterator<ExtendedCell> excludeIter = excludeTail.iterator(); 184 Iterator<ExtendedCell> includeIter = includeTail.iterator(); 185 for (int j = 1 + i; j != ascCells.length; ++j) { 186 assertEquals(true, PrivateCellUtil.equals(excludeIter.next(), ascCells[j])); 187 } 188 for (int j = i; j != ascCells.length; ++j) { 189 assertEquals(true, PrivateCellUtil.equals(includeIter.next(), ascCells[j])); 190 } 191 } 192 assertEquals(NUM_OF_CELLS, cs.tailSet(lowerOuterCell, false).size()); 193 assertEquals(0, cs.tailSet(upperOuterCell, false).size()); 194 for (int i = 0; i != ascCells.length; ++i) { 195 NavigableSet<ExtendedCell> excludeHead = cs.headSet(ascCells[i], false); 196 NavigableSet<ExtendedCell> includeHead = cs.headSet(ascCells[i], true); 197 assertEquals(i, excludeHead.size()); 198 assertEquals(i + 1, includeHead.size()); 199 Iterator<ExtendedCell> excludeIter = excludeHead.iterator(); 200 Iterator<ExtendedCell> includeIter = includeHead.iterator(); 201 for (int j = 0; j != i; ++j) { 202 assertEquals(true, PrivateCellUtil.equals(excludeIter.next(), ascCells[j])); 203 } 204 for (int j = 0; j != i + 1; ++j) { 205 assertEquals(true, PrivateCellUtil.equals(includeIter.next(), ascCells[j])); 206 } 207 } 208 assertEquals(0, cs.headSet(lowerOuterCell, false).size()); 209 assertEquals(NUM_OF_CELLS, cs.headSet(upperOuterCell, false).size()); 210 211 NavigableMap<ExtendedCell, ExtendedCell> sub = 212 cs.getDelegatee().subMap(lowerOuterCell, true, upperOuterCell, true); 213 assertEquals(NUM_OF_CELLS, sub.size()); 214 Iterator<ExtendedCell> iter = sub.values().iterator(); 215 for (int i = 0; i != ascCells.length; ++i) { 216 assertEquals(true, PrivateCellUtil.equals(iter.next(), ascCells[i])); 217 } 218 } 219 220 /* Generic basic test for immutable CellSet */ 221 private void testCellBlocks(CellSet<ExtendedCell> cs) throws Exception { 222 final byte[] oneAndHalf = Bytes.toBytes(20); 223 final byte[] f = Bytes.toBytes("f"); 224 final byte[] q = Bytes.toBytes("q"); 225 final byte[] v = Bytes.toBytes(4); 226 final KeyValue outerCell = new KeyValue(oneAndHalf, f, q, 10, v); 227 228 assertEquals(NUM_OF_CELLS, cs.size()); // check size 229 assertFalse(cs.contains(outerCell)); // check outer cell 230 231 assertTrue(cs.contains(ascCells[0])); // check existence of the first 232 Cell first = cs.first(); 233 assertTrue(ascCells[0].equals(first)); 234 235 assertTrue(cs.contains(ascCells[NUM_OF_CELLS - 1])); // check last 236 Cell last = cs.last(); 237 assertTrue(ascCells[NUM_OF_CELLS - 1].equals(last)); 238 239 SortedSet<ExtendedCell> tail = cs.tailSet(ascCells[1]); // check tail abd head sizes 240 assertEquals(NUM_OF_CELLS - 1, tail.size()); 241 SortedSet<ExtendedCell> head = cs.headSet(ascCells[1]); 242 assertEquals(1, head.size()); 243 244 SortedSet<ExtendedCell> tailOuter = cs.tailSet(outerCell); // check tail starting from outer 245 // cell 246 assertEquals(NUM_OF_CELLS - 1, tailOuter.size()); 247 248 Cell tailFirst = tail.first(); 249 assertTrue(ascCells[1].equals(tailFirst)); 250 Cell tailLast = tail.last(); 251 assertTrue(ascCells[NUM_OF_CELLS - 1].equals(tailLast)); 252 253 Cell headFirst = head.first(); 254 assertTrue(ascCells[0].equals(headFirst)); 255 Cell headLast = head.last(); 256 assertTrue(ascCells[0].equals(headLast)); 257 } 258 259 /* Generic iterators test for immutable CellSet */ 260 private void testIterators(CellSet<ExtendedCell> cs) throws Exception { 261 // Assert that we have NUM_OF_CELLS values and that they are in order 262 int count = 0; 263 for (Cell kv : cs) { 264 assertEquals(ascCells[count], kv, 265 "\n\n-------------------------------------------------------------------\n" 266 + "Comparing iteration number " + (count + 1) + " the returned cell: " + kv 267 + ", the first Cell in the CellBlocksMap: " + ascCells[count] 268 + ", and the same transformed to String: " + ascCells[count].toString() 269 + "\n-------------------------------------------------------------------\n"); 270 count++; 271 } 272 assertEquals(NUM_OF_CELLS, count); 273 274 // Test descending iterator 275 count = 0; 276 for (Iterator<ExtendedCell> i = cs.descendingIterator(); i.hasNext();) { 277 Cell kv = i.next(); 278 assertEquals(ascCells[NUM_OF_CELLS - (count + 1)], kv); 279 count++; 280 } 281 assertEquals(NUM_OF_CELLS, count); 282 } 283 284 /* Create CellChunkMap with four cells inside the index chunk */ 285 private CellChunkMap<ExtendedCell> setUpCellChunkMap(boolean asc) { 286 // allocate new chunks and use the data chunk to hold the full data of the cells 287 // and the index chunk to hold the cell-representations 288 Chunk dataChunk = chunkCreator.getChunk(); 289 Chunk idxChunk = chunkCreator.getChunk(); 290 // the array of index chunks to be used as a basis for CellChunkMap 291 Chunk chunkArray[] = new Chunk[8]; // according to test currently written 8 is way enough 292 int chunkArrayIdx = 0; 293 chunkArray[chunkArrayIdx++] = idxChunk; 294 295 ByteBuffer idxBuffer = idxChunk.getData(); // the buffers of the chunks 296 ByteBuffer dataBuffer = dataChunk.getData(); 297 int dataOffset = ChunkCreator.SIZEOF_CHUNK_HEADER; // offset inside data buffer 298 int idxOffset = ChunkCreator.SIZEOF_CHUNK_HEADER; // skip the space for chunk ID 299 300 ExtendedCell[] cellArray = asc ? ascCells : descCells; 301 302 for (ExtendedCell kv : cellArray) { 303 // do we have enough space to write the cell data on the data chunk? 304 if (dataOffset + kv.getSerializedSize() > chunkCreator.getChunkSize()) { 305 // allocate more data chunks if needed 306 dataChunk = chunkCreator.getChunk(); 307 dataBuffer = dataChunk.getData(); 308 dataOffset = ChunkCreator.SIZEOF_CHUNK_HEADER; 309 } 310 int dataStartOfset = dataOffset; 311 dataOffset = KeyValueUtil.appendTo(kv, dataBuffer, dataOffset, false); // write deep cell data 312 313 // do we have enough space to write the cell-representation on the index chunk? 314 if (idxOffset + ClassSize.CELL_CHUNK_MAP_ENTRY > chunkCreator.getChunkSize()) { 315 // allocate more index chunks if needed 316 idxChunk = chunkCreator.getChunk(); 317 idxBuffer = idxChunk.getData(); 318 idxOffset = ChunkCreator.SIZEOF_CHUNK_HEADER; 319 chunkArray[chunkArrayIdx++] = idxChunk; 320 } 321 idxOffset = ByteBufferUtils.putInt(idxBuffer, idxOffset, dataChunk.getId()); // write data 322 // chunk id 323 idxOffset = ByteBufferUtils.putInt(idxBuffer, idxOffset, dataStartOfset); // offset 324 idxOffset = ByteBufferUtils.putInt(idxBuffer, idxOffset, kv.getSerializedSize()); // length 325 idxOffset = ByteBufferUtils.putLong(idxBuffer, idxOffset, kv.getSequenceId()); // seqId 326 } 327 328 return new CellChunkMap<>(CellComparator.getInstance(), chunkArray, 0, NUM_OF_CELLS, !asc); 329 } 330 331 /* 332 * Create CellChunkMap with four cells inside the data jumbo chunk. This test is working only with 333 * small chunks sized SMALL_CHUNK_SIZE (64) bytes 334 */ 335 private CellChunkMap<ExtendedCell> setUpJumboCellChunkMap(boolean asc) { 336 int smallChunkSize = SMALL_CHUNK_SIZE + 8; 337 // allocate new chunks and use the data JUMBO chunk to hold the full data of the cells 338 // and the normal index chunk to hold the cell-representations 339 Chunk dataJumboChunk = chunkCreator.getChunk(ChunkType.JUMBO_CHUNK, smallChunkSize); 340 assertTrue(dataJumboChunk.isJumbo()); 341 Chunk idxChunk = chunkCreator.getChunk(); 342 // the array of index chunks to be used as a basis for CellChunkMap 343 Chunk[] chunkArray = new Chunk[8]; // according to test currently written 8 is way enough 344 int chunkArrayIdx = 0; 345 chunkArray[chunkArrayIdx++] = idxChunk; 346 347 ByteBuffer idxBuffer = idxChunk.getData(); // the buffers of the chunks 348 ByteBuffer dataBuffer = dataJumboChunk.getData(); 349 int dataOffset = ChunkCreator.SIZEOF_CHUNK_HEADER; // offset inside data buffer 350 int idxOffset = ChunkCreator.SIZEOF_CHUNK_HEADER; // skip the space for chunk ID 351 352 ExtendedCell[] cellArray = asc ? ascCells : descCells; 353 354 for (ExtendedCell kv : cellArray) { 355 int dataStartOfset = dataOffset; 356 dataOffset = KeyValueUtil.appendTo(kv, dataBuffer, dataOffset, false); // write deep cell data 357 358 // do we have enough space to write the cell-representation on the index chunk? 359 if (idxOffset + ClassSize.CELL_CHUNK_MAP_ENTRY > chunkCreator.getChunkSize()) { 360 // allocate more index chunks if needed 361 idxChunk = chunkCreator.getChunk(); 362 idxBuffer = idxChunk.getData(); 363 idxOffset = ChunkCreator.SIZEOF_CHUNK_HEADER; 364 chunkArray[chunkArrayIdx++] = idxChunk; 365 } 366 // write data chunk id 367 idxOffset = ByteBufferUtils.putInt(idxBuffer, idxOffset, dataJumboChunk.getId()); 368 idxOffset = ByteBufferUtils.putInt(idxBuffer, idxOffset, dataStartOfset); // offset 369 idxOffset = ByteBufferUtils.putInt(idxBuffer, idxOffset, kv.getSerializedSize()); // length 370 idxOffset = ByteBufferUtils.putLong(idxBuffer, idxOffset, kv.getSequenceId()); // seqId 371 372 // Jumbo chunks are working only with one cell per chunk, thus always allocate a new jumbo 373 // data chunk for next cell 374 dataJumboChunk = chunkCreator.getChunk(ChunkType.JUMBO_CHUNK, smallChunkSize); 375 assertTrue(dataJumboChunk.isJumbo()); 376 dataBuffer = dataJumboChunk.getData(); 377 dataOffset = ChunkCreator.SIZEOF_CHUNK_HEADER; 378 } 379 380 return new CellChunkMap<>(CellComparator.getInstance(), chunkArray, 0, NUM_OF_CELLS, !asc); 381 } 382}