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 */ 018 019package org.apache.hadoop.hbase.master.assignment; 020 021import java.io.IOException; 022import java.util.ArrayList; 023import java.util.Arrays; 024import java.util.Collection; 025import java.util.List; 026import java.util.stream.Collectors; 027import org.apache.hadoop.conf.Configuration; 028import org.apache.hadoop.fs.FileSystem; 029import org.apache.hadoop.fs.Path; 030import org.apache.hadoop.hbase.MetaMutationAnnotation; 031import org.apache.hadoop.hbase.ServerName; 032import org.apache.hadoop.hbase.TableName; 033import org.apache.hadoop.hbase.UnknownRegionException; 034import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor; 035import org.apache.hadoop.hbase.client.DoNotRetryRegionException; 036import org.apache.hadoop.hbase.client.MasterSwitchType; 037import org.apache.hadoop.hbase.client.Mutation; 038import org.apache.hadoop.hbase.client.RegionInfo; 039import org.apache.hadoop.hbase.client.RegionInfoBuilder; 040import org.apache.hadoop.hbase.client.RegionReplicaUtil; 041import org.apache.hadoop.hbase.client.TableDescriptor; 042import org.apache.hadoop.hbase.exceptions.MergeRegionException; 043import org.apache.hadoop.hbase.io.hfile.CacheConfig; 044import org.apache.hadoop.hbase.master.CatalogJanitor; 045import org.apache.hadoop.hbase.master.MasterCoprocessorHost; 046import org.apache.hadoop.hbase.master.MasterFileSystem; 047import org.apache.hadoop.hbase.master.RegionState; 048import org.apache.hadoop.hbase.master.RegionState.State; 049import org.apache.hadoop.hbase.master.normalizer.NormalizationPlan; 050import org.apache.hadoop.hbase.master.procedure.AbstractStateMachineTableProcedure; 051import org.apache.hadoop.hbase.master.procedure.MasterProcedureEnv; 052import org.apache.hadoop.hbase.master.procedure.MasterProcedureUtil; 053import org.apache.hadoop.hbase.master.procedure.TableQueue; 054import org.apache.hadoop.hbase.procedure2.ProcedureMetrics; 055import org.apache.hadoop.hbase.procedure2.ProcedureStateSerializer; 056import org.apache.hadoop.hbase.quotas.QuotaExceededException; 057import org.apache.hadoop.hbase.regionserver.HRegionFileSystem; 058import org.apache.hadoop.hbase.regionserver.HStoreFile; 059import org.apache.hadoop.hbase.regionserver.StoreFileInfo; 060import org.apache.hadoop.hbase.util.Bytes; 061import org.apache.hadoop.hbase.util.FSUtils; 062import org.apache.hadoop.hbase.wal.WALSplitter; 063import org.apache.yetus.audience.InterfaceAudience; 064import org.slf4j.Logger; 065import org.slf4j.LoggerFactory; 066 067import org.apache.hbase.thirdparty.com.google.common.annotations.VisibleForTesting; 068 069import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil; 070import org.apache.hadoop.hbase.shaded.protobuf.generated.AdminProtos.GetRegionInfoResponse; 071import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProcedureProtos; 072import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProcedureProtos.MergeTableRegionsState; 073 074/** 075 * The procedure to Merge regions in a table. This procedure takes an exclusive table 076 * lock since it is working over multiple regions. It holds the lock for the life of the procedure. 077 * Throws exception on construction if determines context hostile to merge (cluster going down or 078 * master is shutting down or table is disabled). 079 */ 080@InterfaceAudience.Private 081public class MergeTableRegionsProcedure 082 extends AbstractStateMachineTableProcedure<MergeTableRegionsState> { 083 private static final Logger LOG = LoggerFactory.getLogger(MergeTableRegionsProcedure.class); 084 private ServerName regionLocation; 085 086 /** 087 * Two or more regions to merge, the 'merge parents'. 088 */ 089 private RegionInfo[] regionsToMerge; 090 091 /** 092 * The resulting merged region. 093 */ 094 private RegionInfo mergedRegion; 095 096 private boolean force; 097 098 public MergeTableRegionsProcedure() { 099 // Required by the Procedure framework to create the procedure on replay 100 } 101 102 public MergeTableRegionsProcedure(final MasterProcedureEnv env, 103 final RegionInfo[] regionsToMerge, final boolean force) 104 throws IOException { 105 super(env); 106 // Check parent regions. Make sure valid before starting work. 107 // This check calls the super method #checkOnline also. 108 checkRegionsToMerge(env, regionsToMerge, force); 109 // Sort the regions going into the merge. 110 Arrays.sort(regionsToMerge); 111 this.regionsToMerge = regionsToMerge; 112 this.mergedRegion = createMergedRegionInfo(regionsToMerge); 113 // Preflight depends on mergedRegion being set (at least). 114 preflightChecks(env, true); 115 this.force = force; 116 } 117 118 /** 119 * @throws MergeRegionException If unable to merge regions for whatever reasons. 120 */ 121 private static void checkRegionsToMerge(MasterProcedureEnv env, final RegionInfo[] regions, 122 final boolean force) throws MergeRegionException { 123 long count = Arrays.stream(regions).distinct().count(); 124 if (regions.length != count) { 125 throw new MergeRegionException("Duplicate regions specified; cannot merge a region to " + 126 "itself. Passed in " + regions.length + " but only " + count + " unique."); 127 } 128 if (count < 2) { 129 throw new MergeRegionException("Need two Regions at least to run a Merge"); 130 } 131 RegionInfo previous = null; 132 for (RegionInfo ri: regions) { 133 if (previous != null) { 134 if (!previous.getTable().equals(ri.getTable())) { 135 String msg = "Can't merge regions from different tables: " + previous + ", " + ri; 136 LOG.warn(msg); 137 throw new MergeRegionException(msg); 138 } 139 if (!force && !ri.isAdjacent(previous) && !ri.isOverlap(previous)) { 140 String msg = "Unable to merge non-adjacent or non-overlapping regions " + 141 previous.getShortNameToLog() + ", " + ri.getShortNameToLog() + " when force=false"; 142 LOG.warn(msg); 143 throw new MergeRegionException(msg); 144 } 145 } 146 147 if (ri.getReplicaId() != RegionInfo.DEFAULT_REPLICA_ID) { 148 throw new MergeRegionException("Can't merge non-default replicas; " + ri); 149 } 150 try { 151 checkOnline(env, ri); 152 } catch (DoNotRetryRegionException dnrre) { 153 throw new MergeRegionException(dnrre); 154 } 155 156 previous = ri; 157 } 158 } 159 160 /** 161 * Create merged region info by looking at passed in <code>regionsToMerge</code> 162 * to figure what extremes for start and end keys to use; merged region needs 163 * to have an extent sufficient to cover all regions-to-merge. 164 */ 165 private static RegionInfo createMergedRegionInfo(final RegionInfo[] regionsToMerge) { 166 byte [] lowestStartKey = null; 167 byte [] highestEndKey = null; 168 long highestRegionId = -1; 169 for (RegionInfo ri: regionsToMerge) { 170 if (lowestStartKey == null) { 171 lowestStartKey = ri.getStartKey(); 172 } else if (Bytes.compareTo(ri.getStartKey(), lowestStartKey) < 0) { 173 lowestStartKey = ri.getStartKey(); 174 } 175 if (highestEndKey == null) { 176 highestEndKey = ri.getEndKey(); 177 } else if (ri.isLast() || Bytes.compareTo(ri.getEndKey(), highestEndKey) > 0) { 178 highestEndKey = ri.getEndKey(); 179 } 180 highestRegionId = ri.getRegionId() > highestRegionId? ri.getRegionId(): highestRegionId; 181 } 182 // Merged region is sorted between two merging regions in META 183 return RegionInfoBuilder.newBuilder(regionsToMerge[0].getTable()). 184 setStartKey(lowestStartKey). 185 setEndKey(highestEndKey). 186 setSplit(false). 187 setRegionId(highestRegionId + 1/*Add one so new merged region is highest*/). 188 build(); 189 } 190 191 @Override 192 protected Flow executeFromState(final MasterProcedureEnv env, 193 MergeTableRegionsState state) { 194 LOG.trace("{} execute state={}", this, state); 195 try { 196 switch (state) { 197 case MERGE_TABLE_REGIONS_PREPARE: 198 if (!prepareMergeRegion(env)) { 199 assert isFailed() : "Merge region should have an exception here"; 200 return Flow.NO_MORE_STATE; 201 } 202 setNextState(MergeTableRegionsState.MERGE_TABLE_REGIONS_PRE_MERGE_OPERATION); 203 break; 204 case MERGE_TABLE_REGIONS_PRE_MERGE_OPERATION: 205 preMergeRegions(env); 206 setNextState(MergeTableRegionsState.MERGE_TABLE_REGIONS_CLOSE_REGIONS); 207 break; 208 case MERGE_TABLE_REGIONS_CLOSE_REGIONS: 209 addChildProcedure(createUnassignProcedures(env, getRegionReplication(env))); 210 setNextState(MergeTableRegionsState.MERGE_TABLE_REGIONS_CHECK_CLOSED_REGIONS); 211 break; 212 case MERGE_TABLE_REGIONS_CHECK_CLOSED_REGIONS: 213 List<RegionInfo> ris = hasRecoveredEdits(env); 214 if (ris.isEmpty()) { 215 setNextState(MergeTableRegionsState.MERGE_TABLE_REGIONS_CREATE_MERGED_REGION); 216 } else { 217 // Need to reopen parent regions to pickup missed recovered.edits. Do it by creating 218 // child assigns and then stepping back to MERGE_TABLE_REGIONS_CLOSE_REGIONS. 219 // Just assign the primary regions recovering the missed recovered.edits -- no replicas. 220 // May need to cycle here a few times if heavy writes. 221 // TODO: Add an assign read-only. 222 for (RegionInfo ri: ris) { 223 LOG.info("Found recovered.edits under {}, reopen to pickup missed edits!", ri); 224 addChildProcedure(env.getAssignmentManager().createAssignProcedure(ri)); 225 } 226 setNextState(MergeTableRegionsState.MERGE_TABLE_REGIONS_CLOSE_REGIONS); 227 } 228 break; 229 case MERGE_TABLE_REGIONS_CREATE_MERGED_REGION: 230 createMergedRegion(env); 231 setNextState(MergeTableRegionsState.MERGE_TABLE_REGIONS_WRITE_MAX_SEQUENCE_ID_FILE); 232 break; 233 case MERGE_TABLE_REGIONS_WRITE_MAX_SEQUENCE_ID_FILE: 234 writeMaxSequenceIdFile(env); 235 setNextState(MergeTableRegionsState.MERGE_TABLE_REGIONS_PRE_MERGE_COMMIT_OPERATION); 236 break; 237 case MERGE_TABLE_REGIONS_PRE_MERGE_COMMIT_OPERATION: 238 preMergeRegionsCommit(env); 239 setNextState(MergeTableRegionsState.MERGE_TABLE_REGIONS_UPDATE_META); 240 break; 241 case MERGE_TABLE_REGIONS_UPDATE_META: 242 updateMetaForMergedRegions(env); 243 setNextState(MergeTableRegionsState.MERGE_TABLE_REGIONS_POST_MERGE_COMMIT_OPERATION); 244 break; 245 case MERGE_TABLE_REGIONS_POST_MERGE_COMMIT_OPERATION: 246 postMergeRegionsCommit(env); 247 setNextState(MergeTableRegionsState.MERGE_TABLE_REGIONS_OPEN_MERGED_REGION); 248 break; 249 case MERGE_TABLE_REGIONS_OPEN_MERGED_REGION: 250 addChildProcedure(createAssignProcedures(env, getRegionReplication(env))); 251 setNextState(MergeTableRegionsState.MERGE_TABLE_REGIONS_POST_OPERATION); 252 break; 253 case MERGE_TABLE_REGIONS_POST_OPERATION: 254 postCompletedMergeRegions(env); 255 return Flow.NO_MORE_STATE; 256 default: 257 throw new UnsupportedOperationException(this + " unhandled state=" + state); 258 } 259 } catch (IOException e) { 260 String msg = "Error trying to merge " + RegionInfo.getShortNameToLog(regionsToMerge) + 261 " in " + getTableName() + " (in state=" + state + ")"; 262 if (!isRollbackSupported(state)) { 263 // We reach a state that cannot be rolled back. We just need to keep retrying. 264 LOG.warn(msg, e); 265 } else { 266 LOG.error(msg, e); 267 setFailure("master-merge-regions", e); 268 } 269 } 270 return Flow.HAS_MORE_STATE; 271 } 272 273 /** 274 * To rollback {@link MergeTableRegionsProcedure}, two AssignProcedures are asynchronously 275 * submitted for each region to be merged (rollback doesn't wait on the completion of the 276 * AssignProcedures) . This can be improved by changing rollback() to support sub-procedures. 277 * See HBASE-19851 for details. 278 */ 279 @Override 280 protected void rollbackState(final MasterProcedureEnv env, final MergeTableRegionsState state) 281 throws IOException { 282 LOG.trace("{} rollback state={}", this, state); 283 284 try { 285 switch (state) { 286 case MERGE_TABLE_REGIONS_POST_OPERATION: 287 case MERGE_TABLE_REGIONS_OPEN_MERGED_REGION: 288 case MERGE_TABLE_REGIONS_POST_MERGE_COMMIT_OPERATION: 289 case MERGE_TABLE_REGIONS_UPDATE_META: 290 String msg = this + " We are in the " + state + " state." + 291 " It is complicated to rollback the merge operation that region server is working on." + 292 " Rollback is not supported and we should let the merge operation to complete"; 293 LOG.warn(msg); 294 // PONR 295 throw new UnsupportedOperationException(this + " unhandled state=" + state); 296 case MERGE_TABLE_REGIONS_PRE_MERGE_COMMIT_OPERATION: 297 break; 298 case MERGE_TABLE_REGIONS_CREATE_MERGED_REGION: 299 case MERGE_TABLE_REGIONS_WRITE_MAX_SEQUENCE_ID_FILE: 300 cleanupMergedRegion(env); 301 break; 302 case MERGE_TABLE_REGIONS_CHECK_CLOSED_REGIONS: 303 break; 304 case MERGE_TABLE_REGIONS_CLOSE_REGIONS: 305 rollbackCloseRegionsForMerge(env); 306 break; 307 case MERGE_TABLE_REGIONS_PRE_MERGE_OPERATION: 308 postRollBackMergeRegions(env); 309 break; 310 case MERGE_TABLE_REGIONS_PREPARE: 311 break; 312 default: 313 throw new UnsupportedOperationException(this + " unhandled state=" + state); 314 } 315 } catch (Exception e) { 316 // This will be retried. Unless there is a bug in the code, 317 // this should be just a "temporary error" (e.g. network down) 318 LOG.warn("Failed rollback attempt step " + state + " for merging the regions " 319 + RegionInfo.getShortNameToLog(regionsToMerge) + " in table " + getTableName(), e); 320 throw e; 321 } 322 } 323 324 /* 325 * Check whether we are in the state that can be rolled back 326 */ 327 @Override 328 protected boolean isRollbackSupported(final MergeTableRegionsState state) { 329 switch (state) { 330 case MERGE_TABLE_REGIONS_POST_OPERATION: 331 case MERGE_TABLE_REGIONS_OPEN_MERGED_REGION: 332 case MERGE_TABLE_REGIONS_POST_MERGE_COMMIT_OPERATION: 333 case MERGE_TABLE_REGIONS_UPDATE_META: 334 // It is not safe to rollback in these states. 335 return false; 336 default: 337 break; 338 } 339 return true; 340 } 341 342 @Override 343 protected MergeTableRegionsState getState(final int stateId) { 344 return MergeTableRegionsState.forNumber(stateId); 345 } 346 347 @Override 348 protected int getStateId(final MergeTableRegionsState state) { 349 return state.getNumber(); 350 } 351 352 @Override 353 protected MergeTableRegionsState getInitialState() { 354 return MergeTableRegionsState.MERGE_TABLE_REGIONS_PREPARE; 355 } 356 357 @Override 358 protected void serializeStateData(ProcedureStateSerializer serializer) 359 throws IOException { 360 super.serializeStateData(serializer); 361 362 final MasterProcedureProtos.MergeTableRegionsStateData.Builder mergeTableRegionsMsg = 363 MasterProcedureProtos.MergeTableRegionsStateData.newBuilder() 364 .setUserInfo(MasterProcedureUtil.toProtoUserInfo(getUser())) 365 .setMergedRegionInfo(ProtobufUtil.toRegionInfo(mergedRegion)) 366 .setForcible(force); 367 for (RegionInfo ri: regionsToMerge) { 368 mergeTableRegionsMsg.addRegionInfo(ProtobufUtil.toRegionInfo(ri)); 369 } 370 serializer.serialize(mergeTableRegionsMsg.build()); 371 } 372 373 @Override 374 protected void deserializeStateData(ProcedureStateSerializer serializer) 375 throws IOException { 376 super.deserializeStateData(serializer); 377 378 final MasterProcedureProtos.MergeTableRegionsStateData mergeTableRegionsMsg = 379 serializer.deserialize(MasterProcedureProtos.MergeTableRegionsStateData.class); 380 setUser(MasterProcedureUtil.toUserInfo(mergeTableRegionsMsg.getUserInfo())); 381 382 assert(mergeTableRegionsMsg.getRegionInfoCount() == 2); 383 regionsToMerge = new RegionInfo[mergeTableRegionsMsg.getRegionInfoCount()]; 384 for (int i = 0; i < regionsToMerge.length; i++) { 385 regionsToMerge[i] = ProtobufUtil.toRegionInfo(mergeTableRegionsMsg.getRegionInfo(i)); 386 } 387 388 mergedRegion = ProtobufUtil.toRegionInfo(mergeTableRegionsMsg.getMergedRegionInfo()); 389 } 390 391 @Override 392 public void toStringClassDetails(StringBuilder sb) { 393 sb.append(getClass().getSimpleName()); 394 sb.append(" table="); 395 sb.append(getTableName()); 396 sb.append(", regions="); 397 sb.append(RegionInfo.getShortNameToLog(regionsToMerge)); 398 sb.append(", force="); 399 sb.append(force); 400 } 401 402 @Override 403 protected LockState acquireLock(final MasterProcedureEnv env) { 404 if (env.getProcedureScheduler().waitRegions(this, getTableName(), 405 mergedRegion, regionsToMerge[0], regionsToMerge[1])) { 406 try { 407 LOG.debug(LockState.LOCK_EVENT_WAIT + " " + env.getProcedureScheduler().dumpLocks()); 408 } catch (IOException e) { 409 // Ignore, just for logging 410 } 411 return LockState.LOCK_EVENT_WAIT; 412 } 413 return LockState.LOCK_ACQUIRED; 414 } 415 416 @Override 417 protected void releaseLock(final MasterProcedureEnv env) { 418 env.getProcedureScheduler().wakeRegions(this, getTableName(), 419 mergedRegion, regionsToMerge[0], regionsToMerge[1]); 420 } 421 422 @Override 423 protected boolean holdLock(MasterProcedureEnv env) { 424 return true; 425 } 426 427 @Override 428 public TableName getTableName() { 429 return mergedRegion.getTable(); 430 } 431 432 @Override 433 public TableOperationType getTableOperationType() { 434 return TableOperationType.REGION_MERGE; 435 } 436 437 @Override 438 protected ProcedureMetrics getProcedureMetrics(MasterProcedureEnv env) { 439 return env.getAssignmentManager().getAssignmentManagerMetrics().getMergeProcMetrics(); 440 } 441 442 /** 443 * Return list of regions that have recovered.edits... usually its an empty list. 444 * @param env the master env 445 * @throws IOException IOException 446 */ 447 private List<RegionInfo> hasRecoveredEdits(final MasterProcedureEnv env) throws IOException { 448 List<RegionInfo> ris = new ArrayList<RegionInfo>(regionsToMerge.length); 449 for (int i = 0; i < regionsToMerge.length; i++) { 450 RegionInfo ri = regionsToMerge[i]; 451 if (SplitTableRegionProcedure.hasRecoveredEdits(env, ri)) { 452 ris.add(ri); 453 } 454 } 455 return ris; 456 } 457 458 /** 459 * Prepare merge and do some check 460 * @param env MasterProcedureEnv 461 * @throws IOException 462 */ 463 private boolean prepareMergeRegion(final MasterProcedureEnv env) throws IOException { 464 // Fail if we are taking snapshot for the given table 465 TableName tn = regionsToMerge[0].getTable(); 466 if (env.getMasterServices().getSnapshotManager().isTakingSnapshot(tn)) { 467 throw new MergeRegionException("Skip merging regions " + 468 RegionInfo.getShortNameToLog(regionsToMerge) + ", because we are snapshotting " + tn); 469 } 470 if (!env.getMasterServices().isSplitOrMergeEnabled(MasterSwitchType.MERGE)) { 471 String regionsStr = Arrays.deepToString(this.regionsToMerge); 472 LOG.warn("Merge switch is off! skip merge of " + regionsStr); 473 super.setFailure(getClass().getSimpleName(), 474 new IOException("Merge of " + regionsStr + " failed because merge switch is off")); 475 return false; 476 } 477 // See HBASE-21395, for 2.0.x and 2.1.x only. 478 // A safe fence here, if there is a table procedure going on, abort the merge. 479 // There some cases that may lead to table procedure roll back (more serious 480 // than roll back the merge procedure here), or the merged regions was brought online 481 // by the table procedure because of the race between merge procedure and table procedure 482 List<AbstractStateMachineTableProcedure> tableProcedures = env 483 .getMasterServices().getProcedures().stream() 484 .filter(p -> p instanceof AbstractStateMachineTableProcedure) 485 .map(p -> (AbstractStateMachineTableProcedure) p) 486 .filter(p -> p.getProcId() != this.getProcId() && p.getTableName() 487 .equals(regionsToMerge[0].getTable()) && !p.isFinished() 488 && TableQueue.requireTableExclusiveLock(p)) 489 .collect(Collectors.toList()); 490 if (tableProcedures != null && tableProcedures.size() > 0) { 491 throw new MergeRegionException(tableProcedures.get(0).toString() 492 + " is going on against the same table, abort the merge of " + this 493 .toString()); 494 } 495 496 CatalogJanitor catalogJanitor = env.getMasterServices().getCatalogJanitor(); 497 RegionStates regionStates = env.getAssignmentManager().getRegionStates(); 498 for (RegionInfo ri: this.regionsToMerge) { 499 if (!catalogJanitor.cleanMergeQualifier(ri)) { 500 String msg = "Skip merging " + RegionInfo.getShortNameToLog(regionsToMerge) + 501 ", because parent " + RegionInfo.getShortNameToLog(ri) + " has a merge qualifier"; 502 LOG.warn(msg); 503 throw new MergeRegionException(msg); 504 } 505 RegionState state = regionStates.getRegionState(ri.getEncodedName()); 506 if (state == null) { 507 throw new UnknownRegionException("No state for " + RegionInfo.getShortNameToLog(ri)); 508 } 509 if (!state.isOpened()) { 510 throw new MergeRegionException("Unable to merge regions that are not online: " + ri); 511 } 512 // Ask the remote regionserver if regions are mergeable. If we get an IOE, report it 513 // along with the failure, so we can see why regions are not mergeable at this time. 514 try { 515 if (!isMergeable(env, state)) { 516 return false; 517 } 518 } catch (IOException e) { 519 IOException ioe = new IOException(RegionInfo.getShortNameToLog(ri) + " NOT mergeable", e); 520 super.setFailure(getClass().getSimpleName(), ioe); 521 return false; 522 } 523 } 524 525 // Update region states to Merging 526 setRegionStateToMerging(env); 527 return true; 528 } 529 530 private boolean isMergeable(final MasterProcedureEnv env, final RegionState rs) 531 throws IOException { 532 GetRegionInfoResponse response = 533 Util.getRegionInfoResponse(env, rs.getServerName(), rs.getRegion()); 534 return response.hasMergeable() && response.getMergeable(); 535 } 536 537 /** 538 * Pre merge region action 539 * @param env MasterProcedureEnv 540 **/ 541 private void preMergeRegions(final MasterProcedureEnv env) throws IOException { 542 final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost(); 543 if (cpHost != null) { 544 cpHost.preMergeRegionsAction(regionsToMerge, getUser()); 545 } 546 // TODO: Clean up split and merge. Currently all over the place. 547 try { 548 env.getMasterServices().getMasterQuotaManager().onRegionMerged(this.mergedRegion); 549 } catch (QuotaExceededException e) { 550 env.getMasterServices().getRegionNormalizer().planSkipped(this.mergedRegion, 551 NormalizationPlan.PlanType.MERGE); 552 throw e; 553 } 554 } 555 556 /** 557 * Action after rollback a merge table regions action. 558 */ 559 private void postRollBackMergeRegions(final MasterProcedureEnv env) throws IOException { 560 final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost(); 561 if (cpHost != null) { 562 cpHost.postRollBackMergeRegionsAction(regionsToMerge, getUser()); 563 } 564 } 565 566 /** 567 * Set the region states to MERGING state 568 * @param env MasterProcedureEnv 569 */ 570 public void setRegionStateToMerging(final MasterProcedureEnv env) { 571 // Set State.MERGING to regions to be merged 572 RegionStates regionStates = env.getAssignmentManager().getRegionStates(); 573 for (RegionInfo ri: this.regionsToMerge) { 574 regionStates.getRegionStateNode(ri).setState(State.MERGING); 575 } 576 } 577 578 /** 579 * Create merged region. 580 * The way the merge works is that we make a 'merges' temporary 581 * directory in the FIRST parent region to merge (Do not change this without 582 * also changing the rollback where we look in this FIRST region for the 583 * merge dir). We then collect here references to all the store files in all 584 * the parent regions including those of the FIRST parent region into a 585 * subdirectory, named for the resultant merged region. We then call 586 * commitMergeRegion. It finds this subdirectory of storefile references 587 * and moves them under the new merge region (creating the region layout 588 * as side effect). After assign of the new merge region, we will run a 589 * compaction. This will undo the references but the reference files remain 590 * in place until the archiver runs (which it does on a period as a chore 591 * in the RegionServer that hosts the merge region -- see 592 * CompactedHFilesDischarger). Once the archiver has moved aside the 593 * no-longer used references, the merge region no longer has references. 594 * The catalog janitor will notice when it runs next and it will remove 595 * the old parent regions. 596 */ 597 private void createMergedRegion(final MasterProcedureEnv env) throws IOException { 598 final MasterFileSystem mfs = env.getMasterServices().getMasterFileSystem(); 599 final Path tabledir = FSUtils.getTableDir(mfs.getRootDir(), regionsToMerge[0].getTable()); 600 final FileSystem fs = mfs.getFileSystem(); 601 HRegionFileSystem mergeRegionFs = null; 602 for (RegionInfo ri: this.regionsToMerge) { 603 HRegionFileSystem regionFs = HRegionFileSystem.openRegionFromFileSystem( 604 env.getMasterConfiguration(), fs, tabledir, ri, false); 605 if (mergeRegionFs == null) { 606 mergeRegionFs = regionFs; 607 mergeRegionFs.createMergesDir(); 608 } 609 mergeStoreFiles(env, regionFs, mergeRegionFs.getMergesDir()); 610 } 611 assert mergeRegionFs != null; 612 mergeRegionFs.commitMergedRegion(mergedRegion); 613 614 // Prepare to create merged regions 615 env.getAssignmentManager().getRegionStates(). 616 getOrCreateRegionStateNode(mergedRegion).setState(State.MERGING_NEW); 617 } 618 619 /** 620 * Create reference file(s) to parent region hfiles in the <code>mergeDir</code> 621 * @param regionFs merge parent region file system 622 * @param mergeDir the temp directory in which we are accumulating references. 623 */ 624 private void mergeStoreFiles(final MasterProcedureEnv env, final HRegionFileSystem regionFs, 625 final Path mergeDir) throws IOException { 626 final MasterFileSystem mfs = env.getMasterServices().getMasterFileSystem(); 627 final Configuration conf = env.getMasterConfiguration(); 628 final TableDescriptor htd = env.getMasterServices().getTableDescriptors().get(getTableName()); 629 for (ColumnFamilyDescriptor hcd : htd.getColumnFamilies()) { 630 String family = hcd.getNameAsString(); 631 final Collection<StoreFileInfo> storeFiles = regionFs.getStoreFiles(family); 632 if (storeFiles != null && storeFiles.size() > 0) { 633 for (StoreFileInfo storeFileInfo : storeFiles) { 634 // Create reference file(s) to parent region file here in mergedDir. 635 // As this procedure is running on master, use CacheConfig.DISABLED means 636 // don't cache any block. 637 regionFs.mergeStoreFile(mergedRegion, family, new HStoreFile(mfs.getFileSystem(), 638 storeFileInfo, conf, CacheConfig.DISABLED, hcd.getBloomFilterType(), true), 639 mergeDir); 640 } 641 } 642 } 643 } 644 645 /** 646 * Clean up a merged region on rollback after failure. 647 */ 648 private void cleanupMergedRegion(final MasterProcedureEnv env) throws IOException { 649 final MasterFileSystem mfs = env.getMasterServices().getMasterFileSystem(); 650 TableName tn = this.regionsToMerge[0].getTable(); 651 final Path tabledir = FSUtils.getTableDir(mfs.getRootDir(), tn); 652 final FileSystem fs = mfs.getFileSystem(); 653 // See createMergedRegion above where we specify the merge dir as being in the 654 // FIRST merge parent region. 655 HRegionFileSystem regionFs = HRegionFileSystem.openRegionFromFileSystem( 656 env.getMasterConfiguration(), fs, tabledir, regionsToMerge[0], false); 657 regionFs.cleanupMergedRegion(mergedRegion); 658 } 659 660 /** 661 * Rollback close regions 662 * @param env MasterProcedureEnv 663 **/ 664 private void rollbackCloseRegionsForMerge(final MasterProcedureEnv env) throws IOException { 665 // Check whether the region is closed; if so, open it in the same server 666 final int regionReplication = getRegionReplication(env); 667 final ServerName serverName = getServerName(env); 668 669 final AssignProcedure[] procs = 670 new AssignProcedure[regionsToMerge.length * regionReplication]; 671 int procsIdx = 0; 672 for (int i = 0; i < regionsToMerge.length; ++i) { 673 for (int j = 0; j < regionReplication; ++j) { 674 final RegionInfo hri = RegionReplicaUtil.getRegionInfoForReplica(regionsToMerge[i], j); 675 procs[procsIdx++] = env.getAssignmentManager().createAssignProcedure(hri, serverName); 676 } 677 } 678 env.getMasterServices().getMasterProcedureExecutor().submitProcedures(procs); 679 } 680 681 private UnassignProcedure[] createUnassignProcedures(final MasterProcedureEnv env, 682 final int regionReplication) { 683 final UnassignProcedure[] procs = 684 new UnassignProcedure[regionsToMerge.length * regionReplication]; 685 int procsIdx = 0; 686 for (int i = 0; i < regionsToMerge.length; ++i) { 687 for (int j = 0; j < regionReplication; ++j) { 688 final RegionInfo hri = RegionReplicaUtil.getRegionInfoForReplica(regionsToMerge[i], j); 689 procs[procsIdx++] = env.getAssignmentManager(). 690 createUnassignProcedure(hri, null, true, !RegionReplicaUtil.isDefaultReplica(hri)); 691 } 692 } 693 return procs; 694 } 695 696 private AssignProcedure[] createAssignProcedures(final MasterProcedureEnv env, 697 final int regionReplication) { 698 final ServerName targetServer = getServerName(env); 699 final AssignProcedure[] procs = new AssignProcedure[regionReplication]; 700 for (int i = 0; i < procs.length; ++i) { 701 final RegionInfo hri = RegionReplicaUtil.getRegionInfoForReplica(mergedRegion, i); 702 procs[i] = env.getAssignmentManager().createAssignProcedure(hri, targetServer); 703 } 704 return procs; 705 } 706 707 private int getRegionReplication(final MasterProcedureEnv env) throws IOException { 708 return env.getMasterServices().getTableDescriptors().get(getTableName()). 709 getRegionReplication(); 710 } 711 712 /** 713 * Post merge region action 714 * @param env MasterProcedureEnv 715 **/ 716 private void preMergeRegionsCommit(final MasterProcedureEnv env) throws IOException { 717 final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost(); 718 if (cpHost != null) { 719 @MetaMutationAnnotation 720 final List<Mutation> metaEntries = new ArrayList<>(); 721 cpHost.preMergeRegionsCommit(regionsToMerge, metaEntries, getUser()); 722 try { 723 for (Mutation p : metaEntries) { 724 RegionInfo.parseRegionName(p.getRow()); 725 } 726 } catch (IOException e) { 727 LOG.error("Row key of mutation from coprocessor is not parsable as region name. " 728 + "Mutations from coprocessor should only be for hbase:meta table.", e); 729 throw e; 730 } 731 } 732 } 733 734 /** 735 * Add merged region to META and delete original regions. 736 */ 737 private void updateMetaForMergedRegions(final MasterProcedureEnv env) throws IOException { 738 env.getAssignmentManager().markRegionAsMerged(mergedRegion, getServerName(env), 739 this.regionsToMerge); 740 } 741 742 /** 743 * Post merge region action 744 * @param env MasterProcedureEnv 745 **/ 746 private void postMergeRegionsCommit(final MasterProcedureEnv env) throws IOException { 747 final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost(); 748 if (cpHost != null) { 749 cpHost.postMergeRegionsCommit(regionsToMerge, mergedRegion, getUser()); 750 } 751 } 752 753 /** 754 * Post merge region action 755 * @param env MasterProcedureEnv 756 **/ 757 private void postCompletedMergeRegions(final MasterProcedureEnv env) throws IOException { 758 final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost(); 759 if (cpHost != null) { 760 cpHost.postCompletedMergeRegionsAction(regionsToMerge, mergedRegion, getUser()); 761 } 762 } 763 764 /** 765 * The procedure could be restarted from a different machine. If the variable is null, we need to 766 * retrieve it. 767 * @param env MasterProcedureEnv 768 * @return serverName 769 */ 770 private ServerName getServerName(final MasterProcedureEnv env) { 771 if (regionLocation == null) { 772 regionLocation = env.getAssignmentManager().getRegionStates(). 773 getRegionServerOfRegion(regionsToMerge[0]); 774 // May still be null here but return null and let caller deal. 775 // Means we lost the in-memory-only location. We are in recovery 776 // or so. The caller should be able to deal w/ a null ServerName. 777 // Let them go to the Balancer to find one to use instead. 778 } 779 return regionLocation; 780 } 781 782 private void writeMaxSequenceIdFile(MasterProcedureEnv env) throws IOException { 783 MasterFileSystem fs = env.getMasterFileSystem(); 784 long maxSequenceId = -1L; 785 for (RegionInfo region : regionsToMerge) { 786 maxSequenceId = 787 Math.max(maxSequenceId, WALSplitter.getMaxRegionSequenceId(env.getMasterConfiguration(), 788 region, fs::getFileSystem, fs::getWALFileSystem)); 789 } 790 if (maxSequenceId > 0) { 791 WALSplitter.writeRegionSequenceIdFile(fs.getWALFileSystem(), 792 getWALRegionDir(env, mergedRegion), maxSequenceId); 793 } 794 } 795 796 /** 797 * @return The merged region. Maybe be null if called to early or we failed. 798 */ 799 @VisibleForTesting 800 RegionInfo getMergedRegion() { 801 return this.mergedRegion; 802 } 803 804 @Override 805 protected boolean abort(MasterProcedureEnv env) { 806 // Abort means rollback. We can't rollback all steps. HBASE-18018 added abort to all 807 // Procedures. Here is a Procedure that has a PONR and cannot be aborted once it enters this 808 // range of steps; what do we do for these should an operator want to cancel them? HBASE-20022. 809 return isRollbackSupported(getCurrentState()) && super.abort(env); 810 } 811}