org.apache.hadoop.hbase.master.procedure.DispatchMergingRegionsProcedure.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.hadoop.hbase.master.procedure.DispatchMergingRegionsProcedure.java

Source

/**
 * 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.Map;
import java.util.concurrent.atomic.AtomicBoolean;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.hbase.HRegionInfo;
import org.apache.hadoop.hbase.RegionLoad;
import org.apache.hadoop.hbase.ServerLoad;
import org.apache.hadoop.hbase.ServerName;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.UnknownRegionException;
import org.apache.hadoop.hbase.classification.InterfaceAudience;
import org.apache.hadoop.hbase.exceptions.MergeRegionException;
import org.apache.hadoop.hbase.exceptions.RegionOpeningException;
import org.apache.hadoop.hbase.master.AssignmentManager;
import org.apache.hadoop.hbase.master.CatalogJanitor;
import org.apache.hadoop.hbase.master.RegionPlan;
import org.apache.hadoop.hbase.master.RegionState;
import org.apache.hadoop.hbase.master.RegionStates;
import org.apache.hadoop.hbase.master.ServerManager;
import org.apache.hadoop.hbase.procedure2.StateMachineProcedure;
import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
import org.apache.hadoop.hbase.protobuf.generated.MasterProcedureProtos;
import org.apache.hadoop.hbase.protobuf.generated.MasterProcedureProtos.DispatchMergingRegionsState;
import org.apache.hadoop.hbase.security.User;
import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;

/**
 * The procedure to Merge a region in a table.
 */
