/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hadoop.hbase.master.procedure;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hbase.DoNotRetryIOException;
import org.apache.hadoop.hbase.HTableDescriptor;
import org.apache.hadoop.hbase.HColumnDescriptor;
import org.apache.hadoop.hbase.HConstants;
import org.apache.hadoop.hbase.HRegionInfo;
import org.apache.hadoop.hbase.ServerName;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.classification.InterfaceAudience;
import org.apache.hadoop.hbase.client.Mutation;
import org.apache.hadoop.hbase.io.hfile.CacheConfig;
import org.apache.hadoop.hbase.master.MasterCoprocessorHost;
import org.apache.hadoop.hbase.master.MasterFileSystem;
import org.apache.hadoop.hbase.master.RegionState;
import org.apache.hadoop.hbase.master.RegionStates;
import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProcedureProtos;
import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProcedureProtos.SplitTableRegionState;
import org.apache.hadoop.hbase.shaded.protobuf.generated.RegionServerStatusProtos.RegionStateTransition;
import org.apache.hadoop.hbase.shaded.protobuf.generated.RegionServerStatusProtos.RegionStateTransition.TransitionCode;
import org.apache.hadoop.hbase.regionserver.HRegionFileSystem;
import org.apache.hadoop.hbase.regionserver.HStore;
import org.apache.hadoop.hbase.regionserver.StoreFile;
import org.apache.hadoop.hbase.regionserver.StoreFileInfo;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
import org.apache.hadoop.hbase.util.FSUtils;
import org.apache.hadoop.hbase.util.Pair;
import org.apache.hadoop.hbase.util.Threads;
import com.google.common.annotations.VisibleForTesting;
/**
* The procedure to split a region in a table.
*/
@InterfaceAudience.Private
public class SplitTableRegionProcedure
extends AbstractStateMachineTableProcedure<SplitTableRegionState> {
private static final Log LOG = LogFactory.getLog(SplitTableRegionProcedure.class);
private Boolean traceEnabled;
/*
* Region to split
*/
private HRegionInfo parentHRI;
private HRegionInfo daughter_1_HRI;
private HRegionInfo daughter_2_HRI;
public SplitTableRegionProcedure() {
this.traceEnabled = null;
}
public SplitTableRegionProcedure(final MasterProcedureEnv env,
final HRegionInfo regionToSplit, final byte[] splitRow) throws IOException {
super(env);
checkSplitRow(regionToSplit, splitRow);
this.traceEnabled = null;
this.parentHRI = regionToSplit;
final TableName table = regionToSplit.getTable();
final long rid = getDaughterRegionIdTimestamp(regionToSplit);
this.daughter_1_HRI = new HRegionInfo(table, regionToSplit.getStartKey(), splitRow, false, rid);
this.daughter_2_HRI = new HRegionInfo(table, splitRow, regionToSplit.getEndKey(), false, rid);
}
private static void checkSplitRow(final HRegionInfo regionToSplit, final byte[] splitRow)
throws IOException {
if (splitRow == null || splitRow.length == 0) {
throw new DoNotRetryIOException("Split row cannot be null");
}
if (Bytes.equals(regionToSplit.getStartKey(), splitRow)) {
throw new DoNotRetryIOException(
"Split row is equal to startkey: " + Bytes.toStringBinary(splitRow));
}
if (!regionToSplit.containsRow(splitRow)) {
throw new DoNotRetryIOException(
"Split row is not inside region key range splitKey:" + Bytes.toStringBinary(splitRow) +
" region: " + regionToSplit);
}
}
/**
* Calculate daughter regionid to use.
* @param hri Parent {@link HRegionInfo}
* @return Daughter region id (timestamp) to use.
*/
private static long getDaughterRegionIdTimestamp(final HRegionInfo hri) {
long rid = EnvironmentEdgeManager.currentTime();
// Regionid is timestamp. Can't be less than that of parent else will insert
// at wrong location in hbase:meta (See HBASE-710).
if (rid < hri.getRegionId()) {
LOG.warn("Clock skew; parent regions id is " + hri.getRegionId() +
" but current time here is " + rid);
rid = hri.getRegionId() + 1;
}
return rid;
}
@Override
protected Flow executeFromState(final MasterProcedureEnv env, final SplitTableRegionState state)
throws InterruptedException {
if (isTraceEnabled()) {
LOG.trace(this + " execute state=" + state);
}
try {
switch (state) {
case SPLIT_TABLE_REGION_PREPARE:
if (prepareSplitRegion(env)) {
setNextState(SplitTableRegionState.SPLIT_TABLE_REGION_PRE_OPERATION);
break;
} else {
assert isFailed() : "split region should have an exception here";
return Flow.NO_MORE_STATE;
}
case SPLIT_TABLE_REGION_PRE_OPERATION:
preSplitRegion(env);
setNextState(SplitTableRegionState.SPLIT_TABLE_REGION_SET_SPLITTING_TABLE_STATE);
break;
case SPLIT_TABLE_REGION_SET_SPLITTING_TABLE_STATE:
setRegionStateToSplitting(env);
setNextState(SplitTableRegionState.SPLIT_TABLE_REGION_CLOSE_PARENT_REGION);
break;
case SPLIT_TABLE_REGION_CLOSE_PARENT_REGION:
closeParentRegionForSplit(env);
setNextState(SplitTableRegionState.SPLIT_TABLE_REGION_CREATE_DAUGHTER_REGIONS);
break;
case SPLIT_TABLE_REGION_CREATE_DAUGHTER_REGIONS:
createDaughterRegions(env);
setNextState(SplitTableRegionState.SPLIT_TABLE_REGION_PRE_OPERATION_BEFORE_PONR);
break;
case SPLIT_TABLE_REGION_PRE_OPERATION_BEFORE_PONR:
preSplitRegionBeforePONR(env);
setNextState(SplitTableRegionState.SPLIT_TABLE_REGION_UPDATE_META);
break;
case SPLIT_TABLE_REGION_UPDATE_META:
// This is the point of no return. Adding subsequent edits to .META. as we
// do below when we do the daughter opens adding each to .META. can fail in
// various interesting ways the most interesting of which is a timeout
// BUT the edits all go through (See HBASE-3872). IF we reach the PONR
// then subsequent failures need to crash out this region server; the
// server shutdown processing should be able to fix-up the incomplete split.
// The offlined parent will have the daughters as extra columns. If
// we leave the daughter regions in place and do not remove them when we
// crash out, then they will have their references to the parent in place
// still and the server shutdown fixup of .META. will point to these
// regions.
// We should add PONR JournalEntry before offlineParentInMeta,so even if
// OfflineParentInMeta timeout,this will cause regionserver exit,and then
// master ServerShutdownHandler will fix daughter & avoid data loss. (See
// HBase-4562).
updateMetaForDaughterRegions(env);
setNextState(SplitTableRegionState.SPLIT_TABLE_REGION_PRE_OPERATION_AFTER_PONR);
break;
case SPLIT_TABLE_REGION_PRE_OPERATION_AFTER_PONR:
preSplitRegionAfterPONR(env);
setNextState(SplitTableRegionState.SPLIT_TABLE_REGION_OPEN_CHILD_REGIONS);
break;
case SPLIT_TABLE_REGION_OPEN_CHILD_REGIONS:
openDaughterRegions(env);
setNextState(SplitTableRegionState.SPLIT_TABLE_REGION_POST_OPERATION);
break;
case SPLIT_TABLE_REGION_POST_OPERATION:
postSplitRegion(env);
return Flow.NO_MORE_STATE;
default:
throw new UnsupportedOperationException(this + " unhandled state=" + state);
}
} catch (IOException e) {
String msg = "Error trying to split region " + parentHRI.getEncodedName() + " in the table "
+ getTableName() + " (in state=" + state + ")";
if (!isRollbackSupported(state)) {
// We reach a state that cannot be rolled back. We just need to keep retry.
LOG.warn(msg, e);
} else {
LOG.error(msg, e);
setFailure("master-split-region", e);
}
}
return Flow.HAS_MORE_STATE;
}
@Override
protected void rollbackState(final MasterProcedureEnv env, final SplitTableRegionState state)
throws IOException, InterruptedException {
if (isTraceEnabled()) {
LOG.trace(this + " rollback state=" + state);
}
try {
switch (state) {
case SPLIT_TABLE_REGION_POST_OPERATION:
case SPLIT_TABLE_REGION_OPEN_CHILD_REGIONS:
case SPLIT_TABLE_REGION_PRE_OPERATION_AFTER_PONR:
case SPLIT_TABLE_REGION_UPDATE_META:
// PONR
throw new UnsupportedOperationException(this + " unhandled state=" + state);
case SPLIT_TABLE_REGION_PRE_OPERATION_BEFORE_PONR:
break;
case SPLIT_TABLE_REGION_CREATE_DAUGHTER_REGIONS:
// Doing nothing, as re-open parent region would clean up daughter region directories.
break;
case SPLIT_TABLE_REGION_CLOSE_PARENT_REGION:
openParentRegion(env);
break;
case SPLIT_TABLE_REGION_SET_SPLITTING_TABLE_STATE:
setRegionStateToRevertSplitting(env);
break;
case SPLIT_TABLE_REGION_PRE_OPERATION:
postRollBackSplitRegion(env);
break;
case SPLIT_TABLE_REGION_PREPARE:
break; // nothing to do
default:
throw new UnsupportedOperationException(this + " unhandled state=" + state);
}
} catch (IOException e) {
// This will be retried. Unless there is a bug in the code,
// this should be just a "temporary error" (e.g. network down)
LOG.warn("Failed rollback attempt step " + state + " for splitting the region "
+ parentHRI.getEncodedName() + " in table " + getTableName(), e);
throw e;
}
}
/*
* Check whether we are in the state that can be rollback
*/
@Override
protected boolean isRollbackSupported(final SplitTableRegionState state) {
switch (state) {
case SPLIT_TABLE_REGION_POST_OPERATION:
case SPLIT_TABLE_REGION_OPEN_CHILD_REGIONS:
case SPLIT_TABLE_REGION_PRE_OPERATION_AFTER_PONR:
case SPLIT_TABLE_REGION_UPDATE_META:
// It is not safe to rollback if we reach to these states.
return false;
default:
break;
}
return true;
}
@Override
protected SplitTableRegionState getState(final int stateId) {
return SplitTableRegionState.forNumber(stateId);
}
@Override
protected int getStateId(final SplitTableRegionState state) {
return state.getNumber();
}
@Override
protected SplitTableRegionState getInitialState() {
return SplitTableRegionState.SPLIT_TABLE_REGION_PREPARE;
}
@Override
public void serializeStateData(final OutputStream stream) throws IOException {
super.serializeStateData(stream);
final MasterProcedureProtos.SplitTableRegionStateData.Builder splitTableRegionMsg =
MasterProcedureProtos.SplitTableRegionStateData.newBuilder()
.setUserInfo(MasterProcedureUtil.toProtoUserInfo(getUser()))
.setParentRegionInfo(HRegionInfo.convert(parentHRI))
.addChildRegionInfo(HRegionInfo.convert(daughter_1_HRI))
.addChildRegionInfo(HRegionInfo.convert(daughter_2_HRI));
splitTableRegionMsg.build().writeDelimitedTo(stream);
}
@Override
public void deserializeStateData(final InputStream stream) throws IOException {
super.deserializeStateData(stream);
final MasterProcedureProtos.SplitTableRegionStateData splitTableRegionsMsg =
MasterProcedureProtos.SplitTableRegionStateData.parseDelimitedFrom(stream);
setUser(MasterProcedureUtil.toUserInfo(splitTableRegionsMsg.getUserInfo()));
parentHRI = HRegionInfo.convert(splitTableRegionsMsg.getParentRegionInfo());
if (splitTableRegionsMsg.getChildRegionInfoCount() == 0) {
daughter_1_HRI = daughter_2_HRI = null;
} else {
assert(splitTableRegionsMsg.getChildRegionInfoCount() == 2);
daughter_1_HRI = HRegionInfo.convert(splitTableRegionsMsg.getChildRegionInfoList().get(0));
daughter_2_HRI = HRegionInfo.convert(splitTableRegionsMsg.getChildRegionInfoList().get(1));
}
}
@Override
public void toStringClassDetails(StringBuilder sb) {
sb.append(getClass().getSimpleName());
sb.append(" (table=");
sb.append(getTableName());
sb.append(" parent region=");
sb.append(parentHRI);
if (daughter_1_HRI != null) {
sb.append(" first daughter region=");
sb.append(daughter_1_HRI);
}
if (daughter_2_HRI != null) {
sb.append(" and second daughter region=");
sb.append(daughter_2_HRI);
}
sb.append(")");
}
@Override
protected LockState acquireLock(final MasterProcedureEnv env) {
if (env.waitInitialized(this)) {
return LockState.LOCK_EVENT_WAIT;
}
return env.getProcedureScheduler().waitRegions(this, getTableName(), parentHRI)?
LockState.LOCK_EVENT_WAIT: LockState.LOCK_ACQUIRED;
}
@Override
protected void releaseLock(final MasterProcedureEnv env) {
env.getProcedureScheduler().wakeRegions(this, getTableName(), parentHRI);
}
@Override
public TableName getTableName() {
return parentHRI.getTable();
}
@Override
public TableOperationType getTableOperationType() {
return TableOperationType.SPLIT;
}
private byte[] getSplitRow() {
return daughter_2_HRI.getStartKey();
}
/**
* Prepare to Split region.
* @param env MasterProcedureEnv
* @throws IOException
*/
@VisibleForTesting
public boolean prepareSplitRegion(final MasterProcedureEnv env) throws IOException {
// Check whether the region is splittable
final RegionState state = getParentRegionState(env);
if (state.isClosing() || state.isClosed() ||
state.isSplittingOrSplitOnServer(state.getServerName())) {
setFailure(
"master-split-region",
new IOException("Split region " + parentHRI + " failed due to region is not splittable"));
return false;
}
return true;
}
/**
* Action before splitting region in a table.
* @param env MasterProcedureEnv
* @param state the procedure state
* @throws IOException
* @throws InterruptedException
*/
private void preSplitRegion(final MasterProcedureEnv env)
throws IOException, InterruptedException {
final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost();
if (cpHost != null) {
cpHost.preSplitRegionAction(getTableName(), getSplitRow(), getUser());
}
}
/**
* Action after rollback a split table region action.
* @param env MasterProcedureEnv
* @throws IOException
*/
private void postRollBackSplitRegion(final MasterProcedureEnv env) throws IOException {
final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost();
if (cpHost != null) {
cpHost.postRollBackSplitRegionAction(getUser());
}
}
/**
* Set the parent region state to SPLITTING state
* @param env MasterProcedureEnv
* @throws IOException
*/
@VisibleForTesting
public void setRegionStateToSplitting(final MasterProcedureEnv env) throws IOException {
RegionStateTransition.Builder transition = RegionStateTransition.newBuilder();
transition.setTransitionCode(TransitionCode.READY_TO_SPLIT);
transition.addRegionInfo(HRegionInfo.convert(parentHRI));
transition.addRegionInfo(HRegionInfo.convert(daughter_1_HRI));
transition.addRegionInfo(HRegionInfo.convert(daughter_2_HRI));
if (env.getMasterServices().getAssignmentManager().onRegionTransition(
getParentRegionState(env).getServerName(), transition.build()) != null) {
throw new IOException("Failed to update region state to SPLITTING for "
+ parentHRI.getRegionNameAsString());
}
}
/**
* Rollback the region state change
* @param env MasterProcedureEnv
* @throws IOException
*/
private void setRegionStateToRevertSplitting(final MasterProcedureEnv env) throws IOException {
RegionStateTransition.Builder transition = RegionStateTransition.newBuilder();
transition.setTransitionCode(TransitionCode.SPLIT_REVERTED);
transition.addRegionInfo(HRegionInfo.convert(parentHRI));
transition.addRegionInfo(HRegionInfo.convert(daughter_1_HRI));
transition.addRegionInfo(HRegionInfo.convert(daughter_2_HRI));
if (env.getMasterServices().getAssignmentManager().onRegionTransition(
getParentRegionState(env).getServerName(), transition.build()) != null) {
throw new IOException("Failed to update region state for "
+ parentHRI.getRegionNameAsString() + " as part of operation for reverting split");
}
}
/**
* RPC to region server that host the parent region, ask for close the parent regions
* @param env MasterProcedureEnv
* @throws IOException
*/
@VisibleForTesting
public void closeParentRegionForSplit(final MasterProcedureEnv env) throws IOException {
boolean success = env.getMasterServices().getServerManager().sendRegionCloseForSplitOrMerge(
getParentRegionState(env).getServerName(), parentHRI);
if (!success) {
throw new IOException("Close parent region " + parentHRI + " for splitting failed."
+ " Check region server log for more details");
}
}
/**
* Rollback close parent region
* @param env MasterProcedureEnv
**/
private void openParentRegion(final MasterProcedureEnv env) throws IOException {
// Check whether the region is closed; if so, open it in the same server
RegionState state = getParentRegionState(env);
if (state.isClosing() || state.isClosed()) {
env.getMasterServices().getServerManager().sendRegionOpen(
getParentRegionState(env).getServerName(),
parentHRI,
ServerName.EMPTY_SERVER_LIST);
}
}
/**
* Create daughter regions
* @param env MasterProcedureEnv
* @throws IOException
*/
@VisibleForTesting
public void createDaughterRegions(final MasterProcedureEnv env) throws IOException {
final MasterFileSystem mfs = env.getMasterServices().getMasterFileSystem();
final Path tabledir = FSUtils.getTableDir(mfs.getRootDir(), parentHRI.getTable());
final FileSystem fs = mfs.getFileSystem();
HRegionFileSystem regionFs = HRegionFileSystem.openRegionFromFileSystem(
env.getMasterConfiguration(), fs, tabledir, parentHRI, false);
regionFs.createSplitsDir();
Pair<Integer, Integer> expectedReferences = splitStoreFiles(env, regionFs);
assertReferenceFileCount(
fs, expectedReferences.getFirst(), regionFs.getSplitsDir(daughter_1_HRI));
//Move the files from the temporary .splits to the final /table/region directory
regionFs.commitDaughterRegion(daughter_1_HRI);
assertReferenceFileCount(
fs,
expectedReferences.getFirst(),
new Path(tabledir, daughter_1_HRI.getEncodedName()));
assertReferenceFileCount(
fs, expectedReferences.getSecond(), regionFs.getSplitsDir(daughter_2_HRI));
regionFs.commitDaughterRegion(daughter_2_HRI);
assertReferenceFileCount(
fs,
expectedReferences.getSecond(),
new Path(tabledir, daughter_2_HRI.getEncodedName()));
}
/**
* Create Split directory
* @param env MasterProcedureEnv
* @throws IOException
*/
private Pair<Integer, Integer> splitStoreFiles(final MasterProcedureEnv env,
final HRegionFileSystem regionFs) throws IOException {
final MasterFileSystem mfs = env.getMasterServices().getMasterFileSystem();
final Configuration conf = env.getMasterConfiguration();
// The following code sets up a thread pool executor with as many slots as
// there's files to split. It then fires up everything, waits for
// completion and finally checks for any exception
//
// Note: splitStoreFiles creates daughter region dirs under the parent splits dir
// Nothing to unroll here if failure -- re-run createSplitsDir will
// clean this up.
int nbFiles = 0;
for (String family: regionFs.getFamilies()) {
Collection<StoreFileInfo> storeFiles = regionFs.getStoreFiles(family);
if (storeFiles != null) {
nbFiles += storeFiles.size();
}
}
if (nbFiles == 0) {
// no file needs to be splitted.
return new Pair<>(0,0);
}
// Default max #threads to use is the smaller of table's configured number of blocking store
// files or the available number of logical cores.
int defMaxThreads = Math.min(
conf.getInt(HStore.BLOCKING_STOREFILES_KEY, HStore.DEFAULT_BLOCKING_STOREFILE_COUNT),
Runtime.getRuntime().availableProcessors());
// Max #threads is the smaller of the number of storefiles or the default max determined above.
int maxThreads = Math.min(
conf.getInt(HConstants.REGION_SPLIT_THREADS_MAX, defMaxThreads), nbFiles);
LOG.info("Preparing to split " + nbFiles + " storefiles for region " + parentHRI +
" using " + maxThreads + " threads");
ThreadPoolExecutor threadPool = (ThreadPoolExecutor) Executors.newFixedThreadPool(
maxThreads, Threads.getNamedThreadFactory("StoreFileSplitter-%1$d"));
List<Future<Pair<Path,Path>>> futures = new ArrayList<>(nbFiles);
// Split each store file.
final HTableDescriptor htd = env.getMasterServices().getTableDescriptors().get(getTableName());
for (String family: regionFs.getFamilies()) {
final HColumnDescriptor hcd = htd.getFamily(family.getBytes());
final Collection<StoreFileInfo> storeFiles = regionFs.getStoreFiles(family);
if (storeFiles != null && storeFiles.size() > 0) {
final CacheConfig cacheConf = new CacheConfig(conf, hcd);
for (StoreFileInfo storeFileInfo: storeFiles) {
StoreFileSplitter sfs =
new StoreFileSplitter(regionFs, family.getBytes(), new StoreFile(mfs.getFileSystem(),
storeFileInfo, conf, cacheConf, hcd.getBloomFilterType(), true));
futures.add(threadPool.submit(sfs));
}
}
}
// Shutdown the pool
threadPool.shutdown();
// Wait for all the tasks to finish
long fileSplitTimeout = conf.getLong("hbase.master.fileSplitTimeout", 30000);
try {
boolean stillRunning = !threadPool.awaitTermination(fileSplitTimeout, TimeUnit.MILLISECONDS);
if (stillRunning) {
threadPool.shutdownNow();
// wait for the thread to shutdown completely.
while (!threadPool.isTerminated()) {
Thread.sleep(50);
}
throw new IOException("Took too long to split the" +
" files and create the references, aborting split");
}
} catch (InterruptedException e) {
throw (InterruptedIOException)new InterruptedIOException().initCause(e);
}
int daughterA = 0;
int daughterB = 0;
// Look for any exception
for (Future<Pair<Path, Path>> future : futures) {
try {
Pair<Path, Path> p = future.get();
daughterA += p.getFirst() != null ? 1 : 0;
daughterB += p.getSecond() != null ? 1 : 0;
} catch (InterruptedException e) {
throw (InterruptedIOException) new InterruptedIOException().initCause(e);
} catch (ExecutionException e) {
throw new IOException(e);
}
}
if (LOG.isDebugEnabled()) {
LOG.debug("Split storefiles for region " + parentHRI + " Daughter A: " + daughterA
+ " storefiles, Daughter B: " + daughterB + " storefiles.");
}
return new Pair<>(daughterA, daughterB);
}
private void assertReferenceFileCount(
final FileSystem fs,
final int expectedReferenceFileCount,
final Path dir)
throws IOException {
if (expectedReferenceFileCount != 0 &&
expectedReferenceFileCount != FSUtils.getRegionReferenceFileCount(fs, dir)) {
throw new IOException("Failing split. Expected reference file count isn't equal.");
}
}
private Pair<Path, Path> splitStoreFile(final HRegionFileSystem regionFs,
final byte[] family, final StoreFile sf) throws IOException {
if (LOG.isDebugEnabled()) {
LOG.debug("Splitting started for store file: " + sf.getPath() + " for region: " + parentHRI);
}
final byte[] splitRow = getSplitRow();
final String familyName = Bytes.toString(family);
final Path path_first =
regionFs.splitStoreFile(this.daughter_1_HRI, familyName, sf, splitRow, false, null);
final Path path_second =
regionFs.splitStoreFile(this.daughter_2_HRI, familyName, sf, splitRow, true, null);
if (LOG.isDebugEnabled()) {
LOG.debug("Splitting complete for store file: " + sf.getPath() + " for region: " + parentHRI);
}
return new Pair<>(path_first, path_second);
}
/**
* Utility class used to do the file splitting / reference writing
* in parallel instead of sequentially.
*/
private class StoreFileSplitter implements Callable<Pair<Path,Path>> {
private final HRegionFileSystem regionFs;
private final byte[] family;
private final StoreFile sf;
/**
* Constructor that takes what it needs to split
* @param regionFs the file system
* @param family Family that contains the store file
* @param sf which file
*/
public StoreFileSplitter(
final HRegionFileSystem regionFs,
final byte[] family,
final StoreFile sf) {
this.regionFs = regionFs;
this.sf = sf;
this.family = family;
}
public Pair<Path,Path> call() throws IOException {
return splitStoreFile(regionFs, family, sf);
}
}
/**
* Post split region actions before the Point-of-No-Return step
* @param env MasterProcedureEnv
**/
private void preSplitRegionBeforePONR(final MasterProcedureEnv env)
throws IOException, InterruptedException {
final List<Mutation> metaEntries = new ArrayList<>();
final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost();
if (cpHost != null) {
if (cpHost.preSplitBeforePONRAction(getSplitRow(), metaEntries, getUser())) {
throw new IOException("Coprocessor bypassing region " +
parentHRI.getRegionNameAsString() + " split.");
}
try {
for (Mutation p : metaEntries) {
HRegionInfo.parseRegionName(p.getRow());
}
} catch (IOException e) {
LOG.error("Row key of mutation from coprocessor is not parsable as region name."
+ "Mutations from coprocessor should only for hbase:meta table.");
throw e;
}
}
}
/**
* Add daughter regions to META
* @param env MasterProcedureEnv
* @throws IOException
*/
private void updateMetaForDaughterRegions(final MasterProcedureEnv env) throws IOException {
RegionStateTransition.Builder transition = RegionStateTransition.newBuilder();
transition.setTransitionCode(TransitionCode.SPLIT_PONR);
transition.addRegionInfo(HRegionInfo.convert(parentHRI));
transition.addRegionInfo(HRegionInfo.convert(daughter_1_HRI));
transition.addRegionInfo(HRegionInfo.convert(daughter_2_HRI));
if (env.getMasterServices().getAssignmentManager().onRegionTransition(
getParentRegionState(env).getServerName(), transition.build()) != null) {
throw new IOException("Failed to update meta to add daughter regions in split region "
+ parentHRI.getRegionNameAsString());
}
}
/**
* Pre split region actions after the Point-of-No-Return step
* @param env MasterProcedureEnv
**/
private void preSplitRegionAfterPONR(final MasterProcedureEnv env)
throws IOException, InterruptedException {
final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost();
if (cpHost != null) {
cpHost.preSplitAfterPONRAction(getUser());
}
}
/**
* Assign daughter regions
* @param env MasterProcedureEnv
* @throws IOException
* @throws InterruptedException
**/
private void openDaughterRegions(final MasterProcedureEnv env)
throws IOException, InterruptedException {
env.getMasterServices().getAssignmentManager().assignDaughterRegions(
parentHRI, daughter_1_HRI, daughter_2_HRI);
}
/**
* Post split region actions
* @param env MasterProcedureEnv
**/
private void postSplitRegion(final MasterProcedureEnv env) throws IOException {
final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost();
if (cpHost != null) {
cpHost.postCompletedSplitRegionAction(daughter_1_HRI, daughter_2_HRI, getUser());
}
}
/**
* Get parent region state
* @param env MasterProcedureEnv
* @return parent region state
*/
private RegionState getParentRegionState(final MasterProcedureEnv env) {
RegionStates regionStates = env.getMasterServices().getAssignmentManager().getRegionStates();
RegionState state = regionStates.getRegionState(parentHRI);
if (state == null) {
LOG.warn("Split but not in region states: " + parentHRI);
state = regionStates.createRegionState(parentHRI);
}
return state;
}
/**
* The procedure could be restarted from a different machine. If the variable is null, we need to
* retrieve it.
* @return traceEnabled
*/
private boolean isTraceEnabled() {
if (traceEnabled == null) {
traceEnabled = LOG.isTraceEnabled();
}
return traceEnabled;
}
}