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