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.HBaseTestingUtil.START_KEY_BYTES; 021import static org.apache.hadoop.hbase.HBaseTestingUtil.fam1; 022import static org.apache.hadoop.hbase.HBaseTestingUtil.fam2; 023import static org.junit.Assert.assertEquals; 024import static org.junit.Assert.assertTrue; 025 026import java.io.IOException; 027import java.util.Collection; 028import java.util.List; 029import java.util.Optional; 030import java.util.stream.Collectors; 031import org.apache.hadoop.conf.Configuration; 032import org.apache.hadoop.hbase.HBaseClassTestRule; 033import org.apache.hadoop.hbase.HBaseTestingUtil; 034import org.apache.hadoop.hbase.HConstants; 035import org.apache.hadoop.hbase.HTestConst; 036import org.apache.hadoop.hbase.TableName; 037import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder; 038import org.apache.hadoop.hbase.client.Delete; 039import org.apache.hadoop.hbase.client.Get; 040import org.apache.hadoop.hbase.client.Result; 041import org.apache.hadoop.hbase.client.Table; 042import org.apache.hadoop.hbase.client.TableDescriptor; 043import org.apache.hadoop.hbase.regionserver.compactions.CompactionContext; 044import org.apache.hadoop.hbase.regionserver.compactions.CompactionRequestImpl; 045import org.apache.hadoop.hbase.regionserver.compactions.RatioBasedCompactionPolicy; 046import org.apache.hadoop.hbase.regionserver.throttle.NoLimitThroughputController; 047import org.apache.hadoop.hbase.testclassification.RegionServerTests; 048import org.apache.hadoop.hbase.testclassification.SmallTests; 049import org.apache.hadoop.hbase.util.Bytes; 050import org.apache.hadoop.hbase.wal.WAL; 051import org.junit.After; 052import org.junit.Before; 053import org.junit.BeforeClass; 054import org.junit.ClassRule; 055import org.junit.Rule; 056import org.junit.Test; 057import org.junit.experimental.categories.Category; 058import org.junit.rules.TestName; 059 060/** 061 * Test minor compactions 062 */ 063@Category({ RegionServerTests.class, SmallTests.class }) 064public class TestMinorCompaction { 065 066 @ClassRule 067 public static final HBaseClassTestRule CLASS_RULE = 068 HBaseClassTestRule.forClass(TestMinorCompaction.class); 069 070 @Rule 071 public TestName name = new TestName(); 072 private static final HBaseTestingUtil UTIL = new HBaseTestingUtil(); 073 private static Configuration CONF = UTIL.getConfiguration(); 074 075 private HRegion r = null; 076 private TableDescriptor htd = null; 077 private static int COMPACTION_THRESHOLD; 078 private static byte[] FIRST_ROW_BYTES, SECOND_ROW_BYTES, THIRD_ROW_BYTES; 079 private static byte[] COL1, COL2; 080 081 public static final class MyCompactionPolicy extends RatioBasedCompactionPolicy { 082 083 public MyCompactionPolicy(Configuration conf, StoreConfigInformation storeConfigInfo) { 084 super(conf, storeConfigInfo); 085 } 086 087 @Override 088 public CompactionRequestImpl selectCompaction(Collection<HStoreFile> candidateFiles, 089 List<HStoreFile> filesCompacting, boolean isUserCompaction, boolean mayUseOffPeak, 090 boolean forceMajor) throws IOException { 091 return new CompactionRequestImpl( 092 candidateFiles.stream().filter(f -> !filesCompacting.contains(f)) 093 .limit(COMPACTION_THRESHOLD).collect(Collectors.toList())); 094 } 095 } 096 097 @BeforeClass 098 public static void setUpBeforeClass() { 099 // Set cache flush size to 1MB 100 CONF.setInt(HConstants.HREGION_MEMSTORE_FLUSH_SIZE, 1024 * 1024); 101 CONF.setInt(HConstants.HREGION_MEMSTORE_BLOCK_MULTIPLIER, 100); 102 COMPACTION_THRESHOLD = CONF.getInt("hbase.hstore.compactionThreshold", 3); 103 CONF.setClass(DefaultStoreEngine.DEFAULT_COMPACTION_POLICY_CLASS_KEY, MyCompactionPolicy.class, 104 RatioBasedCompactionPolicy.class); 105 106 FIRST_ROW_BYTES = START_KEY_BYTES; 107 SECOND_ROW_BYTES = START_KEY_BYTES.clone(); 108 // Increment the least significant character so we get to next row. 109 SECOND_ROW_BYTES[START_KEY_BYTES.length - 1]++; 110 THIRD_ROW_BYTES = START_KEY_BYTES.clone(); 111 THIRD_ROW_BYTES[START_KEY_BYTES.length - 1] = 112 (byte) (THIRD_ROW_BYTES[START_KEY_BYTES.length - 1] + 2); 113 COL1 = Bytes.toBytes("column1"); 114 COL2 = Bytes.toBytes("column2"); 115 } 116 117 @Before 118 public void setUp() throws Exception { 119 this.htd = UTIL.createTableDescriptor(TableName.valueOf(name.getMethodName()), 120 ColumnFamilyDescriptorBuilder.DEFAULT_MIN_VERSIONS, 3, HConstants.FOREVER, 121 ColumnFamilyDescriptorBuilder.DEFAULT_KEEP_DELETED); 122 this.r = UTIL.createLocalHRegion(htd, null, null); 123 } 124 125 @After 126 public void tearDown() throws Exception { 127 WAL wal = ((HRegion) r).getWAL(); 128 ((HRegion) r).close(); 129 wal.close(); 130 } 131 132 @Test 133 public void testMinorCompactionWithDeleteRow() throws Exception { 134 Delete deleteRow = new Delete(SECOND_ROW_BYTES); 135 testMinorCompactionWithDelete(deleteRow); 136 } 137 138 @Test 139 public void testMinorCompactionWithDeleteColumn1() throws Exception { 140 Delete dc = new Delete(SECOND_ROW_BYTES); 141 /* delete all timestamps in the column */ 142 dc.addColumns(fam2, COL2); 143 testMinorCompactionWithDelete(dc); 144 } 145 146 @Test 147 public void testMinorCompactionWithDeleteColumn2() throws Exception { 148 Delete dc = new Delete(SECOND_ROW_BYTES); 149 dc.addColumn(fam2, COL2); 150 /* 151 * compactionThreshold is 3. The table has 4 versions: 0, 1, 2, and 3. we only delete the latest 152 * version. One might expect to see only versions 1 and 2. HBase differs, and gives us 0, 1 and 153 * 2. This is okay as well. Since there was no compaction done before the delete, version 0 154 * seems to stay on. 155 */ 156 testMinorCompactionWithDelete(dc, 3); 157 } 158 159 @Test 160 public void testMinorCompactionWithDeleteColumnFamily() throws Exception { 161 Delete deleteCF = new Delete(SECOND_ROW_BYTES); 162 deleteCF.addFamily(fam2); 163 testMinorCompactionWithDelete(deleteCF); 164 } 165 166 @Test 167 public void testMinorCompactionWithDeleteVersion1() throws Exception { 168 Delete deleteVersion = new Delete(SECOND_ROW_BYTES); 169 deleteVersion.addColumns(fam2, COL2, 2); 170 /* 171 * compactionThreshold is 3. The table has 4 versions: 0, 1, 2, and 3. We delete versions 0 ... 172 * 2. So, we still have one remaining. 173 */ 174 testMinorCompactionWithDelete(deleteVersion, 1); 175 } 176 177 @Test 178 public void testMinorCompactionWithDeleteVersion2() throws Exception { 179 Delete deleteVersion = new Delete(SECOND_ROW_BYTES); 180 deleteVersion.addColumn(fam2, COL2, 1); 181 /* 182 * the table has 4 versions: 0, 1, 2, and 3. We delete 1. Should have 3 remaining. 183 */ 184 testMinorCompactionWithDelete(deleteVersion, 3); 185 } 186 187 /* 188 * A helper function to test the minor compaction algorithm. We check that the delete markers are 189 * left behind. Takes delete as an argument, which can be any delete (row, column, columnfamliy 190 * etc), that essentially deletes row2 and column2. row1 and column1 should be undeleted 191 */ 192 private void testMinorCompactionWithDelete(Delete delete) throws Exception { 193 testMinorCompactionWithDelete(delete, 0); 194 } 195 196 private void testMinorCompactionWithDelete(Delete delete, int expectedResultsAfterDelete) 197 throws Exception { 198 Table loader = new RegionAsTable(r); 199 for (int i = 0; i < COMPACTION_THRESHOLD + 1; i++) { 200 HTestConst.addContent(loader, Bytes.toString(fam1), Bytes.toString(COL1), FIRST_ROW_BYTES, 201 THIRD_ROW_BYTES, i); 202 HTestConst.addContent(loader, Bytes.toString(fam1), Bytes.toString(COL2), FIRST_ROW_BYTES, 203 THIRD_ROW_BYTES, i); 204 HTestConst.addContent(loader, Bytes.toString(fam2), Bytes.toString(COL1), FIRST_ROW_BYTES, 205 THIRD_ROW_BYTES, i); 206 HTestConst.addContent(loader, Bytes.toString(fam2), Bytes.toString(COL2), FIRST_ROW_BYTES, 207 THIRD_ROW_BYTES, i); 208 r.flush(true); 209 } 210 211 Result result = r.get(new Get(FIRST_ROW_BYTES).addColumn(fam1, COL1).readVersions(100)); 212 assertEquals(COMPACTION_THRESHOLD, result.size()); 213 result = r.get(new Get(SECOND_ROW_BYTES).addColumn(fam2, COL2).readVersions(100)); 214 assertEquals(COMPACTION_THRESHOLD, result.size()); 215 216 // Now add deletes to memstore and then flush it. That will put us over 217 // the compaction threshold of 3 store files. Compacting these store files 218 // should result in a compacted store file that has no references to the 219 // deleted row. 220 r.delete(delete); 221 222 // Make sure that we have only deleted family2 from secondRowBytes 223 result = r.get(new Get(SECOND_ROW_BYTES).addColumn(fam2, COL2).readVersions(100)); 224 assertEquals(expectedResultsAfterDelete, result.size()); 225 // but we still have firstrow 226 result = r.get(new Get(FIRST_ROW_BYTES).addColumn(fam1, COL1).readVersions(100)); 227 assertEquals(COMPACTION_THRESHOLD, result.size()); 228 229 r.flush(true); 230 // should not change anything. 231 // Let us check again 232 233 // Make sure that we have only deleted family2 from secondRowBytes 234 result = r.get(new Get(SECOND_ROW_BYTES).addColumn(fam2, COL2).readVersions(100)); 235 assertEquals(expectedResultsAfterDelete, result.size()); 236 // but we still have firstrow 237 result = r.get(new Get(FIRST_ROW_BYTES).addColumn(fam1, COL1).readVersions(100)); 238 assertEquals(COMPACTION_THRESHOLD, result.size()); 239 240 // do a compaction 241 HStore store2 = r.getStore(fam2); 242 int numFiles1 = store2.getStorefiles().size(); 243 assertTrue("Was expecting to see 4 store files", numFiles1 > COMPACTION_THRESHOLD); // > 3 244 Optional<CompactionContext> compaction = store2.requestCompaction(); 245 assertTrue(compaction.isPresent()); 246 store2.compact(compaction.get(), NoLimitThroughputController.INSTANCE, null); // = 3 247 int numFiles2 = store2.getStorefiles().size(); 248 // Check that we did compact 249 assertTrue("Number of store files should go down", numFiles1 > numFiles2); 250 // Check that it was a minor compaction. 251 assertTrue("Was not supposed to be a major compaction", numFiles2 > 1); 252 253 // Make sure that we have only deleted family2 from secondRowBytes 254 result = r.get(new Get(SECOND_ROW_BYTES).addColumn(fam2, COL2).readVersions(100)); 255 assertEquals(expectedResultsAfterDelete, result.size()); 256 // but we still have firstrow 257 result = r.get(new Get(FIRST_ROW_BYTES).addColumn(fam1, COL1).readVersions(100)); 258 assertEquals(COMPACTION_THRESHOLD, result.size()); 259 } 260}