@InterfaceAudience.Private
public class DispatchMergingRegionsProcedure extends
        StateMachineProcedure<MasterProcedureEnv, DispatchMergingRegionsState> implements TableProcedureInterface {
    private static final Log LOG = LogFactory.getLog(DispatchMergingRegionsProcedure.class);

    private final AtomicBoolean aborted = new AtomicBoolean(false);
    private Boolean traceEnabled;
    private AssignmentManager assignmentManager;
    private int timeout;
    private ServerName regionLocation;
    private String regionsToMergeListFullName;
    private String regionsToMergeListEncodedName;

    private User user;
    private TableName tableName;
    private HRegionInfo[] regionsToMerge;
    private boolean forcible;

    public DispatchMergingRegionsProcedure() {
        this.traceEnabled = isTraceEnabled();
        this.assignmentManager = null;
        this.timeout = -1;
        this.regionLocation = null;
        this.regionsToMergeListFullName = null;
        this.regionsToMergeListEncodedName = null;
    }

    public DispatchMergingRegionsProcedure(final MasterProcedureEnv env, final TableName tableName,
            final HRegionInfo[] regionsToMerge, final boolean forcible) throws IOException {
        this.traceEnabled = isTraceEnabled();
        this.assignmentManager = getAssignmentManager(env);
        this.tableName = tableName;
        // For now, we only merge 2 regions.  It could be extended to more than 2 regions in
        // the future.
        assert (regionsToMerge.length == 2);
        this.regionsToMerge = regionsToMerge;
        this.forcible = forcible;

        this.user = env.getRequestUser();
        this.setOwner(this.user.getShortName());

        this.timeout = -1;
        this.regionsToMergeListFullName = getRegionsToMergeListFullNameString();
        this.regionsToMergeListEncodedName = getRegionsToMergeListEncodedNameString();
    }

    @Override
    protected Flow executeFromState(final MasterProcedureEnv env, final DispatchMergingRegionsState state)
            throws InterruptedException {
        if (isTraceEnabled()) {
            LOG.trace(this + " execute state=" + state);
        }

        try {
            switch (state) {
            case DISPATCH_MERGING_REGIONS_PREPARE:
                prepareMergeRegion(env);
                setNextState(DispatchMergingRegionsState.DISPATCH_MERGING_REGIONS_PRE_OPERATION);
                break;
            case DISPATCH_MERGING_REGIONS_PRE_OPERATION:
                //Unused for now - reserve to add preMerge coprocessor in the future
                setNextState(DispatchMergingRegionsState.DISPATCH_MERGING_REGIONS_MOVE_REGION_TO_SAME_RS);
                break;
            case DISPATCH_MERGING_REGIONS_MOVE_REGION_TO_SAME_RS:
                if (MoveRegionsToSameRS(env)) {
                    setNextState(DispatchMergingRegionsState.DISPATCH_MERGING_REGIONS_DO_MERGE_IN_RS);
                } else {
                    LOG.info("Cancel merging regions " + getRegionsToMergeListFullNameString()
                            + ", because can't move them to the same RS");
                    setNextState(DispatchMergingRegionsState.DISPATCH_MERGING_REGIONS_POST_OPERATION);
                }
                break;
            case DISPATCH_MERGING_REGIONS_DO_MERGE_IN_RS:
                doMergeInRS(env);
                setNextState(DispatchMergingRegionsState.DISPATCH_MERGING_REGIONS_POST_OPERATION);
                break;
            case DISPATCH_MERGING_REGIONS_POST_OPERATION:
                //Unused for now - reserve to add postCompletedMerge coprocessor in the future
                return Flow.NO_MORE_STATE;
            default:
                throw new UnsupportedOperationException(this + " unhandled state=" + state);
            }
        } catch (IOException e) {
            LOG.warn("Error trying to merge regions " + getRegionsToMergeListFullNameString() + " in the table "
                    + tableName + " (in state=" + state + ")", e);

            setFailure("master-merge-regions", e);
        }
        return Flow.HAS_MORE_STATE;
    }

    @Override
    protected void rollbackState(final MasterProcedureEnv env, final DispatchMergingRegionsState state)
            throws IOException, InterruptedException {
        if (isTraceEnabled()) {
            LOG.trace(this + " rollback state=" + state);
        }

        try {
            switch (state) {
            case DISPATCH_MERGING_REGIONS_POST_OPERATION:
                break; // nothing to rollback
            case DISPATCH_MERGING_REGIONS_DO_MERGE_IN_RS:
                String msg = this + " We are in the " + state + " state."
                        + " It is complicated to rollback the merge operation that region server is working on."
                        + " Rollback is not supported and we should let the merge operation to complete";
                LOG.warn(msg);
                break;
            case DISPATCH_MERGING_REGIONS_MOVE_REGION_TO_SAME_RS:
                break; // nothing to rollback
            case DISPATCH_MERGING_REGIONS_PRE_OPERATION:
                break; // nothing to rollback
            case DISPATCH_MERGING_REGIONS_PREPARE:
                break; // nothing to rollback
            default:
                throw new UnsupportedOperationException(this + " unhandled state=" + state);
            }
        } catch (Exception 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 merging the regions "
                    + getRegionsToMergeListFullNameString() + " in table " + tableName, e);
            throw e;
        }
    }

    @Override
    protected DispatchMergingRegionsState getState(final int stateId) {
        return DispatchMergingRegionsState.valueOf(stateId);
    }

    @Override
    protected int getStateId(final DispatchMergingRegionsState state) {
        return state.getNumber();
    }

    @Override
    protected DispatchMergingRegionsState getInitialState() {
        return DispatchMergingRegionsState.DISPATCH_MERGING_REGIONS_PREPARE;
    }

    @Override
    protected void setNextState(DispatchMergingRegionsState state) {
        if (aborted.get()) {
            setAbortFailure("merge-table-regions", "abort requested");
        } else {
            super.setNextState(state);
        }
    }

    @Override
    public boolean abort(final MasterProcedureEnv env) {
        aborted.set(true);
        return true;
    }

    @Override
    public void serializeStateData(final OutputStream stream) throws IOException {
        super.serializeStateData(stream);

        MasterProcedureProtos.DispatchMergingRegionsStateData.Builder dispatchMergingRegionsMsg = MasterProcedureProtos.DispatchMergingRegionsStateData
                .newBuilder().setUserInfo(MasterProcedureUtil.toProtoUserInfo(user))
                .setTableName(ProtobufUtil.toProtoTableName(tableName)).setForcible(forcible);
        for (HRegionInfo hri : regionsToMerge) {
            dispatchMergingRegionsMsg.addRegionInfo(HRegionInfo.convert(hri));
        }
        dispatchMergingRegionsMsg.build().writeDelimitedTo(stream);
    }

    @Override
    public void deserializeStateData(final InputStream stream) throws IOException {
        super.deserializeStateData(stream);

        MasterProcedureProtos.DispatchMergingRegionsStateData dispatchMergingRegionsMsg = MasterProcedureProtos.DispatchMergingRegionsStateData
                .parseDelimitedFrom(stream);
        user = MasterProcedureUtil.toUserInfo(dispatchMergingRegionsMsg.getUserInfo());
        tableName = ProtobufUtil.toTableName(dispatchMergingRegionsMsg.getTableName());

        assert (dispatchMergingRegionsMsg.getRegionInfoCount() == 2);
        regionsToMerge = new HRegionInfo[dispatchMergingRegionsMsg.getRegionInfoCount()];
        for (int i = 0; i < regionsToMerge.length; i++) {
            regionsToMerge[i] = HRegionInfo.convert(dispatchMergingRegionsMsg.getRegionInfo(i));
        }
    }

    @Override
    public void toStringClassDetails(StringBuilder sb) {
        sb.append(getClass().getSimpleName());
        sb.append(" (table=");
        sb.append(tableName);
        sb.append(" regions=");
        sb.append(getRegionsToMergeListFullNameString());
        sb.append(" forcible=");
        sb.append(forcible);
        sb.append(")");
    }

    @Override
    protected boolean acquireLock(final MasterProcedureEnv env) {
        return env.getProcedureQueue().waitRegions(this, getTableName(), regionsToMerge[0], regionsToMerge[1]);
    }

    @Override
    protected void releaseLock(final MasterProcedureEnv env) {
        env.getProcedureQueue().wakeRegions(this, getTableName(), regionsToMerge[0], regionsToMerge[1]);
    }

    @Override
    public TableName getTableName() {
        return tableName;
    }

    @Override
    public TableOperationType getTableOperationType() {
        return TableOperationType.MERGE;
    }

    /**
     * Prepare merge and do some check
     * @param env MasterProcedureEnv
     * @throws IOException
     */
    private void prepareMergeRegion(final MasterProcedureEnv env) throws IOException {
        // Note: the following logic assumes that we only have 2 regions to merge.  In the future,
        // if we want to extend to more than 2 regions, the code needs to modify a little bit.
        //
        CatalogJanitor catalogJanitor = env.getMasterServices().getCatalogJanitor();
        boolean regionAHasMergeQualifier = !catalogJanitor.cleanMergeQualifier(regionsToMerge[0]);
        if (regionAHasMergeQualifier || !catalogJanitor.cleanMergeQualifier(regionsToMerge[1])) {
            String msg = "Skip merging regions " + regionsToMerge[0].getRegionNameAsString() + ", "
                    + regionsToMerge[1].getRegionNameAsString() + ", because region "
                    + (regionAHasMergeQualifier ? regionsToMerge[0].getEncodedName()
                            : regionsToMerge[1].getEncodedName())
                    + " has merge qualifier";
            LOG.info(msg);
            throw new MergeRegionException(msg);
        }

        RegionStates regionStates = getAssignmentManager(env).getRegionStates();
        RegionState regionStateA = regionStates.getRegionState(regionsToMerge[0].getEncodedName());
        RegionState regionStateB = regionStates.getRegionState(regionsToMerge[1].getEncodedName());
        if (regionStateA == null || regionStateB == null) {
            throw new UnknownRegionException(
                    regionStateA == null ? regionsToMerge[0].getEncodedName() : regionsToMerge[1].getEncodedName());
        }

        if (!regionStateA.isOpened() || !regionStateB.isOpened()) {
            throw new MergeRegionException(
                    "Unable to merge regions not online " + regionStateA + ", " + regionStateB);
        }

        if (regionsToMerge[0].getReplicaId() != HRegionInfo.DEFAULT_REPLICA_ID
                || regionsToMerge[1].getReplicaId() != HRegionInfo.DEFAULT_REPLICA_ID) {
            throw new MergeRegionException("Can't merge non-default replicas");
        }

        if (!forcible && !HRegionInfo.areAdjacent(regionsToMerge[0], regionsToMerge[1])) {
            throw new MergeRegionException(
                    "Unable to merge not adjacent regions " + regionsToMerge[0].getRegionNameAsString() + ", "
                            + regionsToMerge[1].getRegionNameAsString() + " where forcible = " + forcible);
        }
    }

    /**
     * Move all regions to the same region server
     * @param env MasterProcedureEnv
     * @return whether target regions hosted by the same RS
     * @throws IOException
     */
    private boolean MoveRegionsToSameRS(final MasterProcedureEnv env) throws IOException {
        // Make sure regions are on the same regionserver before send merge
        // regions request to region server.
        //
        boolean onSameRS = isRegionsOnTheSameServer(env);
        if (!onSameRS) {
            // Note: the following logic assumes that we only have 2 regions to merge.  In the future,
            // if we want to extend to more than 2 regions, the code needs to modify a little bit.
            //
            RegionStates regionStates = getAssignmentManager(env).getRegionStates();
            ServerName regionLocation2 = regionStates.getRegionServerOfRegion(regionsToMerge[1]);

            RegionLoad loadOfRegionA = getRegionLoad(env, regionLocation, regionsToMerge[0]);
            RegionLoad loadOfRegionB = getRegionLoad(env, regionLocation2, regionsToMerge[1]);
            if (loadOfRegionA != null && loadOfRegionB != null
                    && loadOfRegionA.getRequestsCount() < loadOfRegionB.getRequestsCount()) {
                // switch regionsToMerge[0] and regionsToMerge[1]
                HRegionInfo tmpRegion = this.regionsToMerge[0];
                this.regionsToMerge[0] = this.regionsToMerge[1];
                this.regionsToMerge[1] = tmpRegion;
                ServerName tmpLocation = regionLocation;
                regionLocation = regionLocation2;
                regionLocation2 = tmpLocation;
            }

            long startTime = EnvironmentEdgeManager.currentTime();

            RegionPlan regionPlan = new RegionPlan(regionsToMerge[1], regionLocation2, regionLocation);
            LOG.info("Moving regions to same server for merge: " + regionPlan.toString());
            getAssignmentManager(env).balance(regionPlan);
            do {
                try {
                    Thread.sleep(20);
                    // Make sure check RIT first, then get region location, otherwise
                    // we would make a wrong result if region is online between getting
                    // region location and checking RIT
                    boolean isRIT = regionStates.isRegionInTransition(regionsToMerge[1]);
                    regionLocation2 = regionStates.getRegionServerOfRegion(regionsToMerge[1]);
                    onSameRS = regionLocation.equals(regionLocation2);
                    if (onSameRS || !isRIT) {
                        // Regions are on the same RS, or regionsToMerge[1] is not in
                        // RegionInTransition any more
                        break;
                    }
                } catch (InterruptedException e) {
                    InterruptedIOException iioe = new InterruptedIOException();
                    iioe.initCause(e);
                    throw iioe;
                }
            } while ((EnvironmentEdgeManager.currentTime() - startTime) <= getTimeout(env));
        }
        return onSameRS;
    }

    /**
     * Do the real merge operation in the region server that hosts regions
     * @param env MasterProcedureEnv
     * @throws IOException
     */
    private void doMergeInRS(final MasterProcedureEnv env) throws IOException {
        long duration = 0;
        long startTime = EnvironmentEdgeManager.currentTime();
        do {
            try {
                if (getServerName(env) == null) {
                    // The merge probably already happen. Check
                    RegionState regionState = getAssignmentManager(env).getRegionStates()
                            .getRegionState(regionsToMerge[0].getEncodedName());
                    if (regionState.isMerging() || regionState.isMerged()) {
                        LOG.info("Merge regions " + getRegionsToMergeListEncodedNameString()
                                + " is in progress or completed.  No need to send a new request.");
                    } else {
                        LOG.warn("Cannot sending merge to hosting server of the regions "
                                + getRegionsToMergeListEncodedNameString() + " as the server is unknown");
                    }
                    return;
                }
                // TODO: the following RPC call is not idempotent.  Multiple calls (eg. after master
                // failover, re-execute this step) could result in some exception thrown that does not
                // paint the correct picture.  This behavior is on-par with old releases.  Improvement
                // could happen in the future.
                env.getMasterServices().getServerManager().sendRegionsMerge(getServerName(env), regionsToMerge[0],
                        regionsToMerge[1], forcible, user);
                LOG.info("Sent merge to server " + getServerName(env) + " for region "
                        + getRegionsToMergeListEncodedNameString() + ", focible=" + forcible);
                return;
            } catch (RegionOpeningException roe) {
                // Do a retry since region should be online on RS immediately
                LOG.warn("Failed mergering regions in " + getServerName(env) + ", retrying...", roe);
            } catch (Exception ie) {
                LOG.warn("Failed sending merge to " + getServerName(env) + " for regions "
                        + getRegionsToMergeListEncodedNameString() + ", focible=" + forcible, ie);
                return;
            }
        } while ((duration = EnvironmentEdgeManager.currentTime() - startTime) <= getTimeout(env));

        // If we reaches here, it means that we get timed out.
        String msg = "Failed sending merge to " + getServerName(env) + " after " + duration + "ms";
        LOG.warn(msg);
        throw new IOException(msg);
    }

    private RegionLoad getRegionLoad(final MasterProcedureEnv env, final ServerName sn, final HRegionInfo hri) {
        ServerManager serverManager = env.getMasterServices().getServerManager();
        ServerLoad load = serverManager.getLoad(sn);
        if (load != null) {
            Map<byte[], RegionLoad> regionsLoad = load.getRegionsLoad();
            if (regionsLoad != null) {
                return regionsLoad.get(hri.getRegionName());
            }
        }
        return null;
    }

    /**
     * The procedure could be restarted from a different machine. If the variable is null, we need to
     * retrieve it.
     * @param env MasterProcedureEnv
     * @return whether target regions hosted by the same RS
     */
    private boolean isRegionsOnTheSameServer(final MasterProcedureEnv env) throws IOException {
        Boolean onSameRS = true;
        int i = 0;
        RegionStates regionStates = getAssignmentManager(env).getRegionStates();
        regionLocation = regionStates.getRegionServerOfRegion(regionsToMerge[i]);
        if (regionLocation != null) {
            for (i = 1; i < regionsToMerge.length; i++) {
                ServerName regionLocation2 = regionStates.getRegionServerOfRegion(regionsToMerge[i]);
                if (regionLocation2 != null) {
                    if (onSameRS) {
                        onSameRS = regionLocation.equals(regionLocation2);
                    }
                } else {
                    // At least one region is not online, merge will fail, no need to continue.
                    break;
                }
            }
            if (i == regionsToMerge.length) {
                // Finish checking all regions, return the result;
                return onSameRS;
            }
        }

        // If reaching here, at least one region is not online.
        String msg = "Skip merging regions " + getRegionsToMergeListFullNameString() + ", because region "
                + regionsToMerge[i].getEncodedName() + " is not online now.";
        LOG.warn(msg);
        throw new IOException(msg);
    }

    /**
     * The procedure could be restarted from a different machine. If the variable is null, we need to
     * retrieve it.
     * @param env MasterProcedureEnv
     * @return assignmentManager
     */
    private AssignmentManager getAssignmentManager(final MasterProcedureEnv env) {
        if (assignmentManager == null) {
            assignmentManager = env.getMasterServices().getAssignmentManager();
        }
        return assignmentManager;
    }

    /**
     * The procedure could be restarted from a different machine. If the variable is null, we need to
     * retrieve it.
     * @param env MasterProcedureEnv
     * @return timeout value
     */
    private int getTimeout(final MasterProcedureEnv env) {
        if (timeout == -1) {
            timeout = env.getMasterConfiguration().getInt("hbase.master.regionmerge.timeout",
                    regionsToMerge.length * 60 * 1000);
        }
        return timeout;
    }

    /**
     * The procedure could be restarted from a different machine. If the variable is null, we need to
     * retrieve it.
     * @param env MasterProcedureEnv
     * @return serverName
     */
    private ServerName getServerName(final MasterProcedureEnv env) {
        if (regionLocation == null) {
            regionLocation = getAssignmentManager(env).getRegionStates().getRegionServerOfRegion(regionsToMerge[0]);
        }
        return regionLocation;
    }

    /**
     * The procedure could be restarted from a different machine. If the variable is null, we need to
     * retrieve it.
     * @param fullName whether return only encoded name
     * @return region names in a list
     */
    private String getRegionsToMergeListFullNameString() {
        if (regionsToMergeListFullName == null) {
            StringBuilder sb = new StringBuilder("[");
            int i = 0;
            while (i < regionsToMerge.length - 1) {
                sb.append(regionsToMerge[i].getRegionNameAsString() + ", ");
                i++;
            }
            sb.append(regionsToMerge[i].getRegionNameAsString() + " ]");
            regionsToMergeListFullName = sb.toString();
        }
        return regionsToMergeListFullName;
    }

    /**
     * The procedure could be restarted from a different machine. If the variable is null, we need to
     * retrieve it.
     * @return encoded region names
     */
    private String getRegionsToMergeListEncodedNameString() {
        if (regionsToMergeListEncodedName == null) {
            StringBuilder sb = new StringBuilder("[");
            int i = 0;
            while (i < regionsToMerge.length - 1) {
                sb.append(regionsToMerge[i].getEncodedName() + ", ");
                i++;
            }
            sb.append(regionsToMerge[i].getEncodedName() + " ]");
            regionsToMergeListEncodedName = sb.toString();
        }
        return regionsToMergeListEncodedName;
    }

    /**
     * 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;
    }
}