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.junit.jupiter.api.Assertions.assertEquals; 021import static org.junit.jupiter.api.Assertions.assertTrue; 022 023import java.io.IOException; 024import java.util.ArrayList; 025import java.util.List; 026import org.apache.hadoop.conf.Configuration; 027import org.apache.hadoop.fs.Path; 028import org.apache.hadoop.hbase.HBaseTestingUtil; 029import org.apache.hadoop.hbase.TableName; 030import org.apache.hadoop.hbase.client.Put; 031import org.apache.hadoop.hbase.client.RegionInfo; 032import org.apache.hadoop.hbase.client.RegionInfoBuilder; 033import org.apache.hadoop.hbase.client.Table; 034import org.apache.hadoop.hbase.master.assignment.SplitTableRegionProcedure; 035import org.apache.hadoop.hbase.master.procedure.MasterProcedureEnv; 036import org.apache.hadoop.hbase.procedure2.Procedure; 037import org.apache.hadoop.hbase.regionserver.storefiletracker.StoreFileTracker; 038import org.apache.hadoop.hbase.regionserver.storefiletracker.StoreFileTrackerFactory; 039import org.apache.hadoop.hbase.testclassification.LargeTests; 040import org.apache.hadoop.hbase.testclassification.RegionServerTests; 041import org.apache.hadoop.hbase.util.Bytes; 042import org.apache.hadoop.hbase.util.EnvironmentEdgeManager; 043import org.junit.jupiter.api.AfterAll; 044import org.junit.jupiter.api.BeforeAll; 045import org.junit.jupiter.api.BeforeEach; 046import org.junit.jupiter.api.Tag; 047import org.junit.jupiter.api.Test; 048import org.junit.jupiter.api.TestInfo; 049 050@Tag(RegionServerTests.TAG) 051@Tag(LargeTests.TAG) 052public class TestDirectStoreSplitsMerges { 053 054 private static HBaseTestingUtil TEST_UTIL = new HBaseTestingUtil(); 055 056 public static final byte[] FAMILY_NAME = Bytes.toBytes("info"); 057 private String name; 058 059 @BeforeEach 060 public void setTestName(TestInfo testInfo) { 061 this.name = testInfo.getTestMethod().get().getName(); 062 } 063 064 @BeforeAll 065 public static void setup() throws Exception { 066 TEST_UTIL.startMiniCluster(); 067 } 068 069 @AfterAll 070 public static void after() throws Exception { 071 TEST_UTIL.shutdownMiniCluster(); 072 } 073 074 @Test 075 public void testSplitStoreDir() throws Exception { 076 TableName table = TableName.valueOf(name); 077 TEST_UTIL.createTable(table, FAMILY_NAME); 078 // first put some data in order to have a store file created 079 putThreeRowsAndFlush(table); 080 HRegion region = TEST_UTIL.getHBaseCluster().getRegions(table).get(0); 081 HRegionFileSystem regionFS = region.getStores().get(0).getRegionFileSystem(); 082 RegionInfo daughterA = 083 RegionInfoBuilder.newBuilder(table).setStartKey(region.getRegionInfo().getStartKey()) 084 .setEndKey(Bytes.toBytes("002")).setSplit(false) 085 .setRegionId(region.getRegionInfo().getRegionId() + EnvironmentEdgeManager.currentTime()) 086 .build(); 087 HStoreFile file = (HStoreFile) region.getStore(FAMILY_NAME).getStorefiles().toArray()[0]; 088 StoreFileTracker sft = 089 StoreFileTrackerFactory.create(TEST_UTIL.getHBaseCluster().getMaster().getConfiguration(), 090 true, region.getStores().get(0).getStoreContext()); 091 Path result = regionFS.splitStoreFile(daughterA, Bytes.toString(FAMILY_NAME), file, 092 Bytes.toBytes("002"), false, region.getSplitPolicy(), sft).getPath(); 093 // asserts the reference file naming is correct 094 validateResultingFile(region.getRegionInfo().getEncodedName(), result); 095 // Additionally check if split region dir was created directly under table dir, not on .tmp 096 Path resultGreatGrandParent = result.getParent().getParent().getParent(); 097 assertEquals(regionFS.getTableDir().getName(), resultGreatGrandParent.getName()); 098 } 099 100 @Test 101 public void testMergeStoreFile() throws Exception { 102 TableName table = TableName.valueOf(name); 103 TEST_UTIL.createTable(table, FAMILY_NAME); 104 // splitting the table first 105 TEST_UTIL.getAdmin().split(table, Bytes.toBytes("002")); 106 waitForSplitProcComplete(1000, 10); 107 // Add data and flush to create files in the two different regions 108 putThreeRowsAndFlush(table); 109 List<HRegion> regions = TEST_UTIL.getHBaseCluster().getRegions(table); 110 HRegion first = regions.get(0); 111 HRegion second = regions.get(1); 112 HRegionFileSystem regionFS = first.getRegionFileSystem(); 113 114 RegionInfo mergeResult = 115 RegionInfoBuilder.newBuilder(table).setStartKey(first.getRegionInfo().getStartKey()) 116 .setEndKey(second.getRegionInfo().getEndKey()).setSplit(false) 117 .setRegionId(first.getRegionInfo().getRegionId() + EnvironmentEdgeManager.currentTime()) 118 .build(); 119 120 Configuration configuration = TEST_UTIL.getHBaseCluster().getMaster().getConfiguration(); 121 HRegionFileSystem mergeRegionFs = HRegionFileSystem.createRegionOnFileSystem(configuration, 122 regionFS.getFileSystem(), regionFS.getTableDir(), mergeResult); 123 124 // merge file from first region 125 HStoreFile file = (HStoreFile) first.getStore(FAMILY_NAME).getStorefiles().toArray()[0]; 126 mergeFileFromRegion(mergeRegionFs, first, file, StoreFileTrackerFactory.create(configuration, 127 true, first.getStore(FAMILY_NAME).getStoreContext())); 128 // merge file from second region 129 file = (HStoreFile) second.getStore(FAMILY_NAME).getStorefiles().toArray()[0]; 130 mergeFileFromRegion(mergeRegionFs, second, file, StoreFileTrackerFactory.create(configuration, 131 true, second.getStore(FAMILY_NAME).getStoreContext())); 132 } 133 134 @Test 135 public void testCommitDaughterRegionNoFiles() throws Exception { 136 TableName table = TableName.valueOf(name); 137 TEST_UTIL.createTable(table, FAMILY_NAME); 138 HRegion region = TEST_UTIL.getHBaseCluster().getRegions(table).get(0); 139 HRegionFileSystem regionFS = region.getStores().get(0).getRegionFileSystem(); 140 RegionInfo daughterA = 141 RegionInfoBuilder.newBuilder(table).setStartKey(region.getRegionInfo().getStartKey()) 142 .setEndKey(Bytes.toBytes("002")).setSplit(false) 143 .setRegionId(region.getRegionInfo().getRegionId() + EnvironmentEdgeManager.currentTime()) 144 .build(); 145 Path splitDir = regionFS.getSplitsDir(daughterA); 146 MasterProcedureEnv env = 147 TEST_UTIL.getMiniHBaseCluster().getMaster().getMasterProcedureExecutor().getEnvironment(); 148 Path result = regionFS.commitDaughterRegion(daughterA, new ArrayList<>(), env); 149 assertEquals(splitDir, result); 150 } 151 152 @Test 153 public void testCommitDaughterRegionWithFiles() throws Exception { 154 TableName table = TableName.valueOf(name); 155 TEST_UTIL.createTable(table, FAMILY_NAME); 156 // first put some data in order to have a store file created 157 putThreeRowsAndFlush(table); 158 HRegion region = TEST_UTIL.getHBaseCluster().getRegions(table).get(0); 159 HRegionFileSystem regionFS = region.getStores().get(0).getRegionFileSystem(); 160 RegionInfo daughterA = 161 RegionInfoBuilder.newBuilder(table).setStartKey(region.getRegionInfo().getStartKey()) 162 .setEndKey(Bytes.toBytes("002")).setSplit(false) 163 .setRegionId(region.getRegionInfo().getRegionId() + EnvironmentEdgeManager.currentTime()) 164 .build(); 165 RegionInfo daughterB = RegionInfoBuilder.newBuilder(table).setStartKey(Bytes.toBytes("002")) 166 .setEndKey(region.getRegionInfo().getEndKey()).setSplit(false) 167 .setRegionId(region.getRegionInfo().getRegionId()).build(); 168 Path splitDirA = regionFS.getSplitsDir(daughterA); 169 Path splitDirB = regionFS.getSplitsDir(daughterB); 170 HStoreFile file = (HStoreFile) region.getStore(FAMILY_NAME).getStorefiles().toArray()[0]; 171 List<StoreFileInfo> filesA = new ArrayList<>(); 172 StoreFileTracker sft = 173 StoreFileTrackerFactory.create(TEST_UTIL.getHBaseCluster().getMaster().getConfiguration(), 174 true, region.getStores().get(0).getStoreContext()); 175 filesA.add(regionFS.splitStoreFile(daughterA, Bytes.toString(FAMILY_NAME), file, 176 Bytes.toBytes("002"), false, region.getSplitPolicy(), sft)); 177 List<StoreFileInfo> filesB = new ArrayList<>(); 178 filesB.add(regionFS.splitStoreFile(daughterB, Bytes.toString(FAMILY_NAME), file, 179 Bytes.toBytes("002"), true, region.getSplitPolicy(), sft)); 180 MasterProcedureEnv env = 181 TEST_UTIL.getMiniHBaseCluster().getMaster().getMasterProcedureExecutor().getEnvironment(); 182 Path resultA = regionFS.commitDaughterRegion(daughterA, filesA, env); 183 Path resultB = regionFS.commitDaughterRegion(daughterB, filesB, env); 184 assertEquals(splitDirA, resultA); 185 assertEquals(splitDirB, resultB); 186 } 187 188 @Test 189 public void testCommitMergedRegion() throws Exception { 190 TableName table = TableName.valueOf(name); 191 TEST_UTIL.createTable(table, FAMILY_NAME); 192 // splitting the table first 193 TEST_UTIL.getAdmin().split(table, Bytes.toBytes("002")); 194 waitForSplitProcComplete(1000, 10); 195 // Add data and flush to create files in the two different regions 196 putThreeRowsAndFlush(table); 197 List<HRegion> regions = TEST_UTIL.getHBaseCluster().getRegions(table); 198 HRegion first = regions.get(0); 199 HRegion second = regions.get(1); 200 HRegionFileSystem regionFS = first.getRegionFileSystem(); 201 202 RegionInfo mergeResult = 203 RegionInfoBuilder.newBuilder(table).setStartKey(first.getRegionInfo().getStartKey()) 204 .setEndKey(second.getRegionInfo().getEndKey()).setSplit(false) 205 .setRegionId(first.getRegionInfo().getRegionId() + EnvironmentEdgeManager.currentTime()) 206 .build(); 207 208 Configuration configuration = TEST_UTIL.getHBaseCluster().getMaster().getConfiguration(); 209 HRegionFileSystem mergeRegionFs = HRegionFileSystem.createRegionOnFileSystem(configuration, 210 regionFS.getFileSystem(), regionFS.getTableDir(), mergeResult); 211 212 // merge file from first region 213 HStoreFile file = (HStoreFile) first.getStore(FAMILY_NAME).getStorefiles().toArray()[0]; 214 mergeFileFromRegion(mergeRegionFs, first, file, StoreFileTrackerFactory.create(configuration, 215 true, first.getStore(FAMILY_NAME).getStoreContext())); 216 // merge file from second region 217 file = (HStoreFile) second.getStore(FAMILY_NAME).getStorefiles().toArray()[0]; 218 List<StoreFileInfo> mergedFiles = new ArrayList<>(); 219 mergedFiles.add(mergeFileFromRegion(mergeRegionFs, second, file, StoreFileTrackerFactory 220 .create(configuration, true, second.getStore(FAMILY_NAME).getStoreContext()))); 221 MasterProcedureEnv env = 222 TEST_UTIL.getMiniHBaseCluster().getMaster().getMasterProcedureExecutor().getEnvironment(); 223 mergeRegionFs.commitMergedRegion(mergedFiles, env); 224 } 225 226 private void waitForSplitProcComplete(int attempts, int waitTime) throws Exception { 227 List<Procedure<?>> procedures = TEST_UTIL.getHBaseCluster().getMaster().getProcedures(); 228 if (procedures.size() > 0) { 229 Procedure splitProc = 230 procedures.stream().filter(p -> p instanceof SplitTableRegionProcedure).findFirst().get(); 231 int count = 0; 232 while ((splitProc.isWaiting() || splitProc.isRunnable()) && count < attempts) { 233 synchronized (splitProc) { 234 splitProc.wait(waitTime); 235 } 236 count++; 237 } 238 assertTrue(splitProc.isSuccess()); 239 } 240 } 241 242 private StoreFileInfo mergeFileFromRegion(HRegionFileSystem regionFS, HRegion regionToMerge, 243 HStoreFile file, StoreFileTracker sft) throws IOException { 244 StoreFileInfo mergedFile = regionFS.mergeStoreFile(regionToMerge.getRegionInfo(), 245 Bytes.toString(FAMILY_NAME), file, sft); 246 validateResultingFile(regionToMerge.getRegionInfo().getEncodedName(), mergedFile.getPath()); 247 return mergedFile; 248 } 249 250 private void validateResultingFile(String originalRegion, Path result) { 251 assertEquals(originalRegion, result.getName().split("\\.")[1]); 252 // asserts we are under the cf directory 253 Path resultParent = result.getParent(); 254 assertEquals(Bytes.toString(FAMILY_NAME), resultParent.getName()); 255 } 256 257 private void putThreeRowsAndFlush(TableName table) throws IOException { 258 Table tbl = TEST_UTIL.getConnection().getTable(table); 259 Put put = new Put(Bytes.toBytes("001")); 260 byte[] qualifier = Bytes.toBytes("1"); 261 put.addColumn(FAMILY_NAME, qualifier, Bytes.toBytes(1)); 262 tbl.put(put); 263 put = new Put(Bytes.toBytes("002")); 264 put.addColumn(FAMILY_NAME, qualifier, Bytes.toBytes(2)); 265 tbl.put(put); 266 put = new Put(Bytes.toBytes("003")); 267 put.addColumn(FAMILY_NAME, qualifier, Bytes.toBytes(2)); 268 tbl.put(put); 269 TEST_UTIL.flush(table); 270 } 271}