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