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}