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.mob; 019 020import static org.junit.jupiter.api.Assertions.assertEquals; 021import static org.junit.jupiter.api.Assertions.assertTrue; 022import static org.junit.jupiter.api.Assertions.fail; 023 024import java.io.IOException; 025import java.util.Arrays; 026import java.util.List; 027import java.util.stream.Collectors; 028import java.util.stream.Stream; 029import org.apache.hadoop.conf.Configuration; 030import org.apache.hadoop.fs.FileStatus; 031import org.apache.hadoop.fs.FileSystem; 032import org.apache.hadoop.fs.Path; 033import org.apache.hadoop.hbase.HBaseParameterizedTestTemplate; 034import org.apache.hadoop.hbase.HBaseTestingUtil; 035import org.apache.hadoop.hbase.ServerName; 036import org.apache.hadoop.hbase.TableName; 037import org.apache.hadoop.hbase.client.Admin; 038import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor; 039import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder; 040import org.apache.hadoop.hbase.client.CompactionState; 041import org.apache.hadoop.hbase.client.Put; 042import org.apache.hadoop.hbase.client.Result; 043import org.apache.hadoop.hbase.client.ResultScanner; 044import org.apache.hadoop.hbase.client.Table; 045import org.apache.hadoop.hbase.client.TableDescriptor; 046import org.apache.hadoop.hbase.regionserver.storefiletracker.StoreFileTrackerFactory; 047import org.apache.hadoop.hbase.testclassification.LargeTests; 048import org.apache.hadoop.hbase.util.Bytes; 049import org.apache.hadoop.hbase.util.RegionSplitter; 050import org.junit.jupiter.api.AfterEach; 051import org.junit.jupiter.api.BeforeEach; 052import org.junit.jupiter.api.Tag; 053import org.junit.jupiter.api.TestInfo; 054import org.junit.jupiter.api.TestTemplate; 055import org.junit.jupiter.params.provider.Arguments; 056import org.slf4j.Logger; 057import org.slf4j.LoggerFactory; 058 059/** 060 * Mob file compaction base test. 1. Enables batch mode for regular MOB compaction, Sets batch size 061 * to 7 regions. (Optional) 2. Disables periodic MOB compactions, sets minimum age to archive to 10 062 * sec 3. Creates MOB table with 20 regions 4. Loads MOB data (randomized keys, 1000 rows), flushes 063 * data. 5. Repeats 4. two more times 6. Verifies that we have 20 *3 = 60 mob files (equals to 064 * number of regions x 3) 7. Runs major MOB compaction. 8. Verifies that number of MOB files in a 065 * mob directory is 20 x4 = 80 9. Waits for a period of time larger than minimum age to archive 10. 066 * Runs Mob cleaner chore 11 Verifies that number of MOB files in a mob directory is 20. 12 Runs 067 * scanner and checks all 3 * 1000 rows. 068 */ 069@Tag(LargeTests.TAG) 070@HBaseParameterizedTestTemplate(name = "{index}: useFileBasedSFT={0}") 071public class TestMobCompactionWithDefaults { 072 private static final Logger LOG = LoggerFactory.getLogger(TestMobCompactionWithDefaults.class); 073 074 protected HBaseTestingUtil HTU; 075 protected static Configuration conf; 076 protected static long minAgeToArchive = 10000; 077 078 protected final static String famStr = "f1"; 079 protected final static byte[] fam = Bytes.toBytes(famStr); 080 protected final static byte[] qualifier = Bytes.toBytes("q1"); 081 protected final static long mobLen = 10; 082 protected final static byte[] mobVal = Bytes 083 .toBytes("01234567890123456789012345678901234567890123456789012345678901234567890123456789"); 084 085 private String testMethodName; 086 protected TableDescriptor tableDescriptor; 087 private ColumnFamilyDescriptor familyDescriptor; 088 protected Admin admin; 089 protected TableName table = null; 090 protected int numRegions = 20; 091 protected int rows = 1000; 092 093 protected Boolean useFileBasedSFT; 094 095 public TestMobCompactionWithDefaults(Boolean useFileBasedSFT) { 096 this.useFileBasedSFT = useFileBasedSFT; 097 } 098 099 public static Stream<Arguments> parameters() { 100 return Stream.of(false, true).map(Arguments::of); 101 } 102 103 protected void htuStart() throws Exception { 104 HTU = new HBaseTestingUtil(); 105 conf = HTU.getConfiguration(); 106 conf.setInt("hfile.format.version", 3); 107 // Disable automatic MOB compaction 108 conf.setLong(MobConstants.MOB_COMPACTION_CHORE_PERIOD, 0); 109 // Disable automatic MOB file cleaner chore 110 conf.setLong(MobConstants.MOB_CLEANER_PERIOD, 0); 111 // Set minimum age to archive to 10 sec 112 conf.setLong(MobConstants.MIN_AGE_TO_ARCHIVE_KEY, minAgeToArchive); 113 // Set compacted file discharger interval to a half minAgeToArchive 114 conf.setLong("hbase.hfile.compaction.discharger.interval", minAgeToArchive / 2); 115 conf.setBoolean("hbase.regionserver.compaction.enabled", false); 116 if (useFileBasedSFT) { 117 conf.set(StoreFileTrackerFactory.TRACKER_IMPL, 118 "org.apache.hadoop.hbase.regionserver.storefiletracker.FileBasedStoreFileTracker"); 119 } 120 additonalConfigSetup(); 121 HTU.startMiniCluster(); 122 } 123 124 protected void additonalConfigSetup() { 125 } 126 127 @BeforeEach 128 public void setUp(TestInfo testInfo) throws Exception { 129 testMethodName = testInfo.getTestMethod().get().getName() 130 + testInfo.getDisplayName().replaceAll("[:= ]", "_").replaceAll("_+", "_").trim(); 131 htuStart(); 132 admin = HTU.getAdmin(); 133 familyDescriptor = ColumnFamilyDescriptorBuilder.newBuilder(fam).setMobEnabled(true) 134 .setMobThreshold(mobLen).setMaxVersions(1).build(); 135 tableDescriptor = HTU.createModifyableTableDescriptor(TestMobUtils.getTableName(testMethodName)) 136 .setColumnFamily(familyDescriptor).build(); 137 RegionSplitter.UniformSplit splitAlgo = new RegionSplitter.UniformSplit(); 138 byte[][] splitKeys = splitAlgo.split(numRegions); 139 table = HTU.createTable(tableDescriptor, splitKeys).getName(); 140 } 141 142 private void loadData(TableName tableName, int num) { 143 LOG.info("Started loading {} rows into {}", num, tableName); 144 try (final Table table = HTU.getConnection().getTable(tableName)) { 145 for (int i = 0; i < num; i++) { 146 byte[] key = new byte[32]; 147 Bytes.random(key); 148 Put p = new Put(key); 149 p.addColumn(fam, qualifier, mobVal); 150 table.put(p); 151 } 152 admin.flush(tableName); 153 LOG.info("Finished loading {} rows into {}", num, tableName); 154 } catch (Exception e) { 155 LOG.error("MOB file compaction chore test FAILED", e); 156 fail("MOB file compaction chore test FAILED"); 157 } 158 } 159 160 @AfterEach 161 public void tearDown() throws Exception { 162 admin.disableTable(tableDescriptor.getTableName()); 163 admin.deleteTable(tableDescriptor.getTableName()); 164 HTU.shutdownMiniCluster(); 165 } 166 167 @TestTemplate 168 public void baseTestMobFileCompaction() throws InterruptedException, IOException { 169 LOG.info("MOB compaction " + description() + " started"); 170 loadAndFlushThreeTimes(rows, table, famStr); 171 mobCompact(tableDescriptor, familyDescriptor); 172 assertEquals(numRegions * 4, getNumberOfMobFiles(table, famStr), 173 "Should have 4 MOB files per region due to 3xflush + compaction."); 174 cleanupAndVerifyCounts(table, famStr, 3 * rows); 175 LOG.info("MOB compaction " + description() + " finished OK"); 176 } 177 178 @TestTemplate 179 public void testMobFileCompactionAfterSnapshotClone() throws InterruptedException, IOException { 180 final TableName clone = TableName.valueOf(TestMobUtils.getTableName(testMethodName) + "-clone"); 181 LOG.info("MOB compaction of cloned snapshot, " + description() + " started"); 182 loadAndFlushThreeTimes(rows, table, famStr); 183 LOG.debug("Taking snapshot and cloning table {}", table); 184 admin.snapshot(TestMobUtils.getTableName(testMethodName), table); 185 admin.cloneSnapshot(TestMobUtils.getTableName(testMethodName), clone); 186 assertEquals(3 * numRegions, getNumberOfMobFiles(clone, famStr), 187 "Should have 3 hlinks per region in MOB area from snapshot clone"); 188 mobCompact(admin.getDescriptor(clone), familyDescriptor); 189 assertEquals(4 * numRegions, getNumberOfMobFiles(clone, famStr), 190 "Should have 3 hlinks + 1 MOB file per region due to clone + compact"); 191 cleanupAndVerifyCounts(clone, famStr, 3 * rows); 192 LOG.info("MOB compaction of cloned snapshot, " + description() + " finished OK"); 193 } 194 195 @TestTemplate 196 public void testMobFileCompactionAfterSnapshotCloneAndFlush() 197 throws InterruptedException, IOException { 198 final TableName clone = TableName.valueOf(TestMobUtils.getTableName(testMethodName) + "-clone"); 199 LOG.info("MOB compaction of cloned snapshot after flush, " + description() + " started"); 200 loadAndFlushThreeTimes(rows, table, famStr); 201 LOG.debug("Taking snapshot and cloning table {}", table); 202 admin.snapshot(TestMobUtils.getTableName(testMethodName), table); 203 admin.cloneSnapshot(TestMobUtils.getTableName(testMethodName), clone); 204 assertEquals(3 * numRegions, getNumberOfMobFiles(clone, famStr), 205 "Should have 3 hlinks per region in MOB area from snapshot clone"); 206 loadAndFlushThreeTimes(rows, clone, famStr); 207 mobCompact(admin.getDescriptor(clone), familyDescriptor); 208 assertEquals(7 * numRegions, getNumberOfMobFiles(clone, famStr), 209 "Should have 7 MOB file per region due to clone + 3xflush + compact"); 210 cleanupAndVerifyCounts(clone, famStr, 6 * rows); 211 LOG.info("MOB compaction of cloned snapshot w flush, " + description() + " finished OK"); 212 } 213 214 protected void loadAndFlushThreeTimes(int rows, TableName table, String family) 215 throws IOException { 216 final long start = getNumberOfMobFiles(table, family); 217 // Load and flush data 3 times 218 loadData(table, rows); 219 loadData(table, rows); 220 loadData(table, rows); 221 assertEquals(start + numRegions * 3, getNumberOfMobFiles(table, family), 222 "Should have 3 more mob files per region from flushing."); 223 } 224 225 protected String description() { 226 return "regular mode"; 227 } 228 229 protected void enableCompactions() throws IOException { 230 final List<String> serverList = 231 admin.getRegionServers().stream().map(sn -> sn.getServerName()).collect(Collectors.toList()); 232 admin.compactionSwitch(true, serverList); 233 } 234 235 protected void disableCompactions() throws IOException { 236 final List<String> serverList = 237 admin.getRegionServers().stream().map(sn -> sn.getServerName()).collect(Collectors.toList()); 238 admin.compactionSwitch(false, serverList); 239 } 240 241 /** 242 * compact the given table and return once it is done. should presume compactions are disabled 243 * when called. should ensure compactions are disabled before returning. 244 */ 245 protected void mobCompact(TableDescriptor tableDescriptor, 246 ColumnFamilyDescriptor familyDescriptor) throws IOException, InterruptedException { 247 LOG.debug("Major compact MOB table " + tableDescriptor.getTableName()); 248 enableCompactions(); 249 mobCompactImpl(tableDescriptor, familyDescriptor); 250 waitUntilCompactionIsComplete(tableDescriptor.getTableName()); 251 disableCompactions(); 252 } 253 254 /** 255 * Call the API for compaction specific to the test set. should not wait for compactions to 256 * finish. may assume compactions are enabled when called. 257 */ 258 protected void mobCompactImpl(TableDescriptor tableDescriptor, 259 ColumnFamilyDescriptor familyDescriptor) throws IOException, InterruptedException { 260 admin.majorCompact(tableDescriptor.getTableName(), familyDescriptor.getName()); 261 } 262 263 protected void waitUntilCompactionIsComplete(TableName table) 264 throws IOException, InterruptedException { 265 CompactionState state = admin.getCompactionState(table); 266 while (state != CompactionState.NONE) { 267 LOG.debug("Waiting for compaction on {} to complete. current state {}", table, state); 268 Thread.sleep(100); 269 state = admin.getCompactionState(table); 270 } 271 LOG.debug("done waiting for compaction on {}", table); 272 } 273 274 protected void cleanupAndVerifyCounts(TableName table, String family, int rows) 275 throws InterruptedException, IOException { 276 // We have guarantee, that compacted file discharger will run during this pause 277 // because it has interval less than this wait time 278 LOG.info("Waiting for {}ms", minAgeToArchive + 1000); 279 280 Thread.sleep(minAgeToArchive + 1000); 281 LOG.info("Cleaning up MOB files"); 282 283 // run cleaner chore on each RS 284 for (ServerName sn : admin.getRegionServers()) { 285 HTU.getMiniHBaseCluster().getRegionServer(sn).getRSMobFileCleanerChore().chore(); 286 } 287 288 assertEquals(numRegions, getNumberOfMobFiles(table, family), 289 "After cleaning, we should have 1 MOB file per region based on size."); 290 291 LOG.debug("checking count of rows"); 292 long scanned = scanTable(table); 293 assertEquals(rows, scanned, "Got the wrong number of rows in table " + table + " cf " + family); 294 295 } 296 297 protected long getNumberOfMobFiles(TableName tableName, String family) throws IOException { 298 FileSystem fs = FileSystem.get(conf); 299 Path dir = MobUtils.getMobFamilyPath(conf, tableName, family); 300 FileStatus[] stat = fs.listStatus(dir); 301 for (FileStatus st : stat) { 302 LOG.debug("MOB Directory content: {}", st.getPath()); 303 } 304 LOG.debug("MOB Directory content total files: {}", stat.length); 305 306 return stat.length; 307 } 308 309 protected long scanTable(TableName tableName) { 310 try (final Table table = HTU.getConnection().getTable(tableName); 311 final ResultScanner scanner = table.getScanner(fam)) { 312 Result result; 313 long counter = 0; 314 while ((result = scanner.next()) != null) { 315 assertTrue(Arrays.equals(result.getValue(fam, qualifier), mobVal)); 316 counter++; 317 } 318 return counter; 319 } catch (Exception e) { 320 LOG.error("MOB file compaction test FAILED", e); 321 if (HTU != null) { 322 fail(e.getMessage()); 323 } else { 324 System.exit(-1); 325 } 326 } 327 return 0; 328 } 329}