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.assertNotNull;
022import static org.junit.jupiter.api.Assertions.assertTrue;
023import static org.junit.jupiter.api.Assertions.fail;
024
025import java.io.IOException;
026import java.lang.management.ManagementFactory;
027import java.nio.ByteBuffer;
028import java.util.Collections;
029import java.util.List;
030import java.util.Random;
031import java.util.concurrent.ThreadLocalRandom;
032import java.util.concurrent.atomic.AtomicReference;
033import org.apache.hadoop.conf.Configuration;
034import org.apache.hadoop.hbase.ByteBufferKeyValue;
035import org.apache.hadoop.hbase.KeyValue;
036import org.apache.hadoop.hbase.exceptions.UnexpectedStateException;
037import org.apache.hadoop.hbase.io.util.MemorySizeUtil;
038import org.apache.hadoop.hbase.regionserver.ChunkCreator.ChunkType;
039import org.apache.hadoop.hbase.testclassification.RegionServerTests;
040import org.apache.hadoop.hbase.testclassification.SmallTests;
041import org.apache.hadoop.hbase.util.Bytes;
042import org.junit.jupiter.api.AfterAll;
043import org.junit.jupiter.api.AfterEach;
044import org.junit.jupiter.api.BeforeAll;
045import org.junit.jupiter.api.Tag;
046import org.junit.jupiter.api.Test;
047
048/**
049 * Test the {@link org.apache.hadoop.hbase.regionserver.ChunkCreator.MemStoreChunkPool} class
050 */
051@Tag(RegionServerTests.TAG)
052@Tag(SmallTests.TAG)
053public class TestMemStoreChunkPool {
054
055  private final static Configuration conf = new Configuration();
056  private static ChunkCreator chunkCreator;
057  private static boolean chunkPoolDisabledBeforeTest;
058
059  @BeforeAll
060  public static void setUpBeforeClass() throws Exception {
061    conf.setBoolean(MemStoreLAB.USEMSLAB_KEY, true);
062    conf.setFloat(MemStoreLAB.CHUNK_POOL_MAXSIZE_KEY, 0.2f);
063    chunkPoolDisabledBeforeTest = ChunkCreator.chunkPoolDisabled;
064    ChunkCreator.chunkPoolDisabled = false;
065    long globalMemStoreLimit =
066      (long) (ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getMax()
067        * MemorySizeUtil.getGlobalMemStoreHeapPercent(conf, false));
068    chunkCreator = ChunkCreator.initialize(MemStoreLAB.CHUNK_SIZE_DEFAULT, false,
069      globalMemStoreLimit, 0.2f, MemStoreLAB.POOL_INITIAL_SIZE_DEFAULT, null,
070      MemStoreLAB.INDEX_CHUNK_SIZE_PERCENTAGE_DEFAULT);
071    assertNotNull(chunkCreator);
072  }
073
074  @AfterAll
075  public static void tearDownAfterClass() throws Exception {
076    ChunkCreator.chunkPoolDisabled = chunkPoolDisabledBeforeTest;
077  }
078
079  @AfterEach
080  public void tearDown() throws Exception {
081    chunkCreator.clearChunksInPool();
082  }
083
084  @Test
085  public void testReusingChunks() {
086    MemStoreLAB mslab = new MemStoreLABImpl(conf);
087    int expectedOff = 0;
088    ByteBuffer lastBuffer = null;
089    final byte[] rk = Bytes.toBytes("r1");
090    final byte[] cf = Bytes.toBytes("f");
091    final byte[] q = Bytes.toBytes("q");
092    // Randomly allocate some bytes
093    final Random rand = ThreadLocalRandom.current();
094    for (int i = 0; i < 100; i++) {
095      int valSize = rand.nextInt(1000);
096      KeyValue kv = new KeyValue(rk, cf, q, new byte[valSize]);
097      int size = kv.getSerializedSize();
098      ByteBufferKeyValue newKv = (ByteBufferKeyValue) mslab.copyCellInto(kv);
099      if (newKv.getBuffer() != lastBuffer) {
100        expectedOff = 4;
101        lastBuffer = newKv.getBuffer();
102      }
103      assertEquals(expectedOff, newKv.getOffset());
104      assertTrue(newKv.getOffset() + size <= newKv.getBuffer().capacity(),
105        "Allocation overruns buffer");
106      expectedOff += size;
107    }
108    // chunks will be put back to pool after close
109    mslab.close();
110    int chunkCount = chunkCreator.getPoolSize();
111    assertTrue(chunkCount > 0);
112    // reconstruct mslab
113    mslab = new MemStoreLABImpl(conf);
114    // chunk should be got from the pool, so we can reuse it.
115    KeyValue kv = new KeyValue(rk, cf, q, new byte[10]);
116    mslab.copyCellInto(kv);
117    assertEquals(chunkCount - 1, chunkCreator.getPoolSize());
118  }
119
120  @Test
121  public void testPuttingBackChunksAfterFlushing() throws UnexpectedStateException {
122    byte[] row = Bytes.toBytes("testrow");
123    byte[] fam = Bytes.toBytes("testfamily");
124    byte[] qf1 = Bytes.toBytes("testqualifier1");
125    byte[] qf2 = Bytes.toBytes("testqualifier2");
126    byte[] qf3 = Bytes.toBytes("testqualifier3");
127    byte[] qf4 = Bytes.toBytes("testqualifier4");
128    byte[] qf5 = Bytes.toBytes("testqualifier5");
129    byte[] val = Bytes.toBytes("testval");
130
131    DefaultMemStore memstore = new DefaultMemStore();
132
133    // Setting up memstore
134    memstore.add(new KeyValue(row, fam, qf1, val), null);
135    memstore.add(new KeyValue(row, fam, qf2, val), null);
136    memstore.add(new KeyValue(row, fam, qf3, val), null);
137
138    // Creating a snapshot
139    MemStoreSnapshot snapshot = memstore.snapshot();
140    assertEquals(3, memstore.getSnapshot().getCellsCount());
141
142    // Adding value to "new" memstore
143    assertEquals(0, memstore.getActive().getCellsCount());
144    memstore.add(new KeyValue(row, fam, qf4, val), null);
145    memstore.add(new KeyValue(row, fam, qf5, val), null);
146    assertEquals(2, memstore.getActive().getCellsCount());
147    // close the scanner - this is how the snapshot will be used
148    for (KeyValueScanner scanner : snapshot.getScanners()) {
149      scanner.close();
150    }
151    memstore.clearSnapshot(snapshot.getId());
152
153    int chunkCount = chunkCreator.getPoolSize();
154    assertTrue(chunkCount > 0);
155
156  }
157
158  @Test
159  public void testPuttingBackChunksWithOpeningScanner() throws IOException {
160    byte[] row = Bytes.toBytes("testrow");
161    byte[] fam = Bytes.toBytes("testfamily");
162    byte[] qf1 = Bytes.toBytes("testqualifier1");
163    byte[] qf2 = Bytes.toBytes("testqualifier2");
164    byte[] qf3 = Bytes.toBytes("testqualifier3");
165    byte[] qf4 = Bytes.toBytes("testqualifier4");
166    byte[] qf5 = Bytes.toBytes("testqualifier5");
167    byte[] qf6 = Bytes.toBytes("testqualifier6");
168    byte[] qf7 = Bytes.toBytes("testqualifier7");
169    byte[] val = Bytes.toBytes("testval");
170
171    DefaultMemStore memstore = new DefaultMemStore();
172
173    // Setting up memstore
174    memstore.add(new KeyValue(row, fam, qf1, val), null);
175    memstore.add(new KeyValue(row, fam, qf2, val), null);
176    memstore.add(new KeyValue(row, fam, qf3, val), null);
177
178    // Creating a snapshot
179    MemStoreSnapshot snapshot = memstore.snapshot();
180    assertEquals(3, memstore.getSnapshot().getCellsCount());
181
182    // Adding value to "new" memstore
183    assertEquals(0, memstore.getActive().getCellsCount());
184    memstore.add(new KeyValue(row, fam, qf4, val), null);
185    memstore.add(new KeyValue(row, fam, qf5, val), null);
186    assertEquals(2, memstore.getActive().getCellsCount());
187
188    // opening scanner before clear the snapshot
189    List<KeyValueScanner> scanners = memstore.getScanners(0);
190    // Shouldn't putting back the chunks to pool,since some scanners are opening
191    // based on their data
192    // close the snapshot scanner
193    for (KeyValueScanner scanner : snapshot.getScanners()) {
194      scanner.close();
195    }
196    memstore.clearSnapshot(snapshot.getId());
197
198    assertTrue(chunkCreator.getPoolSize() == 0);
199
200    // Chunks will be put back to pool after close scanners;
201    for (KeyValueScanner scanner : scanners) {
202      scanner.close();
203    }
204    assertTrue(chunkCreator.getPoolSize() > 0);
205
206    // clear chunks
207    chunkCreator.clearChunksInPool();
208
209    // Creating another snapshot
210    snapshot = memstore.snapshot();
211    // Adding more value
212    memstore.add(new KeyValue(row, fam, qf6, val), null);
213    memstore.add(new KeyValue(row, fam, qf7, val), null);
214    // opening scanners
215    scanners = memstore.getScanners(0);
216    // close scanners before clear the snapshot
217    for (KeyValueScanner scanner : scanners) {
218      scanner.close();
219    }
220    // Since no opening scanner, the chunks of snapshot should be put back to
221    // pool
222    // close the snapshot scanners
223    for (KeyValueScanner scanner : snapshot.getScanners()) {
224      scanner.close();
225    }
226    memstore.clearSnapshot(snapshot.getId());
227    assertTrue(chunkCreator.getPoolSize() > 0);
228  }
229
230  @Test
231  public void testPutbackChunksMultiThreaded() throws Exception {
232    final int maxCount = 10;
233    final int initialCount = 5;
234    final int chunkSize = 40;
235    final int valSize = 7;
236    ChunkCreator oldCreator = ChunkCreator.getInstance();
237    ChunkCreator newCreator = new ChunkCreator(chunkSize, false, 400, 1, 0.5f, null, 0);
238    assertEquals(initialCount, newCreator.getPoolSize());
239    assertEquals(maxCount, newCreator.getMaxCount());
240    // Replace the global ref with the new one we created.
241    // Used it for the testing. Later in finally we put
242    // back the original
243    ChunkCreator.instance = newCreator;
244
245    final KeyValue kv =
246      new KeyValue(Bytes.toBytes("r"), Bytes.toBytes("f"), Bytes.toBytes("q"), new byte[valSize]);
247    final AtomicReference<Throwable> exceptionRef = new AtomicReference<Throwable>();
248    try {
249      Runnable r = new Runnable() {
250        @Override
251        public void run() {
252          try {
253            MemStoreLAB memStoreLAB = new MemStoreLABImpl(conf);
254            for (int i = 0; i < maxCount; i++) {
255              // Try allocate size = chunkSize. Means every
256              // allocate call will result in a new chunk
257              memStoreLAB.copyCellInto(kv);
258            }
259            // Close MemStoreLAB so that all chunks will be tried to be put back to pool
260            memStoreLAB.close();
261          } catch (Throwable execption) {
262            exceptionRef.set(execption);
263          }
264        }
265      };
266      Thread t1 = new Thread(r);
267      Thread t2 = new Thread(r);
268      Thread t3 = new Thread(r);
269      t1.start();
270      t2.start();
271      t3.start();
272      t1.join();
273      t2.join();
274      t3.join();
275      assertTrue(exceptionRef.get() == null);
276      assertTrue(newCreator.getPoolSize() <= maxCount && newCreator.getPoolSize() > 0);
277    } finally {
278      ChunkCreator.instance = oldCreator;
279    }
280  }
281
282  // This test is for HBASE-26142, which throws NPE when indexChunksPool is null.
283  @Test
284  public void testNoIndexChunksPoolOrNoDataChunksPool() throws Exception {
285    final int maxCount = 10;
286    final int initialCount = 5;
287    final int newChunkSize = 40;
288    final int valSize = 7;
289
290    ChunkCreator oldCreator = ChunkCreator.getInstance();
291    try {
292      // Test dataChunksPool is not null and indexChunksPool is null
293      ChunkCreator newCreator = new ChunkCreator(newChunkSize, false, 400, 1, 0.5f, null, 0);
294      assertEquals(initialCount, newCreator.getPoolSize());
295      assertEquals(0, newCreator.getPoolSize(ChunkType.INDEX_CHUNK));
296      assertEquals(maxCount, newCreator.getMaxCount());
297      assertEquals(0, newCreator.getMaxCount(ChunkType.INDEX_CHUNK));
298      assertTrue(newCreator.getDataChunksPool() != null);
299      assertTrue(newCreator.getIndexChunksPool() == null);
300      ChunkCreator.instance = newCreator;
301      final KeyValue kv =
302        new KeyValue(Bytes.toBytes("r"), Bytes.toBytes("f"), Bytes.toBytes("q"), new byte[valSize]);
303
304      MemStoreLAB memStoreLAB = new MemStoreLABImpl(conf);
305      memStoreLAB.copyCellInto(kv);
306      memStoreLAB.close();
307      assertEquals(initialCount, newCreator.getPoolSize());
308      assertEquals(0, newCreator.getPoolSize(ChunkType.INDEX_CHUNK));
309
310      Chunk dataChunk = newCreator.getChunk();
311      assertTrue(dataChunk.isDataChunk());
312      assertTrue(dataChunk.isFromPool());
313      assertEquals(initialCount - 1, newCreator.getPoolSize());
314      assertEquals(0, newCreator.getPoolSize(ChunkType.INDEX_CHUNK));
315      newCreator.putbackChunks(Collections.singleton(dataChunk.getId()));
316      assertEquals(initialCount, newCreator.getPoolSize());
317      assertEquals(0, newCreator.getPoolSize(ChunkType.INDEX_CHUNK));
318
319      // We set ChunkCreator.indexChunkSize to 0, but we want to get a IndexChunk
320      try {
321        newCreator.getChunk(ChunkType.INDEX_CHUNK);
322        fail();
323      } catch (IllegalArgumentException e) {
324      }
325
326      Chunk jumboChunk = newCreator.getJumboChunk(newChunkSize + 10);
327      assertTrue(jumboChunk.isJumbo());
328      assertTrue(!jumboChunk.isFromPool());
329      assertEquals(initialCount, newCreator.getPoolSize());
330      assertEquals(0, newCreator.getPoolSize(ChunkType.INDEX_CHUNK));
331
332      // Test both dataChunksPool and indexChunksPool are null
333      newCreator = new ChunkCreator(newChunkSize, false, 400, 0, 0.5f, null, 0);
334      assertEquals(0, newCreator.getPoolSize());
335      assertEquals(0, newCreator.getPoolSize(ChunkType.INDEX_CHUNK));
336      assertEquals(0, newCreator.getMaxCount());
337      assertEquals(0, newCreator.getMaxCount(ChunkType.INDEX_CHUNK));
338      assertTrue(newCreator.getDataChunksPool() == null);
339      assertTrue(newCreator.getIndexChunksPool() == null);
340      ChunkCreator.instance = newCreator;
341
342      memStoreLAB = new MemStoreLABImpl(conf);
343      memStoreLAB.copyCellInto(kv);
344      memStoreLAB.close();
345      assertEquals(0, newCreator.getPoolSize());
346      assertEquals(0, newCreator.getPoolSize(ChunkType.INDEX_CHUNK));
347
348      dataChunk = newCreator.getChunk();
349      assertTrue(dataChunk.isDataChunk());
350      assertTrue(!dataChunk.isFromPool());
351      assertEquals(0, newCreator.getPoolSize());
352      assertEquals(0, newCreator.getPoolSize(ChunkType.INDEX_CHUNK));
353
354      try {
355        // We set ChunkCreator.indexChunkSize to 0, but we want to get a IndexChunk
356        newCreator.getChunk(ChunkType.INDEX_CHUNK);
357        fail();
358      } catch (IllegalArgumentException e) {
359      }
360
361      jumboChunk = newCreator.getJumboChunk(newChunkSize + 10);
362      assertTrue(jumboChunk.isJumbo());
363      assertTrue(!jumboChunk.isFromPool());
364      assertEquals(0, newCreator.getPoolSize());
365      assertEquals(0, newCreator.getPoolSize(ChunkType.INDEX_CHUNK));
366
367      // Test dataChunksPool is null and indexChunksPool is not null
368      newCreator = new ChunkCreator(newChunkSize, false, 400, 1, 0.5f, null, 1);
369      assertEquals(0, newCreator.getPoolSize());
370      assertEquals(initialCount, newCreator.getPoolSize(ChunkType.INDEX_CHUNK));
371      assertEquals(0, newCreator.getMaxCount());
372      assertEquals(maxCount, newCreator.getMaxCount(ChunkType.INDEX_CHUNK));
373      assertTrue(newCreator.getDataChunksPool() == null);
374      assertTrue(newCreator.getIndexChunksPool() != null);
375      assertEquals(newCreator.getChunkSize(ChunkType.DATA_CHUNK),
376        newCreator.getChunkSize(ChunkType.INDEX_CHUNK));
377      ChunkCreator.instance = newCreator;
378
379      memStoreLAB = new MemStoreLABImpl(conf);
380      memStoreLAB.copyCellInto(kv);
381      memStoreLAB.close();
382      assertEquals(0, newCreator.getPoolSize());
383      assertEquals(initialCount, newCreator.getPoolSize(ChunkType.INDEX_CHUNK));
384
385      dataChunk = newCreator.getChunk();
386      assertTrue(dataChunk.isDataChunk());
387      assertTrue(!dataChunk.isFromPool());
388      assertEquals(0, newCreator.getPoolSize());
389      assertEquals(initialCount, newCreator.getPoolSize(ChunkType.INDEX_CHUNK));
390
391      Chunk indexChunk = newCreator.getChunk(ChunkType.INDEX_CHUNK);
392      assertEquals(0, newCreator.getPoolSize());
393      assertEquals(initialCount - 1, newCreator.getPoolSize(ChunkType.INDEX_CHUNK));
394      assertTrue(indexChunk.isIndexChunk());
395      assertTrue(indexChunk.isFromPool());
396      newCreator.putbackChunks(Collections.singleton(indexChunk.getId()));
397      assertEquals(0, newCreator.getPoolSize());
398      assertEquals(initialCount, newCreator.getPoolSize(ChunkType.INDEX_CHUNK));
399
400      jumboChunk = newCreator.getJumboChunk(newChunkSize + 10);
401      assertTrue(jumboChunk.isJumbo());
402      assertTrue(!jumboChunk.isFromPool());
403      assertEquals(0, newCreator.getPoolSize());
404      assertEquals(initialCount, newCreator.getPoolSize(ChunkType.INDEX_CHUNK));
405    } finally {
406      ChunkCreator.instance = oldCreator;
407    }
408
409    // Test both dataChunksPool and indexChunksPool are not null
410    assertTrue(ChunkCreator.getInstance().getDataChunksPool() != null);
411    assertTrue(ChunkCreator.getInstance().getIndexChunksPool() != null);
412    Chunk dataChunk = ChunkCreator.getInstance().getChunk();
413    assertTrue(dataChunk.isDataChunk());
414    assertTrue(dataChunk.isFromPool());
415    Chunk indexChunk = ChunkCreator.getInstance().getChunk(ChunkType.INDEX_CHUNK);
416    assertTrue(indexChunk.isIndexChunk());
417    assertTrue(indexChunk.isFromPool());
418    Chunk jumboChunk =
419      ChunkCreator.getInstance().getJumboChunk(ChunkCreator.getInstance().getChunkSize() + 10);
420    assertTrue(jumboChunk.isJumbo());
421    assertTrue(!jumboChunk.isFromPool());
422  }
423}