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