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}