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}