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.apache.hadoop.hbase.HConstants.BUCKET_CACHE_SIZE_KEY; 021import static org.apache.hadoop.hbase.io.hfile.bucket.BucketCache.DEFAULT_ERROR_TOLERATION_DURATION; 022import static org.junit.Assert.assertEquals; 023import static org.junit.Assert.assertFalse; 024import static org.junit.Assert.assertNotNull; 025import static org.junit.Assert.assertNull; 026import static org.junit.Assert.assertTrue; 027import static org.junit.Assert.fail; 028 029import java.io.IOException; 030import java.util.ArrayList; 031import java.util.Collections; 032import java.util.HashMap; 033import java.util.HashSet; 034import java.util.List; 035import java.util.Map; 036import java.util.Optional; 037import java.util.Random; 038import java.util.Set; 039import org.apache.hadoop.conf.Configuration; 040import org.apache.hadoop.fs.FileSystem; 041import org.apache.hadoop.fs.Path; 042import org.apache.hadoop.hbase.HBaseClassTestRule; 043import org.apache.hadoop.hbase.HBaseTestingUtil; 044import org.apache.hadoop.hbase.HConstants; 045import org.apache.hadoop.hbase.KeyValue; 046import org.apache.hadoop.hbase.TableName; 047import org.apache.hadoop.hbase.Waiter; 048import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor; 049import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder; 050import org.apache.hadoop.hbase.client.RegionInfo; 051import org.apache.hadoop.hbase.client.RegionInfoBuilder; 052import org.apache.hadoop.hbase.client.TableDescriptor; 053import org.apache.hadoop.hbase.client.TableDescriptorBuilder; 054import org.apache.hadoop.hbase.fs.HFileSystem; 055import org.apache.hadoop.hbase.io.encoding.DataBlockEncoding; 056import org.apache.hadoop.hbase.io.hfile.BlockCache; 057import org.apache.hadoop.hbase.io.hfile.BlockCacheFactory; 058import org.apache.hadoop.hbase.io.hfile.BlockCacheKey; 059import org.apache.hadoop.hbase.io.hfile.BlockType; 060import org.apache.hadoop.hbase.io.hfile.BlockType.BlockCategory; 061import org.apache.hadoop.hbase.io.hfile.CacheConfig; 062import org.apache.hadoop.hbase.io.hfile.CacheTestUtils; 063import org.apache.hadoop.hbase.io.hfile.HFileBlock; 064import org.apache.hadoop.hbase.io.hfile.HFileContextBuilder; 065import org.apache.hadoop.hbase.io.hfile.HFileInfo; 066import org.apache.hadoop.hbase.io.hfile.bucket.BucketCache; 067import org.apache.hadoop.hbase.regionserver.storefiletracker.StoreFileTracker; 068import org.apache.hadoop.hbase.regionserver.storefiletracker.StoreFileTrackerFactory; 069import org.apache.hadoop.hbase.testclassification.RegionServerTests; 070import org.apache.hadoop.hbase.testclassification.SmallTests; 071import org.apache.hadoop.hbase.util.Bytes; 072import org.apache.hadoop.hbase.util.CommonFSUtils; 073import org.apache.hadoop.hbase.util.Pair; 074import org.junit.BeforeClass; 075import org.junit.ClassRule; 076import org.junit.Test; 077import org.junit.experimental.categories.Category; 078import org.slf4j.Logger; 079import org.slf4j.LoggerFactory; 080 081/** 082 * This class is used to test the functionality of the DataTieringManager. 083 * 084 * The mock online regions are stored in {@link TestDataTieringManager#testOnlineRegions}. 085 * For all tests, the setup of {@link TestDataTieringManager#testOnlineRegions} occurs only once. 086 * Please refer to {@link TestDataTieringManager#setupOnlineRegions()} for the structure. 087 * Additionally, a list of all store files is maintained in {@link TestDataTieringManager#hStoreFiles}. 088 * The characteristics of these store files are listed below: 089 * @formatter:off ## HStoreFile Information 090 * 091 * | HStoreFile | Region | Store | DataTiering | isHot | 092 * |------------------|--------------------|---------------------|-----------------------|-------| 093 * | hStoreFile0 | region1 | hStore11 | TIME_RANGE | true | 094 * | hStoreFile1 | region1 | hStore12 | NONE | true | 095 * | hStoreFile2 | region2 | hStore21 | TIME_RANGE | true | 096 * | hStoreFile3 | region2 | hStore22 | TIME_RANGE | false | 097 * @formatter:on 098 */ 099 100@Category({ RegionServerTests.class, SmallTests.class }) 101public class TestDataTieringManager { 102 103 @ClassRule 104 public static final HBaseClassTestRule CLASS_RULE = 105 HBaseClassTestRule.forClass(TestDataTieringManager.class); 106 107 private static final Logger LOG = LoggerFactory.getLogger(TestDataTieringManager.class); 108 private static final HBaseTestingUtil TEST_UTIL = new HBaseTestingUtil(); 109 private static final long DAY = 24 * 60 * 60 * 1000; 110 private static Configuration defaultConf; 111 private static FileSystem fs; 112 private static BlockCache blockCache; 113 private static CacheConfig cacheConf; 114 private static Path testDir; 115 private static final Map<String, HRegion> testOnlineRegions = new HashMap<>(); 116 117 private static DataTieringManager dataTieringManager; 118 private static final List<HStoreFile> hStoreFiles = new ArrayList<>(); 119 120 /** 121 * Represents the current lexicographically increasing string used as a row key when writing 122 * HFiles. It is incremented each time {@link #nextString()} is called to generate unique row 123 * keys. 124 */ 125 private static String rowKeyString; 126 127 @BeforeClass 128 public static void setupBeforeClass() throws Exception { 129 testDir = TEST_UTIL.getDataTestDir(TestDataTieringManager.class.getSimpleName()); 130 defaultConf = TEST_UTIL.getConfiguration(); 131 updateCommonConfigurations(); 132 assertTrue(DataTieringManager.instantiate(defaultConf, testOnlineRegions)); 133 dataTieringManager = DataTieringManager.getInstance(); 134 rowKeyString = ""; 135 } 136 137 private static void updateCommonConfigurations() { 138 defaultConf.setBoolean(DataTieringManager.GLOBAL_DATA_TIERING_ENABLED_KEY, true); 139 defaultConf.setStrings(HConstants.BUCKET_CACHE_IOENGINE_KEY, "offheap"); 140 defaultConf.setLong(BUCKET_CACHE_SIZE_KEY, 32); 141 } 142 143 @FunctionalInterface 144 interface DataTieringMethodCallerWithKey { 145 boolean call(DataTieringManager manager, BlockCacheKey key) throws DataTieringException; 146 } 147 148 @Test 149 public void testDataTieringEnabledWithKey() throws IOException { 150 initializeTestEnvironment(); 151 DataTieringMethodCallerWithKey methodCallerWithKey = DataTieringManager::isDataTieringEnabled; 152 153 // Test with valid key 154 BlockCacheKey key = new BlockCacheKey(hStoreFiles.get(0).getPath(), 0, true, BlockType.DATA); 155 testDataTieringMethodWithKeyNoException(methodCallerWithKey, key, true); 156 157 // Test with another valid key 158 key = new BlockCacheKey(hStoreFiles.get(1).getPath(), 0, true, BlockType.DATA); 159 testDataTieringMethodWithKeyNoException(methodCallerWithKey, key, false); 160 } 161 162 @Test 163 public void testHotDataWithKey() throws IOException { 164 initializeTestEnvironment(); 165 DataTieringMethodCallerWithKey methodCallerWithKey = DataTieringManager::isHotData; 166 // Test with valid key 167 BlockCacheKey key = new BlockCacheKey(hStoreFiles.get(0).getPath(), 0, true, BlockType.DATA); 168 testDataTieringMethodWithKeyNoException(methodCallerWithKey, key, true); 169 170 // Test with another valid key 171 key = new BlockCacheKey(hStoreFiles.get(3).getPath(), 0, true, BlockType.DATA); 172 testDataTieringMethodWithKeyNoException(methodCallerWithKey, key, false); 173 } 174 175 @Test 176 public void testGracePeriodMakesColdFileHot() throws IOException, DataTieringException { 177 initializeTestEnvironment(); 178 179 long hotAge = 1 * DAY; 180 long gracePeriod = 3 * DAY; 181 182 long currentTime = System.currentTimeMillis(); 183 long fileTimestamp = currentTime - (2 * DAY); 184 185 Configuration conf = getConfWithGracePeriod(hotAge, gracePeriod); 186 HRegion region = createHRegion("tableGracePeriod", conf); 187 HStore hStore = createHStore(region, "cf1", conf); 188 189 HStoreFile file = createHStoreFile(hStore.getStoreContext().getFamilyStoreDirectoryPath(), 190 hStore.getReadOnlyConfiguration(), fileTimestamp, region.getRegionFileSystem()); 191 file.initReader(); 192 193 hStore.refreshStoreFiles(); 194 region.stores.put(Bytes.toBytes("cf1"), hStore); 195 testOnlineRegions.put(region.getRegionInfo().getEncodedName(), region); 196 Path hFilePath = file.getPath(); 197 BlockCacheKey key = new BlockCacheKey(hFilePath, 0, true, BlockType.DATA); 198 assertTrue("File should be hot due to grace period", dataTieringManager.isHotData(key)); 199 } 200 201 @Test 202 public void testFileIsColdWithoutGracePeriod() throws IOException, DataTieringException { 203 initializeTestEnvironment(); 204 205 long hotAge = 1 * DAY; 206 long gracePeriod = 0; 207 long currentTime = System.currentTimeMillis(); 208 long fileTimestamp = currentTime - (2 * DAY); 209 210 Configuration conf = getConfWithGracePeriod(hotAge, gracePeriod); 211 HRegion region = createHRegion("tableNoGracePeriod", conf); 212 HStore hStore = createHStore(region, "cf1", conf); 213 214 HStoreFile file = createHStoreFile(hStore.getStoreContext().getFamilyStoreDirectoryPath(), 215 hStore.getReadOnlyConfiguration(), fileTimestamp, region.getRegionFileSystem()); 216 file.initReader(); 217 218 hStore.refreshStoreFiles(); 219 region.stores.put(Bytes.toBytes("cf1"), hStore); 220 testOnlineRegions.put(region.getRegionInfo().getEncodedName(), region); 221 222 Path hFilePath = file.getPath(); 223 BlockCacheKey key = new BlockCacheKey(hFilePath, 0, true, BlockType.DATA); 224 assertFalse("File should be cold without grace period", dataTieringManager.isHotData(key)); 225 } 226 227 @Test 228 public void testPrefetchWhenDataTieringEnabled() throws IOException { 229 setPrefetchBlocksOnOpen(); 230 initializeTestEnvironment(); 231 // Evict blocks from cache by closing the files and passing evict on close. 232 // Then initialize the reader again. Since Prefetch on open is set to true, it should prefetch 233 // those blocks. 234 for (HStoreFile file : hStoreFiles) { 235 file.closeStoreFile(true); 236 file.initReader(); 237 } 238 239 // Since we have one cold file among four files, only three should get prefetched. 240 Optional<Map<String, Pair<String, Long>>> fullyCachedFiles = blockCache.getFullyCachedFiles(); 241 assertTrue("We should get the fully cached files from the cache", fullyCachedFiles.isPresent()); 242 Waiter.waitFor(defaultConf, 10000, () -> fullyCachedFiles.get().size() == 3); 243 assertEquals("Number of fully cached files are incorrect", 3, fullyCachedFiles.get().size()); 244 } 245 246 private void setPrefetchBlocksOnOpen() { 247 defaultConf.setBoolean(CacheConfig.PREFETCH_BLOCKS_ON_OPEN_KEY, true); 248 } 249 250 @Test 251 public void testColdDataFiles() throws IOException { 252 initializeTestEnvironment(); 253 Set<BlockCacheKey> allCachedBlocks = new HashSet<>(); 254 for (HStoreFile file : hStoreFiles) { 255 allCachedBlocks.add(new BlockCacheKey(file.getPath(), 0, true, BlockType.DATA)); 256 } 257 258 // Verify hStoreFile3 is identified as cold data 259 DataTieringMethodCallerWithKey methodCallerWithPath = DataTieringManager::isHotData; 260 Path hFilePath = hStoreFiles.get(3).getPath(); 261 testDataTieringMethodWithKeyNoException(methodCallerWithPath, 262 new BlockCacheKey(hFilePath, 0, true, BlockType.DATA), false); 263 264 // Verify all the other files in hStoreFiles are hot data 265 for (int i = 0; i < hStoreFiles.size() - 1; i++) { 266 hFilePath = hStoreFiles.get(i).getPath(); 267 testDataTieringMethodWithKeyNoException(methodCallerWithPath, 268 new BlockCacheKey(hFilePath, 0, true, BlockType.DATA), true); 269 } 270 271 try { 272 Set<String> coldFilePaths = dataTieringManager.getColdDataFiles(allCachedBlocks); 273 assertEquals(1, coldFilePaths.size()); 274 } catch (DataTieringException e) { 275 fail("Unexpected DataTieringException: " + e.getMessage()); 276 } 277 } 278 279 @Test 280 public void testCacheCompactedBlocksOnWriteDataTieringDisabled() throws IOException { 281 setCacheCompactBlocksOnWrite(); 282 initializeTestEnvironment(); 283 284 HRegion region = createHRegion("table3"); 285 testCacheCompactedBlocksOnWrite(region, true); 286 } 287 288 @Test 289 public void testCacheCompactedBlocksOnWriteWithHotData() throws IOException { 290 setCacheCompactBlocksOnWrite(); 291 initializeTestEnvironment(); 292 293 HRegion region = createHRegion("table3", getConfWithTimeRangeDataTieringEnabled(5 * DAY)); 294 testCacheCompactedBlocksOnWrite(region, true); 295 } 296 297 @Test 298 public void testCacheCompactedBlocksOnWriteWithColdData() throws IOException { 299 setCacheCompactBlocksOnWrite(); 300 initializeTestEnvironment(); 301 302 HRegion region = createHRegion("table3", getConfWithTimeRangeDataTieringEnabled(DAY)); 303 testCacheCompactedBlocksOnWrite(region, false); 304 } 305 306 private void setCacheCompactBlocksOnWrite() { 307 defaultConf.setBoolean(CacheConfig.CACHE_COMPACTED_BLOCKS_ON_WRITE_KEY, true); 308 } 309 310 private void testCacheCompactedBlocksOnWrite(HRegion region, boolean expectDataBlocksCached) 311 throws IOException { 312 HStore hStore = createHStore(region, "cf1"); 313 createTestFilesForCompaction(hStore); 314 hStore.refreshStoreFiles(); 315 316 region.stores.put(Bytes.toBytes("cf1"), hStore); 317 testOnlineRegions.put(region.getRegionInfo().getEncodedName(), region); 318 319 long initialStoreFilesCount = hStore.getStorefilesCount(); 320 long initialCacheDataBlockCount = blockCache.getDataBlockCount(); 321 assertEquals(3, initialStoreFilesCount); 322 assertEquals(0, initialCacheDataBlockCount); 323 324 region.compact(true); 325 326 long compactedStoreFilesCount = hStore.getStorefilesCount(); 327 long compactedCacheDataBlockCount = blockCache.getDataBlockCount(); 328 assertEquals(1, compactedStoreFilesCount); 329 assertEquals(expectDataBlocksCached, compactedCacheDataBlockCount > 0); 330 } 331 332 private void createTestFilesForCompaction(HStore hStore) throws IOException { 333 long currentTime = System.currentTimeMillis(); 334 Path storeDir = hStore.getStoreContext().getFamilyStoreDirectoryPath(); 335 Configuration configuration = hStore.getReadOnlyConfiguration(); 336 337 createHStoreFile(storeDir, configuration, currentTime - 2 * DAY, 338 hStore.getHRegion().getRegionFileSystem()); 339 createHStoreFile(storeDir, configuration, currentTime - 3 * DAY, 340 hStore.getHRegion().getRegionFileSystem()); 341 createHStoreFile(storeDir, configuration, currentTime - 4 * DAY, 342 hStore.getHRegion().getRegionFileSystem()); 343 } 344 345 @Test 346 public void testPickColdDataFiles() throws IOException { 347 initializeTestEnvironment(); 348 Map<String, String> coldDataFiles = dataTieringManager.getColdFilesList(); 349 assertEquals(1, coldDataFiles.size()); 350 // hStoreFiles[3] is the cold file. 351 assert (coldDataFiles.containsKey(hStoreFiles.get(3).getFileInfo().getActiveFileName())); 352 } 353 354 /** 355 * {@link DataTieringManager#isHotData(HFileInfo, org.apache.hadoop.conf.Configuration)} should 356 * record cold files in {@link DataTieringManager#getRegionColdDataSize()} (path and aggregate 357 * size). 358 */ 359 @Test 360 public void testRegionColdDataSizeRecordColdHFile() throws IOException { 361 initializeTestEnvironment(); 362 dataTieringManager.getRegionColdDataSize().clear(); 363 364 HStoreFile coldFile = hStoreFiles.get(3); 365 String region = coldFile.getPath().getParent().getParent().getName(); 366 assertFalse("fixture file should be cold for TIME_RANGE tiering", dataTieringManager 367 .isHotData(coldFile.getFileInfo().getHFileInfo(), coldFile.getFileInfo().getConf())); 368 369 Map<String, Pair<List<String>, Long>> coldByRegion = dataTieringManager.getRegionColdDataSize(); 370 assertTrue(coldByRegion.containsKey(region)); 371 Pair<List<String>, Long> entry = coldByRegion.get(region); 372 long expected = Bytes.toLong(coldFile.getFileInfo().getHFileInfo().get(HFileInfo.FILE_SIZE)); 373 assertEquals(expected, (long) entry.getSecond()); 374 assertEquals(1, entry.getFirst().size()); 375 assertTrue(entry.getFirst().contains(coldFile.getPath().getName())); 376 } 377 378 @Test 379 public void testRegionColdDataSizeSkipsHotHFile() throws IOException { 380 initializeTestEnvironment(); 381 dataTieringManager.getRegionColdDataSize().clear(); 382 383 HStoreFile hotFile = hStoreFiles.get(0); 384 assertTrue(dataTieringManager.isHotData(hotFile.getFileInfo().getHFileInfo(), 385 hotFile.getFileInfo().getConf())); 386 assertTrue(dataTieringManager.getRegionColdDataSize().isEmpty()); 387 } 388 389 @Test 390 public void testRegionColdDataSizeSkipsNoTieringHFile() throws IOException { 391 initializeTestEnvironment(); 392 dataTieringManager.getRegionColdDataSize().clear(); 393 394 HStoreFile file = hStoreFiles.get(1); 395 assertTrue(dataTieringManager.isHotData(file.getFileInfo().getHFileInfo(), 396 file.getFileInfo().getConf())); 397 String encoded = file.getPath().getParent().getParent().getName(); 398 assertFalse(dataTieringManager.getRegionColdDataSize().containsKey(encoded)); 399 } 400 401 @Test 402 public void testRegionColdDataSizeForSameHFile() throws IOException { 403 initializeTestEnvironment(); 404 dataTieringManager.getRegionColdDataSize().clear(); 405 406 HStoreFile coldFile = hStoreFiles.get(3); 407 long expected = Bytes.toLong(coldFile.getFileInfo().getHFileInfo().get(HFileInfo.FILE_SIZE)); 408 dataTieringManager.isHotData(coldFile.getFileInfo().getHFileInfo(), 409 coldFile.getFileInfo().getConf()); 410 dataTieringManager.isHotData(coldFile.getFileInfo().getHFileInfo(), 411 coldFile.getFileInfo().getConf()); 412 413 String region = coldFile.getPath().getParent().getParent().getName(); 414 Pair<List<String>, Long> entry = dataTieringManager.getRegionColdDataSize().get(region); 415 assertNotNull(entry); 416 assertEquals(expected, (long) entry.getSecond()); 417 assertEquals(1, entry.getFirst().size()); 418 } 419 420 @Test 421 public void testUpdateRegionColdDataSizeNoopWhenRegionNotTracked() throws IOException { 422 initializeTestEnvironment(); 423 dataTieringManager.getRegionColdDataSize().clear(); 424 HStoreFile coldFile = hStoreFiles.get(3); 425 dataTieringManager.updateRegionColdDataSize("not-a-real-encoded-region", 426 Collections.singletonList(coldFile), Collections.emptyList()); 427 assertTrue(dataTieringManager.getRegionColdDataSize().isEmpty()); 428 } 429 430 @Test 431 public void testUpdateRegionColdDataSizeRemovesCompactedColdAddsNewHot() throws IOException { 432 initializeTestEnvironment(); 433 dataTieringManager.getRegionColdDataSize().clear(); 434 435 HStoreFile coldFile = hStoreFiles.get(3); 436 String regionName = coldFile.getPath().getParent().getParent().getName(); 437 dataTieringManager.isHotData(coldFile.getFileInfo().getHFileInfo(), 438 coldFile.getFileInfo().getConf()); 439 440 HRegion region = testOnlineRegions.get(regionName); 441 assertNotNull(region); 442 HStore hStore = region.getStore(Bytes.toBytes("cf2")); 443 HStoreFile newFile = createHStoreFile(hStore.getStoreContext().getFamilyStoreDirectoryPath(), 444 hStore.getReadOnlyConfiguration(), System.currentTimeMillis(), region.getRegionFileSystem()); 445 newFile.initReader(); 446 hStore.refreshStoreFiles(); 447 448 dataTieringManager.updateRegionColdDataSize(regionName, Collections.singletonList(coldFile), 449 Collections.singletonList(newFile)); 450 451 Pair<List<String>, Long> after = dataTieringManager.getRegionColdDataSize().get(regionName); 452 assertNotNull(after); 453 assertTrue("Cold compacted file should be removed from tracking", 454 after.getFirst().isEmpty() || !after.getFirst().contains(coldFile.getPath().getName())); 455 assertEquals(0L, (long) after.getSecond()); 456 } 457 458 /** 459 * Like {@link #testUpdateRegionColdDataSizeRemovesCompactedColdAddsNewHot}, but the replacement 460 * store file is still cold under TIME_RANGE rules so {@link DataTieringManager} should keep the 461 * region entry and record the new file's size. 462 */ 463 @Test 464 public void testUpdateRegionColdDataSizeRemovesCompactedColdAddsNewCold() throws IOException { 465 initializeTestEnvironment(); 466 dataTieringManager.getRegionColdDataSize().clear(); 467 468 HStoreFile coldFile = hStoreFiles.get(3); 469 String regionName = coldFile.getPath().getParent().getParent().getName(); 470 dataTieringManager.isHotData(coldFile.getFileInfo().getHFileInfo(), 471 coldFile.getFileInfo().getConf()); 472 473 HRegion region = testOnlineRegions.get(regionName); 474 assertNotNull(region); 475 HStore hStore = region.getStore(Bytes.toBytes("cf2")); 476 // Region2 hot-age is 2.5 * DAY; use 4 * DAY so the new file stays cold. 477 long coldTimestamp = System.currentTimeMillis() - 4 * DAY; 478 HStoreFile newFile = createHStoreFile(hStore.getStoreContext().getFamilyStoreDirectoryPath(), 479 hStore.getReadOnlyConfiguration(), coldTimestamp, region.getRegionFileSystem()); 480 newFile.initReader(); 481 hStore.refreshStoreFiles(); 482 483 assertFalse("new store file must be cold for this scenario", dataTieringManager 484 .isHotData(newFile.getFileInfo().getHFileInfo(), newFile.getFileInfo().getConf())); 485 486 dataTieringManager.updateRegionColdDataSize(regionName, Collections.singletonList(coldFile), 487 Collections.singletonList(newFile)); 488 489 Pair<List<String>, Long> after = dataTieringManager.getRegionColdDataSize().get(regionName); 490 assertNotNull(after); 491 assertFalse("compacted cold file should no longer be tracked", 492 after.getFirst().contains(coldFile.getPath().getName())); 493 assertEquals(1, after.getFirst().size()); 494 assertTrue(after.getFirst().contains(newFile.getPath().getName())); 495 long expectedNew = Bytes.toLong(newFile.getFileInfo().getHFileInfo().get(HFileInfo.FILE_SIZE)); 496 assertEquals(expectedNew, (long) after.getSecond()); 497 } 498 499 /* 500 * Verify that two cold blocks(both) are evicted when bucket reaches its capacity. The hot file 501 * remains in the cache. 502 */ 503 @Test 504 public void testBlockEvictions() throws Exception { 505 initializeTestEnvironment(); 506 long capacitySize = 40 * 1024; 507 int writeThreads = 3; 508 int writerQLen = 64; 509 int[] bucketSizes = new int[] { 8 * 1024 + 1024 }; 510 511 // Setup: Create a bucket cache with lower capacity 512 BucketCache bucketCache = 513 new BucketCache("file:" + testDir + "/bucket.cache", capacitySize, 8192, bucketSizes, 514 writeThreads, writerQLen, null, DEFAULT_ERROR_TOLERATION_DURATION, defaultConf); 515 516 // Create three Cache keys with cold data files and a block with hot data. 517 // hStoreFiles.get(3) is a cold data file, while hStoreFiles.get(0) is a hot file. 518 Set<BlockCacheKey> cacheKeys = new HashSet<>(); 519 cacheKeys.add(new BlockCacheKey(hStoreFiles.get(3).getPath(), 0, true, BlockType.DATA)); 520 cacheKeys.add(new BlockCacheKey(hStoreFiles.get(3).getPath(), 8192, true, BlockType.DATA)); 521 cacheKeys.add(new BlockCacheKey(hStoreFiles.get(0).getPath(), 0, true, BlockType.DATA)); 522 523 // Create dummy data to be cached and fill the cache completely. 524 CacheTestUtils.HFileBlockPair[] blocks = CacheTestUtils.generateHFileBlocks(8192, 3); 525 526 int blocksIter = 0; 527 for (BlockCacheKey key : cacheKeys) { 528 bucketCache.cacheBlock(key, blocks[blocksIter++].getBlock()); 529 // Ensure that the block is persisted to the file. 530 Waiter.waitFor(defaultConf, 10000, 100, () -> (bucketCache.getBackingMap().containsKey(key))); 531 } 532 533 // Verify that the bucket cache contains 3 blocks. 534 assertEquals(3, bucketCache.getBackingMap().keySet().size()); 535 536 // Add an additional block into cache with hot data which should trigger the eviction 537 BlockCacheKey newKey = new BlockCacheKey(hStoreFiles.get(2).getPath(), 0, true, BlockType.DATA); 538 CacheTestUtils.HFileBlockPair[] newBlock = CacheTestUtils.generateHFileBlocks(8192, 1); 539 540 bucketCache.cacheBlock(newKey, newBlock[0].getBlock()); 541 Waiter.waitFor(defaultConf, 10000, 100, 542 () -> (bucketCache.getBackingMap().containsKey(newKey))); 543 544 // Verify that the bucket cache now contains 2 hot blocks blocks only. 545 // Both cold blocks of 8KB will be evicted to make room for 1 block of 8KB + an additional 546 // space. 547 validateBlocks(bucketCache.getBackingMap().keySet(), 2, 2, 0); 548 } 549 550 /* 551 * Verify that two cold blocks(both) are evicted when bucket reaches its capacity, but one cold 552 * block remains in the cache since the required space is freed. 553 */ 554 @Test 555 public void testBlockEvictionsAllColdBlocks() throws Exception { 556 initializeTestEnvironment(); 557 long capacitySize = 40 * 1024; 558 int writeThreads = 3; 559 int writerQLen = 64; 560 int[] bucketSizes = new int[] { 8 * 1024 + 1024 }; 561 562 // Setup: Create a bucket cache with lower capacity 563 BucketCache bucketCache = 564 new BucketCache("file:" + testDir + "/bucket.cache", capacitySize, 8192, bucketSizes, 565 writeThreads, writerQLen, null, DEFAULT_ERROR_TOLERATION_DURATION, defaultConf); 566 567 // Create three Cache keys with three cold data blocks. 568 // hStoreFiles.get(3) is a cold data file. 569 Set<BlockCacheKey> cacheKeys = new HashSet<>(); 570 cacheKeys.add(new BlockCacheKey(hStoreFiles.get(3).getPath(), 0, true, BlockType.DATA)); 571 cacheKeys.add(new BlockCacheKey(hStoreFiles.get(3).getPath(), 8192, true, BlockType.DATA)); 572 cacheKeys.add(new BlockCacheKey(hStoreFiles.get(3).getPath(), 16384, true, BlockType.DATA)); 573 574 // Create dummy data to be cached and fill the cache completely. 575 CacheTestUtils.HFileBlockPair[] blocks = CacheTestUtils.generateHFileBlocks(8192, 3); 576 577 int blocksIter = 0; 578 for (BlockCacheKey key : cacheKeys) { 579 bucketCache.cacheBlock(key, blocks[blocksIter++].getBlock()); 580 // Ensure that the block is persisted to the file. 581 Waiter.waitFor(defaultConf, 10000, 100, () -> (bucketCache.getBackingMap().containsKey(key))); 582 } 583 584 // Verify that the bucket cache contains 3 blocks. 585 assertEquals(3, bucketCache.getBackingMap().keySet().size()); 586 587 // Add an additional block into cache with hot data which should trigger the eviction 588 BlockCacheKey newKey = new BlockCacheKey(hStoreFiles.get(2).getPath(), 0, true, BlockType.DATA); 589 CacheTestUtils.HFileBlockPair[] newBlock = CacheTestUtils.generateHFileBlocks(8192, 1); 590 591 bucketCache.cacheBlock(newKey, newBlock[0].getBlock()); 592 Waiter.waitFor(defaultConf, 10000, 100, 593 () -> (bucketCache.getBackingMap().containsKey(newKey))); 594 595 // Verify that the bucket cache now contains 1 cold block and a newly added hot block. 596 validateBlocks(bucketCache.getBackingMap().keySet(), 2, 1, 1); 597 } 598 599 /* 600 * Verify that a hot block evicted along with a cold block when bucket reaches its capacity. 601 */ 602 @Test 603 public void testBlockEvictionsHotBlocks() throws Exception { 604 initializeTestEnvironment(); 605 long capacitySize = 40 * 1024; 606 int writeThreads = 3; 607 int writerQLen = 64; 608 int[] bucketSizes = new int[] { 8 * 1024 + 1024 }; 609 610 // Setup: Create a bucket cache with lower capacity 611 BucketCache bucketCache = 612 new BucketCache("file:" + testDir + "/bucket.cache", capacitySize, 8192, bucketSizes, 613 writeThreads, writerQLen, null, DEFAULT_ERROR_TOLERATION_DURATION, defaultConf); 614 615 // Create three Cache keys with two hot data blocks and one cold data block 616 // hStoreFiles.get(0) is a hot data file and hStoreFiles.get(3) is a cold data file. 617 Set<BlockCacheKey> cacheKeys = new HashSet<>(); 618 cacheKeys.add(new BlockCacheKey(hStoreFiles.get(0).getPath(), 0, true, BlockType.DATA)); 619 cacheKeys.add(new BlockCacheKey(hStoreFiles.get(0).getPath(), 8192, true, BlockType.DATA)); 620 cacheKeys.add(new BlockCacheKey(hStoreFiles.get(3).getPath(), 0, true, BlockType.DATA)); 621 622 // Create dummy data to be cached and fill the cache completely. 623 CacheTestUtils.HFileBlockPair[] blocks = CacheTestUtils.generateHFileBlocks(8192, 3); 624 625 int blocksIter = 0; 626 for (BlockCacheKey key : cacheKeys) { 627 bucketCache.cacheBlock(key, blocks[blocksIter++].getBlock()); 628 // Ensure that the block is persisted to the file. 629 Waiter.waitFor(defaultConf, 10000, 100, () -> (bucketCache.getBackingMap().containsKey(key))); 630 } 631 632 // Verify that the bucket cache contains 3 blocks. 633 assertEquals(3, bucketCache.getBackingMap().keySet().size()); 634 635 // Add an additional block which should evict the only cold block with an additional hot block. 636 BlockCacheKey newKey = new BlockCacheKey(hStoreFiles.get(2).getPath(), 0, true, BlockType.DATA); 637 CacheTestUtils.HFileBlockPair[] newBlock = CacheTestUtils.generateHFileBlocks(8192, 1); 638 639 bucketCache.cacheBlock(newKey, newBlock[0].getBlock()); 640 Waiter.waitFor(defaultConf, 10000, 100, 641 () -> (bucketCache.getBackingMap().containsKey(newKey))); 642 643 // Verify that the bucket cache now contains 2 hot blocks. 644 // Only one of the older hot blocks is retained and other one is the newly added hot block. 645 validateBlocks(bucketCache.getBackingMap().keySet(), 2, 2, 0); 646 } 647 648 @Test 649 public void testFeatureKeyDisabled() throws Exception { 650 DataTieringManager.resetForTestingOnly(); 651 defaultConf.setBoolean(DataTieringManager.GLOBAL_DATA_TIERING_ENABLED_KEY, false); 652 initializeTestEnvironment(); 653 654 try { 655 assertFalse(DataTieringManager.instantiate(defaultConf, testOnlineRegions)); 656 // Verify that the DataaTieringManager instance is not instantiated in the 657 // instantiate call above. 658 assertNull(DataTieringManager.getInstance()); 659 660 // Also validate that data temperature is not honoured. 661 long capacitySize = 40 * 1024; 662 int writeThreads = 3; 663 int writerQLen = 64; 664 int[] bucketSizes = new int[] { 8 * 1024 + 1024 }; 665 666 // Setup: Create a bucket cache with lower capacity 667 BucketCache bucketCache = 668 new BucketCache("file:" + testDir + "/bucket.cache", capacitySize, 8192, bucketSizes, 669 writeThreads, writerQLen, null, DEFAULT_ERROR_TOLERATION_DURATION, defaultConf); 670 671 // Create three Cache keys with two hot data blocks and one cold data block 672 // hStoreFiles.get(0) is a hot data file and hStoreFiles.get(3) is a cold data file. 673 List<BlockCacheKey> cacheKeys = new ArrayList<>(); 674 cacheKeys.add(new BlockCacheKey(hStoreFiles.get(0).getPath(), 0, true, BlockType.DATA)); 675 cacheKeys.add(new BlockCacheKey(hStoreFiles.get(0).getPath(), 8192, true, BlockType.DATA)); 676 cacheKeys.add(new BlockCacheKey(hStoreFiles.get(3).getPath(), 0, true, BlockType.DATA)); 677 678 // Create dummy data to be cached and fill the cache completely. 679 CacheTestUtils.HFileBlockPair[] blocks = CacheTestUtils.generateHFileBlocks(8192, 3); 680 681 int blocksIter = 0; 682 for (BlockCacheKey key : cacheKeys) { 683 LOG.info("Adding {}", key); 684 bucketCache.cacheBlock(key, blocks[blocksIter++].getBlock()); 685 // Ensure that the block is persisted to the file. 686 Waiter.waitFor(defaultConf, 10000, 100, 687 () -> (bucketCache.getBackingMap().containsKey(key))); 688 } 689 690 // Verify that the bucket cache contains 3 blocks. 691 assertEquals(3, bucketCache.getBackingMap().keySet().size()); 692 693 // Add an additional hot block, which triggers eviction. 694 BlockCacheKey newKey = 695 new BlockCacheKey(hStoreFiles.get(2).getPath(), 0, true, BlockType.DATA); 696 CacheTestUtils.HFileBlockPair[] newBlock = CacheTestUtils.generateHFileBlocks(8192, 1); 697 698 bucketCache.cacheBlock(newKey, newBlock[0].getBlock()); 699 Waiter.waitFor(defaultConf, 10000, 100, 700 () -> (bucketCache.getBackingMap().containsKey(newKey))); 701 702 // Verify that the bucket still contains the only cold block and one newly added hot block. 703 // The older hot blocks are evicted and data-tiering mechanism does not kick in to evict 704 // the cold block. 705 validateBlocks(bucketCache.getBackingMap().keySet(), 2, 1, 1); 706 } finally { 707 DataTieringManager.resetForTestingOnly(); 708 defaultConf.setBoolean(DataTieringManager.GLOBAL_DATA_TIERING_ENABLED_KEY, true); 709 assertTrue(DataTieringManager.instantiate(defaultConf, testOnlineRegions)); 710 } 711 } 712 713 @Test 714 public void testCacheConfigShouldCacheFile() throws Exception { 715 // Evict the files from cache. 716 for (HStoreFile file : hStoreFiles) { 717 file.closeStoreFile(true); 718 } 719 // Verify that the API shouldCacheFileBlock returns the result correctly. 720 // hStoreFiles[0], hStoreFiles[1], hStoreFiles[2] are hot files. 721 // hStoreFiles[3] is a cold file. 722 try { 723 assertTrue(cacheConf.shouldCacheBlockOnRead(BlockCategory.DATA, 724 hStoreFiles.get(0).getFileInfo().getHFileInfo(), 725 hStoreFiles.get(0).getFileInfo().getConf())); 726 assertTrue(cacheConf.shouldCacheBlockOnRead(BlockCategory.DATA, 727 hStoreFiles.get(1).getFileInfo().getHFileInfo(), 728 hStoreFiles.get(1).getFileInfo().getConf())); 729 assertTrue(cacheConf.shouldCacheBlockOnRead(BlockCategory.DATA, 730 hStoreFiles.get(2).getFileInfo().getHFileInfo(), 731 hStoreFiles.get(2).getFileInfo().getConf())); 732 assertFalse(cacheConf.shouldCacheBlockOnRead(BlockCategory.DATA, 733 hStoreFiles.get(3).getFileInfo().getHFileInfo(), 734 hStoreFiles.get(3).getFileInfo().getConf())); 735 } finally { 736 for (HStoreFile file : hStoreFiles) { 737 file.initReader(); 738 } 739 } 740 } 741 742 @Test 743 public void testCacheOnReadColdFile() throws Exception { 744 initializeTestEnvironment(); 745 // hStoreFiles[3] is a cold file. the blocks should not get loaded after a readBlock call. 746 HStoreFile hStoreFile = hStoreFiles.get(3); 747 BlockCacheKey cacheKey = new BlockCacheKey(hStoreFile.getPath(), 0, true, BlockType.DATA); 748 testCacheOnRead(hStoreFile, cacheKey, -1, false); 749 } 750 751 @Test 752 public void testCacheOnReadHotFile() throws Exception { 753 initializeTestEnvironment(); 754 // hStoreFiles[0] is a hot file. the blocks should get loaded after a readBlock call. 755 HStoreFile hStoreFile = hStoreFiles.get(0); 756 BlockCacheKey cacheKey = 757 new BlockCacheKey(hStoreFiles.get(0).getPath(), 0, true, BlockType.DATA); 758 testCacheOnRead(hStoreFile, cacheKey, -1, true); 759 } 760 761 private void testCacheOnRead(HStoreFile hStoreFile, BlockCacheKey key, long onDiskBlockSize, 762 boolean expectedCached) throws Exception { 763 // Execute the read block API which will try to cache the block if the block is a hot block. 764 hStoreFile.getReader().getHFileReader().readBlock(key.getOffset(), onDiskBlockSize, true, false, 765 false, false, key.getBlockType(), DataBlockEncoding.NONE); 766 // Validate that the hot block gets cached and cold block is not cached. 767 HFileBlock block = (HFileBlock) blockCache.getBlock(key, false, false, false, BlockType.DATA); 768 if (expectedCached) { 769 assertNotNull(block); 770 } else { 771 assertNull(block); 772 } 773 } 774 775 private void validateBlocks(Set<BlockCacheKey> keys, int expectedTotalKeys, int expectedHotBlocks, 776 int expectedColdBlocks) { 777 int numHotBlocks = 0, numColdBlocks = 0; 778 779 Waiter.waitFor(defaultConf, 10000, 100, () -> (expectedTotalKeys == keys.size())); 780 int iter = 0; 781 for (BlockCacheKey key : keys) { 782 try { 783 if (dataTieringManager.isHotData(key)) { 784 numHotBlocks++; 785 } else { 786 numColdBlocks++; 787 } 788 } catch (Exception e) { 789 fail("Unexpected exception!"); 790 } 791 } 792 assertEquals(expectedHotBlocks, numHotBlocks); 793 assertEquals(expectedColdBlocks, numColdBlocks); 794 } 795 796 private void testDataTieringMethodWithKey(DataTieringMethodCallerWithKey caller, 797 BlockCacheKey key, boolean expectedResult, DataTieringException exception) { 798 try { 799 boolean value = caller.call(dataTieringManager, key); 800 if (exception != null) { 801 fail("Expected DataTieringException to be thrown"); 802 } 803 assertEquals(expectedResult, value); 804 } catch (DataTieringException e) { 805 if (exception == null) { 806 fail("Unexpected DataTieringException: " + e.getMessage()); 807 } 808 assertEquals(exception.getMessage(), e.getMessage()); 809 } 810 } 811 812 private void testDataTieringMethodWithKeyExpectingException(DataTieringMethodCallerWithKey caller, 813 BlockCacheKey key, DataTieringException exception) { 814 testDataTieringMethodWithKey(caller, key, false, exception); 815 } 816 817 private void testDataTieringMethodWithKeyNoException(DataTieringMethodCallerWithKey caller, 818 BlockCacheKey key, boolean expectedResult) { 819 testDataTieringMethodWithKey(caller, key, expectedResult, null); 820 } 821 822 private static void initializeTestEnvironment() throws IOException { 823 setupFileSystemAndCache(); 824 setupOnlineRegions(); 825 } 826 827 private static void setupFileSystemAndCache() throws IOException { 828 fs = HFileSystem.get(defaultConf); 829 blockCache = BlockCacheFactory.createBlockCache(defaultConf); 830 cacheConf = new CacheConfig(defaultConf, blockCache); 831 } 832 833 private static void setupOnlineRegions() throws IOException { 834 testOnlineRegions.clear(); 835 hStoreFiles.clear(); 836 long day = 24 * 60 * 60 * 1000; 837 long currentTime = System.currentTimeMillis(); 838 839 HRegion region1 = createHRegion("table1"); 840 841 HStore hStore11 = createHStore(region1, "cf1", getConfWithTimeRangeDataTieringEnabled(day)); 842 hStoreFiles.add(createHStoreFile(hStore11.getStoreContext().getFamilyStoreDirectoryPath(), 843 hStore11.getReadOnlyConfiguration(), currentTime, region1.getRegionFileSystem())); 844 hStore11.refreshStoreFiles(); 845 HStore hStore12 = createHStore(region1, "cf2"); 846 hStoreFiles.add(createHStoreFile(hStore12.getStoreContext().getFamilyStoreDirectoryPath(), 847 hStore12.getReadOnlyConfiguration(), currentTime - day, region1.getRegionFileSystem())); 848 hStore12.refreshStoreFiles(); 849 850 region1.stores.put(Bytes.toBytes("cf1"), hStore11); 851 region1.stores.put(Bytes.toBytes("cf2"), hStore12); 852 853 HRegion region2 = 854 createHRegion("table2", getConfWithTimeRangeDataTieringEnabled((long) (2.5 * day))); 855 856 HStore hStore21 = createHStore(region2, "cf1"); 857 hStoreFiles.add(createHStoreFile(hStore21.getStoreContext().getFamilyStoreDirectoryPath(), 858 hStore21.getReadOnlyConfiguration(), currentTime - 2 * day, region2.getRegionFileSystem())); 859 hStore21.refreshStoreFiles(); 860 HStore hStore22 = createHStore(region2, "cf2"); 861 hStoreFiles.add(createHStoreFile(hStore22.getStoreContext().getFamilyStoreDirectoryPath(), 862 hStore22.getReadOnlyConfiguration(), currentTime - 3 * day, region2.getRegionFileSystem())); 863 hStore22.refreshStoreFiles(); 864 865 region2.stores.put(Bytes.toBytes("cf1"), hStore21); 866 region2.stores.put(Bytes.toBytes("cf2"), hStore22); 867 868 for (HStoreFile file : hStoreFiles) { 869 file.initReader(); 870 } 871 872 testOnlineRegions.put(region1.getRegionInfo().getEncodedName(), region1); 873 testOnlineRegions.put(region2.getRegionInfo().getEncodedName(), region2); 874 } 875 876 private static HRegion createHRegion(String table) throws IOException { 877 return createHRegion(table, defaultConf); 878 } 879 880 private static HRegion createHRegion(String table, Configuration conf) throws IOException { 881 TableName tableName = TableName.valueOf(table); 882 883 TableDescriptor htd = TableDescriptorBuilder.newBuilder(tableName) 884 .setValue(DataTieringManager.DATATIERING_KEY, conf.get(DataTieringManager.DATATIERING_KEY)) 885 .setValue(DataTieringManager.DATATIERING_HOT_DATA_AGE_KEY, 886 conf.get(DataTieringManager.DATATIERING_HOT_DATA_AGE_KEY)) 887 .setValue(DataTieringManager.HSTORE_DATATIERING_GRACE_PERIOD_MILLIS_KEY, 888 conf.get(DataTieringManager.HSTORE_DATATIERING_GRACE_PERIOD_MILLIS_KEY)) 889 .build(); 890 RegionInfo hri = RegionInfoBuilder.newBuilder(tableName).build(); 891 892 Configuration testConf = new Configuration(conf); 893 CommonFSUtils.setRootDir(testConf, testDir); 894 HRegionFileSystem regionFs = HRegionFileSystem.createRegionOnFileSystem(testConf, fs, 895 CommonFSUtils.getTableDir(testDir, hri.getTable()), hri); 896 897 HRegion region = new HRegion(regionFs, null, conf, htd, null); 898 // Manually sets the BlockCache for the HRegion instance. 899 // This is necessary because the region server is not started within this method, 900 // and therefore the BlockCache needs to be explicitly configured. 901 region.setBlockCache(blockCache); 902 return region; 903 } 904 905 private static HStore createHStore(HRegion region, String columnFamily) throws IOException { 906 return createHStore(region, columnFamily, defaultConf); 907 } 908 909 private static HStore createHStore(HRegion region, String columnFamily, Configuration conf) 910 throws IOException { 911 ColumnFamilyDescriptor columnFamilyDescriptor = 912 ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes(columnFamily)) 913 .setValue(DataTieringManager.DATATIERING_KEY, conf.get(DataTieringManager.DATATIERING_KEY)) 914 .setValue(DataTieringManager.DATATIERING_HOT_DATA_AGE_KEY, 915 conf.get(DataTieringManager.DATATIERING_HOT_DATA_AGE_KEY)) 916 .setValue(DataTieringManager.HSTORE_DATATIERING_GRACE_PERIOD_MILLIS_KEY, 917 conf.get(DataTieringManager.HSTORE_DATATIERING_GRACE_PERIOD_MILLIS_KEY)) 918 .build(); 919 920 return new HStore(region, columnFamilyDescriptor, conf, false); 921 } 922 923 private static Configuration getConfWithTimeRangeDataTieringEnabled(long hotDataAge) { 924 Configuration conf = new Configuration(defaultConf); 925 conf.set(DataTieringManager.DATATIERING_KEY, DataTieringType.TIME_RANGE.name()); 926 conf.set(DataTieringManager.DATATIERING_HOT_DATA_AGE_KEY, String.valueOf(hotDataAge)); 927 return conf; 928 } 929 930 private static Configuration getConfWithGracePeriod(long hotDataAge, long gracePeriod) { 931 Configuration conf = getConfWithTimeRangeDataTieringEnabled(hotDataAge); 932 conf.set(DataTieringManager.HSTORE_DATATIERING_GRACE_PERIOD_MILLIS_KEY, 933 String.valueOf(gracePeriod)); 934 return conf; 935 } 936 937 static HStoreFile createHStoreFile(Path storeDir, Configuration conf, long timestamp, 938 HRegionFileSystem regionFs) throws IOException { 939 String columnFamily = storeDir.getName(); 940 941 StoreFileWriter storeFileWriter = new StoreFileWriter.Builder(conf, cacheConf, fs) 942 .withOutputDir(storeDir).withFileContext(new HFileContextBuilder().build()).build(); 943 944 writeStoreFileRandomData(storeFileWriter, Bytes.toBytes(columnFamily), timestamp); 945 946 StoreContext storeContext = StoreContext.getBuilder().withRegionFileSystem(regionFs).build(); 947 948 StoreFileTracker sft = StoreFileTrackerFactory.create(conf, true, storeContext); 949 return new HStoreFile(fs, storeFileWriter.getPath(), conf, cacheConf, BloomType.NONE, true, 950 sft); 951 } 952 953 /** 954 * Writes random data to a store file with rows arranged in lexicographically increasing order. 955 * Each row is generated using the {@link #nextString()} method, ensuring that each subsequent row 956 * is lexicographically larger than the previous one. 957 */ 958 private static void writeStoreFileRandomData(final StoreFileWriter writer, byte[] columnFamily, 959 long timestamp) throws IOException { 960 int cellsPerFile = 10; 961 byte[] qualifier = Bytes.toBytes("qualifier"); 962 byte[] value = generateRandomBytes(4 * 1024); 963 try { 964 for (int i = 0; i < cellsPerFile; i++) { 965 byte[] row = Bytes.toBytes(nextString()); 966 writer.append(new KeyValue(row, columnFamily, qualifier, timestamp, value)); 967 } 968 } finally { 969 writer.appendTrackedTimestampsToMetadata(); 970 writer.close(); 971 } 972 } 973 974 private static byte[] generateRandomBytes(int sizeInBytes) { 975 Random random = new Random(); 976 byte[] randomBytes = new byte[sizeInBytes]; 977 random.nextBytes(randomBytes); 978 return randomBytes; 979 } 980 981 /** 982 * Returns the lexicographically larger string every time it's called. 983 */ 984 private static String nextString() { 985 if (rowKeyString == null || rowKeyString.isEmpty()) { 986 rowKeyString = "a"; 987 } 988 char lastChar = rowKeyString.charAt(rowKeyString.length() - 1); 989 if (lastChar < 'z') { 990 rowKeyString = rowKeyString.substring(0, rowKeyString.length() - 1) + (char) (lastChar + 1); 991 } else { 992 rowKeyString = rowKeyString + "a"; 993 } 994 return rowKeyString; 995 } 996}