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