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.apache.hadoop.hbase.mob.MobConstants.MOB_CLEANER_BATCH_SIZE_UPPER_BOUND; 021import static org.apache.hadoop.hbase.mob.MobConstants.MOB_CLEANER_THREAD_COUNT; 022import static org.junit.Assert.assertEquals; 023 024import java.io.IOException; 025import org.apache.hadoop.fs.FileStatus; 026import org.apache.hadoop.fs.Path; 027import org.apache.hadoop.hbase.HBaseClassTestRule; 028import org.apache.hadoop.hbase.HBaseTestingUtil; 029import org.apache.hadoop.hbase.TableName; 030import org.apache.hadoop.hbase.client.Admin; 031import org.apache.hadoop.hbase.client.BufferedMutator; 032import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor; 033import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder; 034import org.apache.hadoop.hbase.client.ConnectionFactory; 035import org.apache.hadoop.hbase.client.Put; 036import org.apache.hadoop.hbase.client.TableDescriptorBuilder; 037import org.apache.hadoop.hbase.testclassification.MasterTests; 038import org.apache.hadoop.hbase.testclassification.MediumTests; 039import org.apache.hadoop.hbase.util.Bytes; 040import org.apache.hadoop.hbase.util.EnvironmentEdgeManager; 041import org.junit.After; 042import org.junit.AfterClass; 043import org.junit.Assert; 044import org.junit.BeforeClass; 045import org.junit.ClassRule; 046import org.junit.Test; 047import org.junit.experimental.categories.Category; 048 049@Category({ MediumTests.class, MasterTests.class }) 050public class TestExpiredMobFileCleanerChore { 051 052 @ClassRule 053 public static final HBaseClassTestRule CLASS_RULE = 054 HBaseClassTestRule.forClass(TestExpiredMobFileCleanerChore.class); 055 private final static HBaseTestingUtil TEST_UTIL = new HBaseTestingUtil(); 056 private final static TableName tableName = TableName.valueOf("TestExpiredMobFileCleaner"); 057 private final static TableName tableName2 = TableName.valueOf("TestExpiredMobFileCleaner2"); 058 private final static String family = "family"; 059 private final static byte[] row1 = Bytes.toBytes("row1"); 060 private final static byte[] row2 = Bytes.toBytes("row2"); 061 private final static byte[] row3 = Bytes.toBytes("row3"); 062 private final static byte[] qf = Bytes.toBytes("qf"); 063 064 private static BufferedMutator table; 065 private static Admin admin; 066 private static BufferedMutator table2; 067 private static MobFileCleanerChore mobFileCleanerChore; 068 069 @BeforeClass 070 public static void setUp() throws Exception { 071 TEST_UTIL.getConfiguration().setInt("hfile.format.version", 3); 072 TEST_UTIL.getConfiguration().setInt(MOB_CLEANER_BATCH_SIZE_UPPER_BOUND, 2); 073 TEST_UTIL.startMiniCluster(1); 074 mobFileCleanerChore = TEST_UTIL.getMiniHBaseCluster().getMaster().getMobFileCleanerChore(); 075 } 076 077 @After 078 public void cleanUp() throws IOException { 079 admin.disableTable(tableName); 080 admin.deleteTable(tableName); 081 admin.disableTable(tableName2); 082 admin.deleteTable(tableName2); 083 admin.close(); 084 } 085 086 @AfterClass 087 public static void tearDown() throws Exception { 088 TEST_UTIL.shutdownMiniCluster(); 089 TEST_UTIL.getTestFileSystem().delete(TEST_UTIL.getDataTestDir(), true); 090 } 091 092 @Test 093 public void testCleanerSingleThread() throws Exception { 094 TEST_UTIL.getConfiguration().setInt(MOB_CLEANER_THREAD_COUNT, 1); 095 mobFileCleanerChore.onConfigurationChange(TEST_UTIL.getConfiguration()); 096 int corePoolSize = mobFileCleanerChore.getExecutor().getCorePoolSize(); 097 Assert.assertEquals(1, corePoolSize); 098 testCleanerInternal(); 099 } 100 101 @Test 102 public void testCleanerMultiThread() throws Exception { 103 TEST_UTIL.getConfiguration().setInt(MOB_CLEANER_THREAD_COUNT, 2); 104 mobFileCleanerChore.onConfigurationChange(TEST_UTIL.getConfiguration()); 105 int corePoolSize = mobFileCleanerChore.getExecutor().getCorePoolSize(); 106 Assert.assertEquals(2, corePoolSize); 107 testCleanerInternal(); 108 } 109 110 private static void init() throws Exception { 111 TableDescriptorBuilder tableDescriptorBuilder = TableDescriptorBuilder.newBuilder(tableName); 112 ColumnFamilyDescriptor columnFamilyDescriptor = 113 ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes(family)).setMobEnabled(true) 114 .setMobThreshold(3L).setMaxVersions(4).build(); 115 tableDescriptorBuilder.setColumnFamily(columnFamilyDescriptor); 116 117 admin = TEST_UTIL.getAdmin(); 118 admin.createTable(tableDescriptorBuilder.build()); 119 120 table = ConnectionFactory.createConnection(TEST_UTIL.getConfiguration()) 121 .getBufferedMutator(tableName); 122 123 TableDescriptorBuilder tableDescriptorBuilder2 = TableDescriptorBuilder.newBuilder(tableName2); 124 ColumnFamilyDescriptor columnFamilyDescriptor2 = 125 ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes(family)).setMobEnabled(true) 126 .setMobThreshold(3L).setMaxVersions(4).build(); 127 tableDescriptorBuilder2.setColumnFamily(columnFamilyDescriptor2); 128 admin.createTable(tableDescriptorBuilder2.build()); 129 130 table2 = ConnectionFactory.createConnection(TEST_UTIL.getConfiguration()) 131 .getBufferedMutator(tableName2); 132 } 133 134 private static void modifyColumnExpiryDays(int expireDays) throws Exception { 135 136 // change ttl as expire days to make some row expired 137 int timeToLive = expireDays * secondsOfDay(); 138 ColumnFamilyDescriptorBuilder columnFamilyDescriptorBuilder = ColumnFamilyDescriptorBuilder 139 .newBuilder(Bytes.toBytes(family)).setMobEnabled(true).setMobThreshold(3L); 140 columnFamilyDescriptorBuilder.setTimeToLive(timeToLive); 141 142 admin.modifyColumnFamily(tableName, columnFamilyDescriptorBuilder.build()); 143 144 ColumnFamilyDescriptorBuilder columnFamilyDescriptorBuilder2 = ColumnFamilyDescriptorBuilder 145 .newBuilder(Bytes.toBytes(family)).setMobEnabled(true).setMobThreshold(3L); 146 columnFamilyDescriptorBuilder2.setTimeToLive(timeToLive); 147 148 admin.modifyColumnFamily(tableName2, columnFamilyDescriptorBuilder2.build()); 149 } 150 151 private static void putKVAndFlush(BufferedMutator table, byte[] row, byte[] value, long ts, 152 TableName tableName) throws Exception { 153 154 Put put = new Put(row, ts); 155 put.addColumn(Bytes.toBytes(family), qf, value); 156 table.mutate(put); 157 158 table.flush(); 159 admin.flush(tableName); 160 } 161 162 /** 163 * Creates a 3 day old hfile and an 1 day old hfile then sets expiry to 2 days. Verifies that the 164 * 3 day old hfile is removed but the 1 day one is still present after the expiry based cleaner is 165 * run. 166 */ 167 private static void testCleanerInternal() throws Exception { 168 init(); 169 170 Path mobDirPath = MobUtils.getMobFamilyPath(TEST_UTIL.getConfiguration(), tableName, family); 171 172 byte[] dummyData = makeDummyData(600); 173 long ts = EnvironmentEdgeManager.currentTime() - 3 * secondsOfDay() * 1000; // 3 days before 174 putKVAndFlush(table, row1, dummyData, ts, tableName); 175 FileStatus[] firstFiles = TEST_UTIL.getTestFileSystem().listStatus(mobDirPath); 176 // the first mob file 177 assertEquals("Before cleanup without delay 1", 1, firstFiles.length); 178 String firstFile = firstFiles[0].getPath().getName(); 179 180 // 1.5 day before 181 ts = (long) (EnvironmentEdgeManager.currentTime() - 1.5 * secondsOfDay() * 1000); 182 putKVAndFlush(table, row2, dummyData, ts, tableName); 183 FileStatus[] secondFiles = TEST_UTIL.getTestFileSystem().listStatus(mobDirPath); 184 // now there are 2 mob files 185 assertEquals("Before cleanup without delay 2", 2, secondFiles.length); 186 String f1 = secondFiles[0].getPath().getName(); 187 String f2 = secondFiles[1].getPath().getName(); 188 String secondFile = f1.equals(firstFile) ? f2 : f1; 189 190 ts = EnvironmentEdgeManager.currentTime() - 4 * secondsOfDay() * 1000; // 4 days before 191 putKVAndFlush(table, row3, dummyData, ts, tableName); 192 ts = EnvironmentEdgeManager.currentTime() - 4 * secondsOfDay() * 1000; // 4 days before 193 putKVAndFlush(table, row3, dummyData, ts, tableName); 194 FileStatus[] thirdFiles = TEST_UTIL.getTestFileSystem().listStatus(mobDirPath); 195 // now there are 4 mob files 196 assertEquals("Before cleanup without delay 3", 4, thirdFiles.length); 197 198 // modifyColumnExpiryDays(2); // ttl = 2, make the first row expired 199 200 // for table 2 201 Path mobDirPath2 = MobUtils.getMobFamilyPath(TEST_UTIL.getConfiguration(), tableName2, family); 202 203 byte[] dummyData2 = makeDummyData(600); 204 205 putKVAndFlush(table2, row1, dummyData2, ts, tableName2); 206 FileStatus[] firstFiles2 = TEST_UTIL.getTestFileSystem().listStatus(mobDirPath2); 207 // the first mob file 208 assertEquals("Before cleanup without delay 1", 1, firstFiles2.length); 209 String firstFile2 = firstFiles2[0].getPath().getName(); 210 211 // 1.5 day before 212 ts = (long) (EnvironmentEdgeManager.currentTime() - 1.5 * secondsOfDay() * 1000); 213 putKVAndFlush(table2, row2, dummyData2, ts, tableName2); 214 FileStatus[] secondFiles2 = TEST_UTIL.getTestFileSystem().listStatus(mobDirPath2); 215 // now there are 2 mob files 216 assertEquals("Before cleanup without delay 2", 2, secondFiles2.length); 217 String f1Second = secondFiles2[0].getPath().getName(); 218 String f2Second = secondFiles2[1].getPath().getName(); 219 String secondFile2 = f1Second.equals(firstFile2) ? f2Second : f1Second; 220 ts = EnvironmentEdgeManager.currentTime() - 4 * secondsOfDay() * 1000; // 4 days before 221 putKVAndFlush(table2, row3, dummyData2, ts, tableName2); 222 ts = EnvironmentEdgeManager.currentTime() - 4 * secondsOfDay() * 1000; // 4 days before 223 putKVAndFlush(table2, row3, dummyData2, ts, tableName2); 224 FileStatus[] thirdFiles2 = TEST_UTIL.getTestFileSystem().listStatus(mobDirPath2); 225 // now there are 4 mob files 226 assertEquals("Before cleanup without delay 3", 4, thirdFiles2.length); 227 228 modifyColumnExpiryDays(2); // ttl = 2, make the first row expired 229 230 // run the cleaner chore 231 mobFileCleanerChore.chore(); 232 233 FileStatus[] filesAfterClean = TEST_UTIL.getTestFileSystem().listStatus(mobDirPath); 234 String lastFile = filesAfterClean[0].getPath().getName(); 235 // there are 4 mob files in total, but only 3 need to be cleaned 236 assertEquals("After cleanup without delay 1", 1, filesAfterClean.length); 237 assertEquals("After cleanup without delay 2", secondFile, lastFile); 238 239 filesAfterClean = TEST_UTIL.getTestFileSystem().listStatus(mobDirPath2); 240 lastFile = filesAfterClean[0].getPath().getName(); 241 // there are 4 mob files in total, but only 3 need to be cleaned 242 assertEquals("After cleanup without delay 1", 1, filesAfterClean.length); 243 assertEquals("After cleanup without delay 2", secondFile2, lastFile); 244 } 245 246 private static int secondsOfDay() { 247 return 24 * 3600; 248 } 249 250 private static byte[] makeDummyData(int size) { 251 byte[] dummyData = new byte[size]; 252 Bytes.random(dummyData); 253 return dummyData; 254 } 255}